diff options
324 files changed, 32714 insertions, 3104 deletions
diff --git a/.qmake.conf b/.qmake.conf index 6ab2606e..48e733ed 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,6 +1,6 @@ load(qt_build_config) CONFIG += qt_example_installs -MODULE_VERSION=1.1.1 +MODULE_VERSION=1.2.0 CMAKE_MODULE_TESTS=- @@ -1,5 +1,5 @@ --------------------------- -Qt Data Visualization 1.1.1 +Qt Data Visualization 1.2.0 --------------------------- Qt Data Visualization module provides multiple graph types to visualize data in 3D space @@ -8,7 +8,7 @@ both with C++ and Qt Quick 2. System Requirements =================== -- Qt 5.2 or newer +- Qt 5.2.1 or newer - OpenGL 2.1 or newer (recommended) or OpenGL ES2 (reduced feature set) - Manipulating Qt Data Visualization graphs with QML Designer requires Qt Creator 3.1 or newer @@ -22,7 +22,7 @@ After running qmake, build the project with make: (Linux) make (Windows with MinGw) mingw32-make (Windows with Visual Studio) nmake - (OSX) make + (OS X) make The above generates the default makefiles for your configuration, which is typically the release build if you are using precompiled binary Qt distribution. To build both @@ -42,7 +42,7 @@ For release builds: qmake CONFIG+=debug_and_release make release -For both builds (Windows/Mac only): +For both builds (Windows/OS X only): qmake CONFIG+="debug_and_release build_all" make @@ -74,17 +74,18 @@ Please refer to the generated documentation for more information: Known Issues ============ -- Android doesn't support both widgets and OpenGL simultaneously, so only - the Qt Quick 2 API is usable in practice in Android. +- Some platforms like Android and WinRT cannot handle multiple native windows properly, + so only the Qt Quick 2 versions of graphs are available in practice for those platforms. - Shadows are not supported with OpenGL ES2 (including Angle builds in Windows). - Anti-aliasing doesn't work with OpenGL ES2 (including Angle builds in Windows). +- QCustom3DVolume items are not supported with OpenGL ES2 (including Angle builds in Windows). - Surfaces with non-straight rows and columns do not always render properly. - Q3DLight class (and Light3D QML item) are currently not usable for anything. -- Changing any of Q3DScene properties affecting subviewports currently has no effect. -- The color style Q3DTheme::ColorStyleObjectGradient doesn't work for surface graphs. +- Changing most of Q3DScene properties affecting subviewports currently has no effect. - Widget based examples layout incorrectly in iOS. - Reparenting a graph to an item in another QQuickWindow is not supported. -- There is a low-impact binary break between 1.0 and 1.1. The break is due to a QML type - registration conflict with QAbstractItemModel between QtDataVisualization and - QtCommercial.Charts. Introducing the binary break makes it possible to use both - Charts and Data Visualization in the same QML application. +- Android builds of QML applications importing QtDataVisualization also require + "QT += datavisualization" in the pro file. This is because Qt Data Visualization QML plugin has + a dependency to Qt Data Visualization C++ library, which Qt Creator doesn't automatically add + to the deployment package. + diff --git a/dist/changes-1.1.0 b/dist/changes-1.1.0 index 7d27438b..d404cf09 100644 --- a/dist/changes-1.1.0 +++ b/dist/changes-1.1.0 @@ -73,4 +73,4 @@ Platform specific changes - Fixed issue with graph not always updating before rotating the graph in iOS. - Fixed shader linking error on some Android versions. -- Fixed memory leaks in Mac and Android builds. +- Fixed memory leaks in OS X and Android builds. diff --git a/dist/changes-1.2.0 b/dist/changes-1.2.0 new file mode 100644 index 00000000..b23d83b1 --- /dev/null +++ b/dist/changes-1.2.0 @@ -0,0 +1,79 @@ +Qt Data Visualization 1.2 + +New features +------------ + +- Added support for volumetric custom objects (QCustom3DVolume) for rendering 3D voxel data. +- Reflection support for bar graphs (floor only). +- Polar horizontal axes supported for scatter and surface graphs. +- Added flipHorizontalGrid property for surface to enable displaying grid in 2D orthographic + projections of the surface (e.g. 2D spectrogram graphs). +- Added horizontalAspectRatio property for graphs to enable better control over graph dimensions. +- Added an API for setting a custom texture for a surface series. +- Added several properties to control the default input handler behavior. +- Exposed default input handlers to QML API. +- Camera can now be targeted at any point within axis ranges on the scatter and surface graphs. + On bar graphs, camera target is limited to any point on the graph floor. +- Added possibility to scale custom items based on data ranges. +- Added a property for specifying the locale for the graph, which affects how various labels + are formatted (e.g. which character is used for the decimal point). +- Added a property for specifying the Y-value of the floor level on bar graphs. +- Added a property to Q3DScene for querying the graph position at a screen position. +- The default input handlers now zoom to cursor/pinch instead of zooming toward the center of the + graph. Added a property to restore the old zoom behavior. +- Added properties to control the minimum and maximum allowable zoom level of the camera. +- Added a method for getting the list of custom items added to the graph. +- Added a property for specifying the graph background margin. + +Fixed issues +------------ + +General: +- Label widths now update consistently when axis range changes. +- Made selection texture creation more robust. +- Grid lines and labels no longer change size if aspect ratio changes. +- Q3DTheme::ColorStyleObjectGradient now works for surface graphs. +- Removed the superfluous common.pri. +- Fixed non-visible selected object drawing in static optimization mode. +- Gradient color styles are now supported equally in both default and static optimization modes. +- Specular highlight now works with rotated objects in static optimization mode. +- Fixed a crash in static optimization mode when data is updated without resizing. +- Fixed changing items in static optimization mode. +- Fixed issues with static optimization mode when some items were outside axis ranges. +- Slice mode grid lines should no longer vanish into the background when using high ambient light + value. +- Reduced the size of the surface selection texture, allowing selection to work with larger + surfaces. +- Fixed QAbstract3DGraph::renderToImage in OpenGL ES2 environments. +- Fixed crash when attempting to enable slicing without row/column selection modes. +- QCustom3DLabels now use the same shader as other labels, which means the specular highlight + no longer makes camera facing custom labels unreadable with some themes. +- Made various selection queries thread safe. +- Fixed selection query synchronization issue when using threaded renderer. +- Font size is automatically reduced if the label gets too wide to fit the label texture. +- Fixed the ordering of the subviews. +- Fixed surface normals in cases where the surface values were not in the same order + (ascending or descending) along both axes. +- Fixed a crash when removing and changing items on the same render frame. +- Fixed an issue with grid line color on surface graphs. +- Prevented selecting bars through the floor in bar graphs. +- Fixed recurring GL_INVALID_VALUE OpenGL errors. +- Improved the surface shadows. +- Fixed the OpenGL context cleanup upon renderer destruction. +- Fixed scatter item autosizing when adding a new series. +- Fixed a crash related to selection render buffer reuse. +- Fixed the flipped Z-coordinate for absolutely positioned custom items. +- Fixed shadows when viewing the graph directly from above or below. + +New examples +------------ + +- Qmlspectrogram example added. It shows how to display 2D spectrogram using surface graph with + gradients and orthographic projection. Also demonstrates the use of polar axes. +- Bars example now demonstrates zooming to selection, which leverages the new ability to + control the camera target. +- Textured surface example added. +- Volumetric example added. It shows how to use the new QCustom3DVolume object to visualize + volumetric data. +- Reflection added to Bars and Customproxy examples. +- Custom camera targeting added to Bars example. diff --git a/examples/datavisualization/audiolevels/audiolevels.cpp b/examples/datavisualization/audiolevels/audiolevels.cpp index 672e4984..c4a6b78c 100644 --- a/examples/datavisualization/audiolevels/audiolevels.cpp +++ b/examples/datavisualization/audiolevels/audiolevels.cpp @@ -74,7 +74,7 @@ AudioLevels::AudioLevels(Q3DBars *graph, QObject *parent) m_audioInput = new QAudioInput(inputDevice, formatAudio, this); #ifdef Q_OS_MAC - // Mac seems to wait for entire buffer to fill before calling writeData, so use smaller buffer + // OS X seems to wait for entire buffer to fill before calling writeData, so use smaller buffer m_audioInput->setBufferSize(256); #else m_audioInput->setBufferSize(1024); diff --git a/examples/datavisualization/bars/doc/images/bars-example.png b/examples/datavisualization/bars/doc/images/bars-example.png Binary files differindex fb79668d..c06fe2c1 100644 --- a/examples/datavisualization/bars/doc/images/bars-example.png +++ b/examples/datavisualization/bars/doc/images/bars-example.png diff --git a/examples/datavisualization/bars/doc/src/bars.qdoc b/examples/datavisualization/bars/doc/src/bars.qdoc index 9717fe8a..1117376f 100644 --- a/examples/datavisualization/bars/doc/src/bars.qdoc +++ b/examples/datavisualization/bars/doc/src/bars.qdoc @@ -188,6 +188,30 @@ You can use the same method with \c SelectionSlice and \c SelectionItem flags, as long as you have either \c SelectionRow or \c SelectionColumn set as well. + \section1 Zooming to selection + + As an example of adjusting camera target we have implemented an animation of zooming to + selection via a button press. Animation initializations are done in the constructor: + + \snippet bars/graphmodifier.cpp 12 + + The function \c{GraphModifier::zoomToSelectedBar()} contains the rest of the functionality: + + \snippet bars/graphmodifier.cpp 11 + + The QPropertyAnimation \c m_animationCameraTarget targets Q3DCamera::target property, + which takes a value normalized to the range (-1, 1). We figure out where the selected bar + is relative to axes, and use that as the end value for \c{m_animationCameraTarget}: + + \snippet bars/graphmodifier.cpp 13 + \dots + \snippet bars/graphmodifier.cpp 14 + + Likewise, we want to angle the camera so that it always points approximately to the center of + the graph at the end of the animation: + + \snippet bars/graphmodifier.cpp 15 + \section1 Example contents */ diff --git a/examples/datavisualization/bars/graphmodifier.cpp b/examples/datavisualization/bars/graphmodifier.cpp index 9c280bfb..587bc1d6 100644 --- a/examples/datavisualization/bars/graphmodifier.cpp +++ b/examples/datavisualization/bars/graphmodifier.cpp @@ -26,6 +26,7 @@ #include <QtDataVisualization/q3dtheme.h> #include <QtCore/QTime> #include <QtWidgets/QComboBox> +#include <QtCore/qmath.h> using namespace QtDataVisualization; @@ -106,6 +107,39 @@ GraphModifier::GraphModifier(Q3DBars *bargraph) //! [9] resetTemperatureData(); //! [9] + + // Set up property animations for zooming to the selected bar + //! [12] + Q3DCamera *camera = m_graph->scene()->activeCamera(); + m_defaultAngleX = camera->xRotation(); + m_defaultAngleY = camera->yRotation(); + m_defaultZoom = camera->zoomLevel(); + m_defaultTarget = camera->target(); + + m_animationCameraX.setTargetObject(camera); + m_animationCameraY.setTargetObject(camera); + m_animationCameraZoom.setTargetObject(camera); + m_animationCameraTarget.setTargetObject(camera); + + m_animationCameraX.setPropertyName("xRotation"); + m_animationCameraY.setPropertyName("yRotation"); + m_animationCameraZoom.setPropertyName("zoomLevel"); + m_animationCameraTarget.setPropertyName("target"); + + int duration = 1700; + m_animationCameraX.setDuration(duration); + m_animationCameraY.setDuration(duration); + m_animationCameraZoom.setDuration(duration); + m_animationCameraTarget.setDuration(duration); + + // The zoom always first zooms out above the graph and then zooms in + qreal zoomOutFraction = 0.3; + m_animationCameraX.setKeyValueAt(zoomOutFraction, QVariant::fromValue(0.0f)); + m_animationCameraY.setKeyValueAt(zoomOutFraction, QVariant::fromValue(90.0f)); + m_animationCameraZoom.setKeyValueAt(zoomOutFraction, QVariant::fromValue(50.0f)); + m_animationCameraTarget.setKeyValueAt(zoomOutFraction, + QVariant::fromValue(QVector3D(0.0f, 0.0f, 0.0f))); + //! [12] } //! [0] @@ -187,6 +221,14 @@ void GraphModifier::changeStyle(int style) void GraphModifier::changePresetCamera() { + m_animationCameraX.stop(); + m_animationCameraY.stop(); + m_animationCameraZoom.stop(); + m_animationCameraTarget.stop(); + + // Restore camera target in case animation has changed it + m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); + //! [10] static int preset = Q3DCamera::CameraPresetFront; @@ -263,6 +305,74 @@ void GraphModifier::setAxisTitleFixed(bool enabled) m_yearAxis->setTitleFixed(enabled); } +//! [11] +void GraphModifier::zoomToSelectedBar() +{ + m_animationCameraX.stop(); + m_animationCameraY.stop(); + m_animationCameraZoom.stop(); + m_animationCameraTarget.stop(); + + Q3DCamera *camera = m_graph->scene()->activeCamera(); + float currentX = camera->xRotation(); + float currentY = camera->yRotation(); + float currentZoom = camera->zoomLevel(); + QVector3D currentTarget = camera->target(); + + m_animationCameraX.setStartValue(QVariant::fromValue(currentX)); + m_animationCameraY.setStartValue(QVariant::fromValue(currentY)); + m_animationCameraZoom.setStartValue(QVariant::fromValue(currentZoom)); + m_animationCameraTarget.setStartValue(QVariant::fromValue(currentTarget)); + + QPoint selectedBar = m_graph->selectedSeries() + ? m_graph->selectedSeries()->selectedBar() + : QBar3DSeries::invalidSelectionPosition(); + + if (selectedBar != QBar3DSeries::invalidSelectionPosition()) { + // Normalize selected bar position within axis range to determine target coordinates + //! [13] + QVector3D endTarget; + float xMin = m_graph->columnAxis()->min(); + float xRange = m_graph->columnAxis()->max() - xMin; + float zMin = m_graph->rowAxis()->min(); + float zRange = m_graph->rowAxis()->max() - zMin; + endTarget.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f); + endTarget.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f); + //! [13] + + // Rotate the camera so that it always points approximately to the graph center + //! [15] + qreal endAngleX = qAtan(qreal(endTarget.z() / endTarget.x())) / M_PI * -180.0 + 90.0; + if (endTarget.x() > 0.0f) + endAngleX -= 180.0f; + float barValue = m_graph->selectedSeries()->dataProxy()->itemAt(selectedBar.x(), + selectedBar.y())->value(); + float endAngleY = barValue >= 0.0f ? 30.0f : -30.0f; + if (m_graph->valueAxis()->reversed()) + endAngleY *= -1.0f; + //! [15] + + m_animationCameraX.setEndValue(QVariant::fromValue(float(endAngleX))); + m_animationCameraY.setEndValue(QVariant::fromValue(endAngleY)); + m_animationCameraZoom.setEndValue(QVariant::fromValue(250)); + //! [14] + m_animationCameraTarget.setEndValue(QVariant::fromValue(endTarget)); + //! [14] + } else { + // No selected bar, so return to the default view + m_animationCameraX.setEndValue(QVariant::fromValue(m_defaultAngleX)); + m_animationCameraY.setEndValue(QVariant::fromValue(m_defaultAngleY)); + m_animationCameraZoom.setEndValue(QVariant::fromValue(m_defaultZoom)); + m_animationCameraTarget.setEndValue(QVariant::fromValue(m_defaultTarget)); + } + + m_animationCameraX.start(); + m_animationCameraY.start(); + m_animationCameraZoom.start(); + m_animationCameraTarget.start(); +} +//! [11] + void GraphModifier::changeShadowQuality(int quality) { QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality); @@ -310,3 +420,8 @@ void GraphModifier::setReverseValueAxis(int enabled) { m_graph->valueAxis()->setReversed(enabled); } + +void GraphModifier::setReflection(bool enabled) +{ + m_graph->setReflection(enabled); +} diff --git a/examples/datavisualization/bars/graphmodifier.h b/examples/datavisualization/bars/graphmodifier.h index 107ffbab..22b00923 100644 --- a/examples/datavisualization/bars/graphmodifier.h +++ b/examples/datavisualization/bars/graphmodifier.h @@ -27,6 +27,7 @@ #include <QtCore/QDebug> #include <QtCore/QStringList> #include <QtCore/QPointer> +#include <QtCore/QPropertyAnimation> using namespace QtDataVisualization; @@ -49,6 +50,7 @@ public: void setSmoothBars(int smooth); void setSeriesVisibility(int enabled); void setReverseValueAxis(int enabled); + void setReflection(bool enabled); public slots: void changeRange(int range); @@ -60,6 +62,7 @@ public slots: void changeLabelRotation(int rotation); void setAxisTitleVisibility(bool enabled); void setAxisTitleFixed(bool enabled); + void zoomToSelectedBar(); signals: void shadowQualityChanged(int quality); @@ -86,6 +89,14 @@ private: QBar3DSeries *m_secondarySeries; QAbstract3DSeries::Mesh m_barMesh; bool m_smooth; + QPropertyAnimation m_animationCameraX; + QPropertyAnimation m_animationCameraY; + QPropertyAnimation m_animationCameraZoom; + QPropertyAnimation m_animationCameraTarget; + float m_defaultAngleX; + float m_defaultAngleY; + float m_defaultZoom; + QVector3D m_defaultTarget; }; #endif diff --git a/examples/datavisualization/bars/main.cpp b/examples/datavisualization/bars/main.cpp index 96abdc51..a7df14e0 100644 --- a/examples/datavisualization/bars/main.cpp +++ b/examples/datavisualization/bars/main.cpp @@ -72,6 +72,7 @@ int main(int argc, char **argv) smoothCheckBox->setText(QStringLiteral("Smooth bars")); smoothCheckBox->setChecked(false); + QComboBox *barStyleList = new QComboBox(widget); barStyleList->addItem(QStringLiteral("Bar"), int(QAbstract3DSeries::MeshBar)); barStyleList->addItem(QStringLiteral("Pyramid"), int(QAbstract3DSeries::MeshPyramid)); @@ -84,6 +85,9 @@ int main(int argc, char **argv) QPushButton *cameraButton = new QPushButton(widget); cameraButton->setText(QStringLiteral("Change camera preset")); + QPushButton *zoomToSelectedButton = new QPushButton(widget); + zoomToSelectedButton->setText(QStringLiteral("Zoom to selected bar")); + QComboBox *selectionModeList = new QComboBox(widget); selectionModeList->addItem(QStringLiteral("None"), int(QAbstract3DGraph::SelectionNone)); @@ -136,6 +140,10 @@ int main(int argc, char **argv) reverseValueAxisCheckBox->setText(QStringLiteral("Reverse value axis")); reverseValueAxisCheckBox->setChecked(false); + QCheckBox *reflectionCheckBox = new QCheckBox(widget); + reflectionCheckBox->setText(QStringLiteral("Show reflections")); + reflectionCheckBox->setChecked(false); + //! [4] QSlider *rotationSliderX = new QSlider(Qt::Horizontal, widget); rotationSliderX->setTickInterval(30); @@ -206,9 +214,11 @@ int main(int argc, char **argv) //! [5] vLayout->addWidget(labelButton, 0, Qt::AlignTop); vLayout->addWidget(cameraButton, 0, Qt::AlignTop); + vLayout->addWidget(zoomToSelectedButton, 0, Qt::AlignTop); vLayout->addWidget(backgroundCheckBox); vLayout->addWidget(gridCheckBox); vLayout->addWidget(smoothCheckBox); + vLayout->addWidget(reflectionCheckBox); vLayout->addWidget(seriesCheckBox); vLayout->addWidget(reverseValueAxisCheckBox); vLayout->addWidget(axisTitlesVisibleCB); @@ -243,6 +253,8 @@ int main(int argc, char **argv) &GraphModifier::changeLabelBackground); QObject::connect(cameraButton, &QPushButton::clicked, modifier, &GraphModifier::changePresetCamera); + QObject::connect(zoomToSelectedButton, &QPushButton::clicked, modifier, + &GraphModifier::zoomToSelectedBar); QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier, &GraphModifier::setBackgroundEnabled); @@ -254,6 +266,8 @@ int main(int argc, char **argv) &GraphModifier::setSeriesVisibility); QObject::connect(reverseValueAxisCheckBox, &QCheckBox::stateChanged, modifier, &GraphModifier::setReverseValueAxis); + QObject::connect(reflectionCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setReflection); QObject::connect(modifier, &GraphModifier::backgroundEnabledChanged, backgroundCheckBox, &QCheckBox::setChecked); diff --git a/examples/datavisualization/customproxy/doc/images/customproxy-example.png b/examples/datavisualization/customproxy/doc/images/customproxy-example.png Binary files differindex 753b8951..4f82943a 100644 --- a/examples/datavisualization/customproxy/doc/images/customproxy-example.png +++ b/examples/datavisualization/customproxy/doc/images/customproxy-example.png diff --git a/examples/datavisualization/customproxy/rainfallgraph.cpp b/examples/datavisualization/customproxy/rainfallgraph.cpp index 024fc2a1..bcd67acc 100644 --- a/examples/datavisualization/customproxy/rainfallgraph.cpp +++ b/examples/datavisualization/customproxy/rainfallgraph.cpp @@ -49,7 +49,7 @@ RainfallGraph::RainfallGraph(Q3DBars *rainfall) // Set up bar specifications; make the bars as wide as they are deep, // and add a small space between the bars m_graph->setBarThickness(1.0f); - m_graph->setBarSpacing(QSizeF(0.2, 0.2)); + m_graph->setBarSpacing(QSizeF(1.1, 1.1)); // Set axis labels and titles QStringList months; @@ -85,6 +85,9 @@ RainfallGraph::RainfallGraph(Q3DBars *rainfall) // Set window title m_graph->setTitle(QStringLiteral("Monthly rainfall in Northern Finland")); + + // Set reflections on + m_graph->setReflection(true); } RainfallGraph::~RainfallGraph() diff --git a/examples/datavisualization/datavisualization.pro b/examples/datavisualization/datavisualization.pro index 1f72820c..b6ece48d 100644 --- a/examples/datavisualization/datavisualization.pro +++ b/examples/datavisualization/datavisualization.pro @@ -1,14 +1,17 @@ TEMPLATE = subdirs -SUBDIRS += qmlbars \ - qmlscatter \ - qmlsurface \ - qmlcustominput \ - qmllegend \ - qmlmultigraph \ - qmloscilloscope \ - qmlsurfacelayers \ - qmlaxisformatter \ - qmlaxisdrag +qtHaveModule(quick) { + SUBDIRS += qmlbars \ + qmlscatter \ + qmlsurface \ + qmlcustominput \ + qmllegend \ + qmlmultigraph \ + qmloscilloscope \ + qmlsurfacelayers \ + qmlaxisformatter \ + qmlaxisdrag \ + qmlspectrogram +} !android:!ios { SUBDIRS += bars \ @@ -19,7 +22,10 @@ SUBDIRS += qmlbars \ surface \ rotations \ draggableaxes \ - customitems + customitems \ + texturesurface \ + volumetric + + qtHaveModule(multimedia): SUBDIRS += audiolevels } -qtHaveModule(multimedia):!android:!ios: SUBDIRS += audiolevels diff --git a/examples/datavisualization/qmlspectrogram/doc/images/qmlspectrogram-example.png b/examples/datavisualization/qmlspectrogram/doc/images/qmlspectrogram-example.png Binary files differnew file mode 100644 index 00000000..de376cd9 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/doc/images/qmlspectrogram-example.png diff --git a/examples/datavisualization/qmlspectrogram/doc/src/qmlspectrogram.qdoc b/examples/datavisualization/qmlspectrogram/doc/src/qmlspectrogram.qdoc new file mode 100644 index 00000000..0aebdde0 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/doc/src/qmlspectrogram.qdoc @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 qmlspectrogram + \title Qt Quick 2 Spectrogram Example + \ingroup qtdatavisualization_examples + \brief Showing spectrogram graph in a QML application. + + The Qt Quick 2 Spectrogram example demonstrates how to show a polar and cartesian spectrograms + and how to utilize orthographic projection to show them in 2D. + + \image qmlspectrogram-example.png + + Spectrogram is simply a surface graph with a range gradient used to emphasize the different + values. Typically spectrograms are shown with two dimensional surfaces, which we simulate + with a top down orthographic view of the graph. To enforce the 2D effect, we disable the + graph rotation via mouse or touch when in the orthographic mode. + + The focus in this example is on showing how to display spectrograms, so the basic + functionality is not explained. For more detailed QML example documentation, + see \l{Qt Quick 2 Scatter Example}. + + \section1 Creating a spectrogram + + To create a 2D spectrogram, we define a Surface3D item: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 0 + + The key properties for enabling the 2D effect are + \l{AbstractGraph3D::orthoProjection}{orthoProjection} and + \l{Camera3D::cameraPreset}{scene.activeCamera.cameraPreset}. We remove the perspective by + enabling orthographic projection for the graph, and then we eliminate the Y-dimension by + viewing the graph directly from above: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 1 + + Since this viewpoint causes the horizontal axis grid to be mostly obscured by the surface, + we also specify that the horizontal grid should be drawn on top of the graph: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 2 + + \section1 Polar spectrogram + + Depending on the data, it is sometimes more natural to use a polar graph instead of a cartesian + one. Qt Data Visualization supports this via \l{AbstractGraph3D::polar}{polar} property. + In this example we provide a button to switch between polar and cartesian modes: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 3 + + In the polar mode, the X-axis is converted into the angular polar axis, and the Z-axis is + converted into the radial polar axis. The surface points are recalculated according to new axes. + + The radial axis labels are drawn outside the graph by default, but in this example we want to + draw them right next to the 0 degree angular axis inside the graph, so we define only a tiny + offset for them: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 4 + + To enforce the 2D effect, graph rotation via user input is disabled when in orthographic mode. + We do this by specifying a new input handler: + + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 5 + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 7 + \dots 0 + \snippet qmlspectrogram/qml/qmlspectrogram/main.qml 6 + \dots 0 + + When the projection mode changes, we adjust the value of the + \l{InputHandler3D::rotationEnabled}{rotationEnabled} property of the \c{customInputHandler} + to control the rotation. + + \section1 Example contents +*/ diff --git a/examples/datavisualization/qmlspectrogram/main.cpp b/examples/datavisualization/qmlspectrogram/main.cpp new file mode 100644 index 00000000..87665564 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/main.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 <QtGui/QGuiApplication> +#include <QtCore/QDir> +#include <QtQuick/QQuickView> +#include <QtQml/QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + // The following are needed to make examples run without having to install the module + // in desktop environments. +#ifdef Q_OS_WIN + QString extraImportPath(QStringLiteral("%1/../../../../%2")); +#else + QString extraImportPath(QStringLiteral("%1/../../../%2")); +#endif + engine.addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(), + QString::fromLatin1("qml"))); + engine.load(QUrl(QStringLiteral("qrc:/qml/qml/qmlspectrogram/main.qml"))); + + return app.exec(); +} diff --git a/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/Data.qml b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/Data.qml new file mode 100644 index 00000000..fc54edf4 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/Data.qml @@ -0,0 +1,1560 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 + +Item { + property alias model: dataModel + + ListModel { + id: dataModel + ListElement{ radius: "0"; angle: "0"; value: "50"; } + ListElement{ radius: "0"; angle: "5"; value: "54.3578"; } + ListElement{ radius: "0"; angle: "10"; value: "58.6824"; } + ListElement{ radius: "0"; angle: "15"; value: "62.941"; } + ListElement{ radius: "0"; angle: "20"; value: "67.101"; } + ListElement{ radius: "0"; angle: "25"; value: "71.1309"; } + ListElement{ radius: "0"; angle: "30"; value: "75"; } + ListElement{ radius: "0"; angle: "35"; value: "78.6788"; } + ListElement{ radius: "0"; angle: "40"; value: "82.1394"; } + ListElement{ radius: "0"; angle: "45"; value: "85.3553"; } + ListElement{ radius: "0"; angle: "50"; value: "88.3022"; } + ListElement{ radius: "0"; angle: "55"; value: "90.9576"; } + ListElement{ radius: "0"; angle: "60"; value: "93.3013"; } + ListElement{ radius: "0"; angle: "65"; value: "95.3154"; } + ListElement{ radius: "0"; angle: "70"; value: "96.9846"; } + ListElement{ radius: "0"; angle: "75"; value: "98.2963"; } + ListElement{ radius: "0"; angle: "80"; value: "99.2404"; } + ListElement{ radius: "0"; angle: "85"; value: "99.8097"; } + ListElement{ radius: "0"; angle: "90"; value: "100"; } + ListElement{ radius: "0"; angle: "95"; value: "99.8097"; } + ListElement{ radius: "0"; angle: "100"; value: "99.2404"; } + ListElement{ radius: "0"; angle: "105"; value: "98.2963"; } + ListElement{ radius: "0"; angle: "110"; value: "96.9846"; } + ListElement{ radius: "0"; angle: "115"; value: "95.3154"; } + ListElement{ radius: "0"; angle: "120"; value: "93.3013"; } + ListElement{ radius: "0"; angle: "125"; value: "90.9576"; } + ListElement{ radius: "0"; angle: "130"; value: "88.3022"; } + ListElement{ radius: "0"; angle: "135"; value: "85.3553"; } + ListElement{ radius: "0"; angle: "140"; value: "82.1394"; } + ListElement{ radius: "0"; angle: "145"; value: "78.6788"; } + ListElement{ radius: "0"; angle: "150"; value: "75"; } + ListElement{ radius: "0"; angle: "155"; value: "71.1309"; } + ListElement{ radius: "0"; angle: "160"; value: "67.101"; } + ListElement{ radius: "0"; angle: "165"; value: "62.941"; } + ListElement{ radius: "0"; angle: "170"; value: "58.6824"; } + ListElement{ radius: "0"; angle: "175"; value: "54.3578"; } + ListElement{ radius: "0"; angle: "180"; value: "50"; } + ListElement{ radius: "0"; angle: "185"; value: "45.6422"; } + ListElement{ radius: "0"; angle: "190"; value: "41.3176"; } + ListElement{ radius: "0"; angle: "195"; value: "37.059"; } + ListElement{ radius: "0"; angle: "200"; value: "32.899"; } + ListElement{ radius: "0"; angle: "205"; value: "28.8691"; } + ListElement{ radius: "0"; angle: "210"; value: "25"; } + ListElement{ radius: "0"; angle: "215"; value: "21.3212"; } + ListElement{ radius: "0"; angle: "220"; value: "17.8606"; } + ListElement{ radius: "0"; angle: "225"; value: "14.6447"; } + ListElement{ radius: "0"; angle: "230"; value: "11.6978"; } + ListElement{ radius: "0"; angle: "235"; value: "9.0424"; } + ListElement{ radius: "0"; angle: "240"; value: "6.69873"; } + ListElement{ radius: "0"; angle: "245"; value: "4.68461"; } + ListElement{ radius: "0"; angle: "250"; value: "3.01537"; } + ListElement{ radius: "0"; angle: "255"; value: "1.70371"; } + ListElement{ radius: "0"; angle: "260"; value: "0.759612"; } + ListElement{ radius: "0"; angle: "265"; value: "0.190265"; } + ListElement{ radius: "0"; angle: "270"; value: "0"; } + ListElement{ radius: "0"; angle: "275"; value: "0.190265"; } + ListElement{ radius: "0"; angle: "280"; value: "0.759612"; } + ListElement{ radius: "0"; angle: "285"; value: "1.70371"; } + ListElement{ radius: "0"; angle: "290"; value: "3.01537"; } + ListElement{ radius: "0"; angle: "295"; value: "4.68461"; } + ListElement{ radius: "0"; angle: "300"; value: "6.69873"; } + ListElement{ radius: "0"; angle: "305"; value: "9.0424"; } + ListElement{ radius: "0"; angle: "310"; value: "11.6978"; } + ListElement{ radius: "0"; angle: "315"; value: "14.6447"; } + ListElement{ radius: "0"; angle: "320"; value: "17.8606"; } + ListElement{ radius: "0"; angle: "325"; value: "21.3212"; } + ListElement{ radius: "0"; angle: "330"; value: "25"; } + ListElement{ radius: "0"; angle: "335"; value: "28.8691"; } + ListElement{ radius: "0"; angle: "340"; value: "32.899"; } + ListElement{ radius: "0"; angle: "345"; value: "37.059"; } + ListElement{ radius: "0"; angle: "350"; value: "41.3176"; } + ListElement{ radius: "0"; angle: "355"; value: "45.6422"; } + ListElement{ radius: "0"; angle: "360"; value: "50"; } + ListElement{ radius: "5"; angle: "0"; value: "49.3844"; } + ListElement{ radius: "5"; angle: "5"; value: "53.7422"; } + ListElement{ radius: "5"; angle: "10"; value: "58.0668"; } + ListElement{ radius: "5"; angle: "15"; value: "62.3254"; } + ListElement{ radius: "5"; angle: "20"; value: "66.4854"; } + ListElement{ radius: "5"; angle: "25"; value: "70.5153"; } + ListElement{ radius: "5"; angle: "30"; value: "74.3844"; } + ListElement{ radius: "5"; angle: "35"; value: "78.0632"; } + ListElement{ radius: "5"; angle: "40"; value: "81.5238"; } + ListElement{ radius: "5"; angle: "45"; value: "84.7398"; } + ListElement{ radius: "5"; angle: "50"; value: "87.6866"; } + ListElement{ radius: "5"; angle: "55"; value: "90.342"; } + ListElement{ radius: "5"; angle: "60"; value: "92.6857"; } + ListElement{ radius: "5"; angle: "65"; value: "94.6998"; } + ListElement{ radius: "5"; angle: "70"; value: "96.369"; } + ListElement{ radius: "5"; angle: "75"; value: "97.6807"; } + ListElement{ radius: "5"; angle: "80"; value: "98.6248"; } + ListElement{ radius: "5"; angle: "85"; value: "99.1942"; } + ListElement{ radius: "5"; angle: "90"; value: "99.3844"; } + ListElement{ radius: "5"; angle: "95"; value: "99.1942"; } + ListElement{ radius: "5"; angle: "100"; value: "98.6248"; } + ListElement{ radius: "5"; angle: "105"; value: "97.6807"; } + ListElement{ radius: "5"; angle: "110"; value: "96.369"; } + ListElement{ radius: "5"; angle: "115"; value: "94.6998"; } + ListElement{ radius: "5"; angle: "120"; value: "92.6857"; } + ListElement{ radius: "5"; angle: "125"; value: "90.342"; } + ListElement{ radius: "5"; angle: "130"; value: "87.6866"; } + ListElement{ radius: "5"; angle: "135"; value: "84.7398"; } + ListElement{ radius: "5"; angle: "140"; value: "81.5238"; } + ListElement{ radius: "5"; angle: "145"; value: "78.0632"; } + ListElement{ radius: "5"; angle: "150"; value: "74.3844"; } + ListElement{ radius: "5"; angle: "155"; value: "70.5153"; } + ListElement{ radius: "5"; angle: "160"; value: "66.4854"; } + ListElement{ radius: "5"; angle: "165"; value: "62.3254"; } + ListElement{ radius: "5"; angle: "170"; value: "58.0668"; } + ListElement{ radius: "5"; angle: "175"; value: "53.7422"; } + ListElement{ radius: "5"; angle: "180"; value: "49.3844"; } + ListElement{ radius: "5"; angle: "185"; value: "45.0266"; } + ListElement{ radius: "5"; angle: "190"; value: "40.702"; } + ListElement{ radius: "5"; angle: "195"; value: "36.4435"; } + ListElement{ radius: "5"; angle: "200"; value: "32.2834"; } + ListElement{ radius: "5"; angle: "205"; value: "28.2535"; } + ListElement{ radius: "5"; angle: "210"; value: "24.3844"; } + ListElement{ radius: "5"; angle: "215"; value: "20.7056"; } + ListElement{ radius: "5"; angle: "220"; value: "17.245"; } + ListElement{ radius: "5"; angle: "225"; value: "14.0291"; } + ListElement{ radius: "5"; angle: "230"; value: "11.0822"; } + ListElement{ radius: "5"; angle: "235"; value: "8.42681"; } + ListElement{ radius: "5"; angle: "240"; value: "6.08315"; } + ListElement{ radius: "5"; angle: "245"; value: "4.06903"; } + ListElement{ radius: "5"; angle: "250"; value: "2.39979"; } + ListElement{ radius: "5"; angle: "255"; value: "1.08813"; } + ListElement{ radius: "5"; angle: "260"; value: "0.144029"; } + ListElement{ radius: "5"; angle: "265"; value: "-0.425318"; } + ListElement{ radius: "5"; angle: "270"; value: "-0.615583"; } + ListElement{ radius: "5"; angle: "275"; value: "-0.425318"; } + ListElement{ radius: "5"; angle: "280"; value: "0.144029"; } + ListElement{ radius: "5"; angle: "285"; value: "1.08813"; } + ListElement{ radius: "5"; angle: "290"; value: "2.39979"; } + ListElement{ radius: "5"; angle: "295"; value: "4.06903"; } + ListElement{ radius: "5"; angle: "300"; value: "6.08315"; } + ListElement{ radius: "5"; angle: "305"; value: "8.42681"; } + ListElement{ radius: "5"; angle: "310"; value: "11.0822"; } + ListElement{ radius: "5"; angle: "315"; value: "14.0291"; } + ListElement{ radius: "5"; angle: "320"; value: "17.245"; } + ListElement{ radius: "5"; angle: "325"; value: "20.7056"; } + ListElement{ radius: "5"; angle: "330"; value: "24.3844"; } + ListElement{ radius: "5"; angle: "335"; value: "28.2535"; } + ListElement{ radius: "5"; angle: "340"; value: "32.2834"; } + ListElement{ radius: "5"; angle: "345"; value: "36.4435"; } + ListElement{ radius: "5"; angle: "350"; value: "40.702"; } + ListElement{ radius: "5"; angle: "355"; value: "45.0266"; } + ListElement{ radius: "5"; angle: "360"; value: "49.3844"; } + ListElement{ radius: "10"; angle: "0"; value: "47.5528"; } + ListElement{ radius: "10"; angle: "5"; value: "51.9106"; } + ListElement{ radius: "10"; angle: "10"; value: "56.2352"; } + ListElement{ radius: "10"; angle: "15"; value: "60.4938"; } + ListElement{ radius: "10"; angle: "20"; value: "64.6538"; } + ListElement{ radius: "10"; angle: "25"; value: "68.6837"; } + ListElement{ radius: "10"; angle: "30"; value: "72.5528"; } + ListElement{ radius: "10"; angle: "35"; value: "76.2316"; } + ListElement{ radius: "10"; angle: "40"; value: "79.6922"; } + ListElement{ radius: "10"; angle: "45"; value: "82.9082"; } + ListElement{ radius: "10"; angle: "50"; value: "85.855"; } + ListElement{ radius: "10"; angle: "55"; value: "88.5104"; } + ListElement{ radius: "10"; angle: "60"; value: "90.8541"; } + ListElement{ radius: "10"; angle: "65"; value: "92.8682"; } + ListElement{ radius: "10"; angle: "70"; value: "94.5375"; } + ListElement{ radius: "10"; angle: "75"; value: "95.8491"; } + ListElement{ radius: "10"; angle: "80"; value: "96.7932"; } + ListElement{ radius: "10"; angle: "85"; value: "97.3626"; } + ListElement{ radius: "10"; angle: "90"; value: "97.5528"; } + ListElement{ radius: "10"; angle: "95"; value: "97.3626"; } + ListElement{ radius: "10"; angle: "100"; value: "96.7932"; } + ListElement{ radius: "10"; angle: "105"; value: "95.8491"; } + ListElement{ radius: "10"; angle: "110"; value: "94.5375"; } + ListElement{ radius: "10"; angle: "115"; value: "92.8682"; } + ListElement{ radius: "10"; angle: "120"; value: "90.8541"; } + ListElement{ radius: "10"; angle: "125"; value: "88.5104"; } + ListElement{ radius: "10"; angle: "130"; value: "85.855"; } + ListElement{ radius: "10"; angle: "135"; value: "82.9082"; } + ListElement{ radius: "10"; angle: "140"; value: "79.6922"; } + ListElement{ radius: "10"; angle: "145"; value: "76.2316"; } + ListElement{ radius: "10"; angle: "150"; value: "72.5528"; } + ListElement{ radius: "10"; angle: "155"; value: "68.6837"; } + ListElement{ radius: "10"; angle: "160"; value: "64.6538"; } + ListElement{ radius: "10"; angle: "165"; value: "60.4938"; } + ListElement{ radius: "10"; angle: "170"; value: "56.2352"; } + ListElement{ radius: "10"; angle: "175"; value: "51.9106"; } + ListElement{ radius: "10"; angle: "180"; value: "47.5528"; } + ListElement{ radius: "10"; angle: "185"; value: "43.195"; } + ListElement{ radius: "10"; angle: "190"; value: "38.8704"; } + ListElement{ radius: "10"; angle: "195"; value: "34.6119"; } + ListElement{ radius: "10"; angle: "200"; value: "30.4518"; } + ListElement{ radius: "10"; angle: "205"; value: "26.4219"; } + ListElement{ radius: "10"; angle: "210"; value: "22.5528"; } + ListElement{ radius: "10"; angle: "215"; value: "18.874"; } + ListElement{ radius: "10"; angle: "220"; value: "15.4134"; } + ListElement{ radius: "10"; angle: "225"; value: "12.1975"; } + ListElement{ radius: "10"; angle: "230"; value: "9.2506"; } + ListElement{ radius: "10"; angle: "235"; value: "6.59522"; } + ListElement{ radius: "10"; angle: "240"; value: "4.25156"; } + ListElement{ radius: "10"; angle: "245"; value: "2.23744"; } + ListElement{ radius: "10"; angle: "250"; value: "0.568195"; } + ListElement{ radius: "10"; angle: "255"; value: "-0.743465"; } + ListElement{ radius: "10"; angle: "260"; value: "-1.68756"; } + ListElement{ radius: "10"; angle: "265"; value: "-2.25691"; } + ListElement{ radius: "10"; angle: "270"; value: "-2.44717"; } + ListElement{ radius: "10"; angle: "275"; value: "-2.25691"; } + ListElement{ radius: "10"; angle: "280"; value: "-1.68756"; } + ListElement{ radius: "10"; angle: "285"; value: "-0.743465"; } + ListElement{ radius: "10"; angle: "290"; value: "0.568195"; } + ListElement{ radius: "10"; angle: "295"; value: "2.23744"; } + ListElement{ radius: "10"; angle: "300"; value: "4.25156"; } + ListElement{ radius: "10"; angle: "305"; value: "6.59522"; } + ListElement{ radius: "10"; angle: "310"; value: "9.2506"; } + ListElement{ radius: "10"; angle: "315"; value: "12.1975"; } + ListElement{ radius: "10"; angle: "320"; value: "15.4134"; } + ListElement{ radius: "10"; angle: "325"; value: "18.874"; } + ListElement{ radius: "10"; angle: "330"; value: "22.5528"; } + ListElement{ radius: "10"; angle: "335"; value: "26.4219"; } + ListElement{ radius: "10"; angle: "340"; value: "30.4518"; } + ListElement{ radius: "10"; angle: "345"; value: "34.6119"; } + ListElement{ radius: "10"; angle: "350"; value: "38.8704"; } + ListElement{ radius: "10"; angle: "355"; value: "43.195"; } + ListElement{ radius: "10"; angle: "360"; value: "47.5528"; } + ListElement{ radius: "15"; angle: "0"; value: "44.5503"; } + ListElement{ radius: "15"; angle: "5"; value: "48.9081"; } + ListElement{ radius: "15"; angle: "10"; value: "53.2327"; } + ListElement{ radius: "15"; angle: "15"; value: "57.4913"; } + ListElement{ radius: "15"; angle: "20"; value: "61.6513"; } + ListElement{ radius: "15"; angle: "25"; value: "65.6812"; } + ListElement{ radius: "15"; angle: "30"; value: "69.5503"; } + ListElement{ radius: "15"; angle: "35"; value: "73.2291"; } + ListElement{ radius: "15"; angle: "40"; value: "76.6897"; } + ListElement{ radius: "15"; angle: "45"; value: "79.9057"; } + ListElement{ radius: "15"; angle: "50"; value: "82.8525"; } + ListElement{ radius: "15"; angle: "55"; value: "85.5079"; } + ListElement{ radius: "15"; angle: "60"; value: "87.8516"; } + ListElement{ radius: "15"; angle: "65"; value: "89.8657"; } + ListElement{ radius: "15"; angle: "70"; value: "91.535"; } + ListElement{ radius: "15"; angle: "75"; value: "92.8466"; } + ListElement{ radius: "15"; angle: "80"; value: "93.7907"; } + ListElement{ radius: "15"; angle: "85"; value: "94.3601"; } + ListElement{ radius: "15"; angle: "90"; value: "94.5503"; } + ListElement{ radius: "15"; angle: "95"; value: "94.3601"; } + ListElement{ radius: "15"; angle: "100"; value: "93.7907"; } + ListElement{ radius: "15"; angle: "105"; value: "92.8466"; } + ListElement{ radius: "15"; angle: "110"; value: "91.535"; } + ListElement{ radius: "15"; angle: "115"; value: "89.8657"; } + ListElement{ radius: "15"; angle: "120"; value: "87.8516"; } + ListElement{ radius: "15"; angle: "125"; value: "85.5079"; } + ListElement{ radius: "15"; angle: "130"; value: "82.8525"; } + ListElement{ radius: "15"; angle: "135"; value: "79.9057"; } + ListElement{ radius: "15"; angle: "140"; value: "76.6897"; } + ListElement{ radius: "15"; angle: "145"; value: "73.2291"; } + ListElement{ radius: "15"; angle: "150"; value: "69.5503"; } + ListElement{ radius: "15"; angle: "155"; value: "65.6812"; } + ListElement{ radius: "15"; angle: "160"; value: "61.6513"; } + ListElement{ radius: "15"; angle: "165"; value: "57.4913"; } + ListElement{ radius: "15"; angle: "170"; value: "53.2327"; } + ListElement{ radius: "15"; angle: "175"; value: "48.9081"; } + ListElement{ radius: "15"; angle: "180"; value: "44.5503"; } + ListElement{ radius: "15"; angle: "185"; value: "40.1925"; } + ListElement{ radius: "15"; angle: "190"; value: "35.8679"; } + ListElement{ radius: "15"; angle: "195"; value: "31.6094"; } + ListElement{ radius: "15"; angle: "200"; value: "27.4493"; } + ListElement{ radius: "15"; angle: "205"; value: "23.4194"; } + ListElement{ radius: "15"; angle: "210"; value: "19.5503"; } + ListElement{ radius: "15"; angle: "215"; value: "15.8715"; } + ListElement{ radius: "15"; angle: "220"; value: "12.4109"; } + ListElement{ radius: "15"; angle: "225"; value: "9.19499"; } + ListElement{ radius: "15"; angle: "230"; value: "6.2481"; } + ListElement{ radius: "15"; angle: "235"; value: "3.59272"; } + ListElement{ radius: "15"; angle: "240"; value: "1.24906"; } + ListElement{ radius: "15"; angle: "245"; value: "-0.765063"; } + ListElement{ radius: "15"; angle: "250"; value: "-2.4343"; } + ListElement{ radius: "15"; angle: "255"; value: "-3.74597"; } + ListElement{ radius: "15"; angle: "260"; value: "-4.69006"; } + ListElement{ radius: "15"; angle: "265"; value: "-5.25941"; } + ListElement{ radius: "15"; angle: "270"; value: "-5.44967"; } + ListElement{ radius: "15"; angle: "275"; value: "-5.25941"; } + ListElement{ radius: "15"; angle: "280"; value: "-4.69006"; } + ListElement{ radius: "15"; angle: "285"; value: "-3.74597"; } + ListElement{ radius: "15"; angle: "290"; value: "-2.4343"; } + ListElement{ radius: "15"; angle: "295"; value: "-0.765063"; } + ListElement{ radius: "15"; angle: "300"; value: "1.24906"; } + ListElement{ radius: "15"; angle: "305"; value: "3.59272"; } + ListElement{ radius: "15"; angle: "310"; value: "6.2481"; } + ListElement{ radius: "15"; angle: "315"; value: "9.19499"; } + ListElement{ radius: "15"; angle: "320"; value: "12.4109"; } + ListElement{ radius: "15"; angle: "325"; value: "15.8715"; } + ListElement{ radius: "15"; angle: "330"; value: "19.5503"; } + ListElement{ radius: "15"; angle: "335"; value: "23.4194"; } + ListElement{ radius: "15"; angle: "340"; value: "27.4493"; } + ListElement{ radius: "15"; angle: "345"; value: "31.6094"; } + ListElement{ radius: "15"; angle: "350"; value: "35.8679"; } + ListElement{ radius: "15"; angle: "355"; value: "40.1925"; } + ListElement{ radius: "15"; angle: "360"; value: "44.5503"; } + ListElement{ radius: "20"; angle: "0"; value: "40.4508"; } + ListElement{ radius: "20"; angle: "5"; value: "44.8086"; } + ListElement{ radius: "20"; angle: "10"; value: "49.1333"; } + ListElement{ radius: "20"; angle: "15"; value: "53.3918"; } + ListElement{ radius: "20"; angle: "20"; value: "57.5519"; } + ListElement{ radius: "20"; angle: "25"; value: "61.5818"; } + ListElement{ radius: "20"; angle: "30"; value: "65.4508"; } + ListElement{ radius: "20"; angle: "35"; value: "69.1297"; } + ListElement{ radius: "20"; angle: "40"; value: "72.5902"; } + ListElement{ radius: "20"; angle: "45"; value: "75.8062"; } + ListElement{ radius: "20"; angle: "50"; value: "78.7531"; } + ListElement{ radius: "20"; angle: "55"; value: "81.4085"; } + ListElement{ radius: "20"; angle: "60"; value: "83.7521"; } + ListElement{ radius: "20"; angle: "65"; value: "85.7662"; } + ListElement{ radius: "20"; angle: "70"; value: "87.4355"; } + ListElement{ radius: "20"; angle: "75"; value: "88.7471"; } + ListElement{ radius: "20"; angle: "80"; value: "89.6912"; } + ListElement{ radius: "20"; angle: "85"; value: "90.2606"; } + ListElement{ radius: "20"; angle: "90"; value: "90.4508"; } + ListElement{ radius: "20"; angle: "95"; value: "90.2606"; } + ListElement{ radius: "20"; angle: "100"; value: "89.6912"; } + ListElement{ radius: "20"; angle: "105"; value: "88.7471"; } + ListElement{ radius: "20"; angle: "110"; value: "87.4355"; } + ListElement{ radius: "20"; angle: "115"; value: "85.7662"; } + ListElement{ radius: "20"; angle: "120"; value: "83.7521"; } + ListElement{ radius: "20"; angle: "125"; value: "81.4085"; } + ListElement{ radius: "20"; angle: "130"; value: "78.7531"; } + ListElement{ radius: "20"; angle: "135"; value: "75.8062"; } + ListElement{ radius: "20"; angle: "140"; value: "72.5902"; } + ListElement{ radius: "20"; angle: "145"; value: "69.1297"; } + ListElement{ radius: "20"; angle: "150"; value: "65.4508"; } + ListElement{ radius: "20"; angle: "155"; value: "61.5818"; } + ListElement{ radius: "20"; angle: "160"; value: "57.5519"; } + ListElement{ radius: "20"; angle: "165"; value: "53.3918"; } + ListElement{ radius: "20"; angle: "170"; value: "49.1333"; } + ListElement{ radius: "20"; angle: "175"; value: "44.8086"; } + ListElement{ radius: "20"; angle: "180"; value: "40.4508"; } + ListElement{ radius: "20"; angle: "185"; value: "36.0931"; } + ListElement{ radius: "20"; angle: "190"; value: "31.7684"; } + ListElement{ radius: "20"; angle: "195"; value: "27.5099"; } + ListElement{ radius: "20"; angle: "200"; value: "23.3498"; } + ListElement{ radius: "20"; angle: "205"; value: "19.3199"; } + ListElement{ radius: "20"; angle: "210"; value: "15.4508"; } + ListElement{ radius: "20"; angle: "215"; value: "11.772"; } + ListElement{ radius: "20"; angle: "220"; value: "8.31147"; } + ListElement{ radius: "20"; angle: "225"; value: "5.09551"; } + ListElement{ radius: "20"; angle: "230"; value: "2.14863"; } + ListElement{ radius: "20"; angle: "235"; value: "-0.506752"; } + ListElement{ radius: "20"; angle: "240"; value: "-2.85042"; } + ListElement{ radius: "20"; angle: "245"; value: "-4.86454"; } + ListElement{ radius: "20"; angle: "250"; value: "-6.53378"; } + ListElement{ radius: "20"; angle: "255"; value: "-7.84544"; } + ListElement{ radius: "20"; angle: "260"; value: "-8.78954"; } + ListElement{ radius: "20"; angle: "265"; value: "-9.35889"; } + ListElement{ radius: "20"; angle: "270"; value: "-9.54915"; } + ListElement{ radius: "20"; angle: "275"; value: "-9.35889"; } + ListElement{ radius: "20"; angle: "280"; value: "-8.78954"; } + ListElement{ radius: "20"; angle: "285"; value: "-7.84544"; } + ListElement{ radius: "20"; angle: "290"; value: "-6.53378"; } + ListElement{ radius: "20"; angle: "295"; value: "-4.86454"; } + ListElement{ radius: "20"; angle: "300"; value: "-2.85042"; } + ListElement{ radius: "20"; angle: "305"; value: "-0.506752"; } + ListElement{ radius: "20"; angle: "310"; value: "2.14863"; } + ListElement{ radius: "20"; angle: "315"; value: "5.09551"; } + ListElement{ radius: "20"; angle: "320"; value: "8.31147"; } + ListElement{ radius: "20"; angle: "325"; value: "11.772"; } + ListElement{ radius: "20"; angle: "330"; value: "15.4508"; } + ListElement{ radius: "20"; angle: "335"; value: "19.3199"; } + ListElement{ radius: "20"; angle: "340"; value: "23.3498"; } + ListElement{ radius: "20"; angle: "345"; value: "27.5099"; } + ListElement{ radius: "20"; angle: "350"; value: "31.7684"; } + ListElement{ radius: "20"; angle: "355"; value: "36.0931"; } + ListElement{ radius: "20"; angle: "360"; value: "40.4508"; } + ListElement{ radius: "25"; angle: "0"; value: "35.3553"; } + ListElement{ radius: "25"; angle: "5"; value: "39.7131"; } + ListElement{ radius: "25"; angle: "10"; value: "44.0377"; } + ListElement{ radius: "25"; angle: "15"; value: "48.2963"; } + ListElement{ radius: "25"; angle: "20"; value: "52.4563"; } + ListElement{ radius: "25"; angle: "25"; value: "56.4863"; } + ListElement{ radius: "25"; angle: "30"; value: "60.3553"; } + ListElement{ radius: "25"; angle: "35"; value: "64.0342"; } + ListElement{ radius: "25"; angle: "40"; value: "67.4947"; } + ListElement{ radius: "25"; angle: "45"; value: "70.7107"; } + ListElement{ radius: "25"; angle: "50"; value: "73.6576"; } + ListElement{ radius: "25"; angle: "55"; value: "76.3129"; } + ListElement{ radius: "25"; angle: "60"; value: "78.6566"; } + ListElement{ radius: "25"; angle: "65"; value: "80.6707"; } + ListElement{ radius: "25"; angle: "70"; value: "82.34"; } + ListElement{ radius: "25"; angle: "75"; value: "83.6516"; } + ListElement{ radius: "25"; angle: "80"; value: "84.5957"; } + ListElement{ radius: "25"; angle: "85"; value: "85.1651"; } + ListElement{ radius: "25"; angle: "90"; value: "85.3553"; } + ListElement{ radius: "25"; angle: "95"; value: "85.1651"; } + ListElement{ radius: "25"; angle: "100"; value: "84.5957"; } + ListElement{ radius: "25"; angle: "105"; value: "83.6516"; } + ListElement{ radius: "25"; angle: "110"; value: "82.34"; } + ListElement{ radius: "25"; angle: "115"; value: "80.6707"; } + ListElement{ radius: "25"; angle: "120"; value: "78.6566"; } + ListElement{ radius: "25"; angle: "125"; value: "76.3129"; } + ListElement{ radius: "25"; angle: "130"; value: "73.6576"; } + ListElement{ radius: "25"; angle: "135"; value: "70.7107"; } + ListElement{ radius: "25"; angle: "140"; value: "67.4947"; } + ListElement{ radius: "25"; angle: "145"; value: "64.0342"; } + ListElement{ radius: "25"; angle: "150"; value: "60.3553"; } + ListElement{ radius: "25"; angle: "155"; value: "56.4863"; } + ListElement{ radius: "25"; angle: "160"; value: "52.4563"; } + ListElement{ radius: "25"; angle: "165"; value: "48.2963"; } + ListElement{ radius: "25"; angle: "170"; value: "44.0377"; } + ListElement{ radius: "25"; angle: "175"; value: "39.7131"; } + ListElement{ radius: "25"; angle: "180"; value: "35.3553"; } + ListElement{ radius: "25"; angle: "185"; value: "30.9976"; } + ListElement{ radius: "25"; angle: "190"; value: "26.6729"; } + ListElement{ radius: "25"; angle: "195"; value: "22.4144"; } + ListElement{ radius: "25"; angle: "200"; value: "18.2543"; } + ListElement{ radius: "25"; angle: "205"; value: "14.2244"; } + ListElement{ radius: "25"; angle: "210"; value: "10.3553"; } + ListElement{ radius: "25"; angle: "215"; value: "6.67652"; } + ListElement{ radius: "25"; angle: "220"; value: "3.21596"; } + ListElement{ radius: "25"; angle: "225"; value: "5.55112e-15"; } + ListElement{ radius: "25"; angle: "230"; value: "-2.94688"; } + ListElement{ radius: "25"; angle: "235"; value: "-5.60226"; } + ListElement{ radius: "25"; angle: "240"; value: "-7.94593"; } + ListElement{ radius: "25"; angle: "245"; value: "-9.96005"; } + ListElement{ radius: "25"; angle: "250"; value: "-11.6293"; } + ListElement{ radius: "25"; angle: "255"; value: "-12.941"; } + ListElement{ radius: "25"; angle: "260"; value: "-13.885"; } + ListElement{ radius: "25"; angle: "265"; value: "-14.4544"; } + ListElement{ radius: "25"; angle: "270"; value: "-14.6447"; } + ListElement{ radius: "25"; angle: "275"; value: "-14.4544"; } + ListElement{ radius: "25"; angle: "280"; value: "-13.885"; } + ListElement{ radius: "25"; angle: "285"; value: "-12.941"; } + ListElement{ radius: "25"; angle: "290"; value: "-11.6293"; } + ListElement{ radius: "25"; angle: "295"; value: "-9.96005"; } + ListElement{ radius: "25"; angle: "300"; value: "-7.94593"; } + ListElement{ radius: "25"; angle: "305"; value: "-5.60226"; } + ListElement{ radius: "25"; angle: "310"; value: "-2.94688"; } + ListElement{ radius: "25"; angle: "315"; value: "-5.55112e-15"; } + ListElement{ radius: "25"; angle: "320"; value: "3.21596"; } + ListElement{ radius: "25"; angle: "325"; value: "6.67652"; } + ListElement{ radius: "25"; angle: "330"; value: "10.3553"; } + ListElement{ radius: "25"; angle: "335"; value: "14.2244"; } + ListElement{ radius: "25"; angle: "340"; value: "18.2543"; } + ListElement{ radius: "25"; angle: "345"; value: "22.4144"; } + ListElement{ radius: "25"; angle: "350"; value: "26.6729"; } + ListElement{ radius: "25"; angle: "355"; value: "30.9976"; } + ListElement{ radius: "25"; angle: "360"; value: "35.3553"; } + ListElement{ radius: "30"; angle: "0"; value: "29.3893"; } + ListElement{ radius: "30"; angle: "5"; value: "33.747"; } + ListElement{ radius: "30"; angle: "10"; value: "38.0717"; } + ListElement{ radius: "30"; angle: "15"; value: "42.3302"; } + ListElement{ radius: "30"; angle: "20"; value: "46.4903"; } + ListElement{ radius: "30"; angle: "25"; value: "50.5202"; } + ListElement{ radius: "30"; angle: "30"; value: "54.3893"; } + ListElement{ radius: "30"; angle: "35"; value: "58.0681"; } + ListElement{ radius: "30"; angle: "40"; value: "61.5286"; } + ListElement{ radius: "30"; angle: "45"; value: "64.7446"; } + ListElement{ radius: "30"; angle: "50"; value: "67.6915"; } + ListElement{ radius: "30"; angle: "55"; value: "70.3469"; } + ListElement{ radius: "30"; angle: "60"; value: "72.6905"; } + ListElement{ radius: "30"; angle: "65"; value: "74.7047"; } + ListElement{ radius: "30"; angle: "70"; value: "76.3739"; } + ListElement{ radius: "30"; angle: "75"; value: "77.6856"; } + ListElement{ radius: "30"; angle: "80"; value: "78.6297"; } + ListElement{ radius: "30"; angle: "85"; value: "79.199"; } + ListElement{ radius: "30"; angle: "90"; value: "79.3893"; } + ListElement{ radius: "30"; angle: "95"; value: "79.199"; } + ListElement{ radius: "30"; angle: "100"; value: "78.6297"; } + ListElement{ radius: "30"; angle: "105"; value: "77.6856"; } + ListElement{ radius: "30"; angle: "110"; value: "76.3739"; } + ListElement{ radius: "30"; angle: "115"; value: "74.7047"; } + ListElement{ radius: "30"; angle: "120"; value: "72.6905"; } + ListElement{ radius: "30"; angle: "125"; value: "70.3469"; } + ListElement{ radius: "30"; angle: "130"; value: "67.6915"; } + ListElement{ radius: "30"; angle: "135"; value: "64.7446"; } + ListElement{ radius: "30"; angle: "140"; value: "61.5286"; } + ListElement{ radius: "30"; angle: "145"; value: "58.0681"; } + ListElement{ radius: "30"; angle: "150"; value: "54.3893"; } + ListElement{ radius: "30"; angle: "155"; value: "50.5202"; } + ListElement{ radius: "30"; angle: "160"; value: "46.4903"; } + ListElement{ radius: "30"; angle: "165"; value: "42.3302"; } + ListElement{ radius: "30"; angle: "170"; value: "38.0717"; } + ListElement{ radius: "30"; angle: "175"; value: "33.747"; } + ListElement{ radius: "30"; angle: "180"; value: "29.3893"; } + ListElement{ radius: "30"; angle: "185"; value: "25.0315"; } + ListElement{ radius: "30"; angle: "190"; value: "20.7069"; } + ListElement{ radius: "30"; angle: "195"; value: "16.4483"; } + ListElement{ radius: "30"; angle: "200"; value: "12.2883"; } + ListElement{ radius: "30"; angle: "205"; value: "8.25835"; } + ListElement{ radius: "30"; angle: "210"; value: "4.38926"; } + ListElement{ radius: "30"; angle: "215"; value: "0.710441"; } + ListElement{ radius: "30"; angle: "220"; value: "-2.75012"; } + ListElement{ radius: "30"; angle: "225"; value: "-5.96608"; } + ListElement{ radius: "30"; angle: "230"; value: "-8.91296"; } + ListElement{ radius: "30"; angle: "235"; value: "-11.5683"; } + ListElement{ radius: "30"; angle: "240"; value: "-13.912"; } + ListElement{ radius: "30"; angle: "245"; value: "-15.9261"; } + ListElement{ radius: "30"; angle: "250"; value: "-17.5954"; } + ListElement{ radius: "30"; angle: "255"; value: "-18.907"; } + ListElement{ radius: "30"; angle: "260"; value: "-19.8511"; } + ListElement{ radius: "30"; angle: "265"; value: "-20.4205"; } + ListElement{ radius: "30"; angle: "270"; value: "-20.6107"; } + ListElement{ radius: "30"; angle: "275"; value: "-20.4205"; } + ListElement{ radius: "30"; angle: "280"; value: "-19.8511"; } + ListElement{ radius: "30"; angle: "285"; value: "-18.907"; } + ListElement{ radius: "30"; angle: "290"; value: "-17.5954"; } + ListElement{ radius: "30"; angle: "295"; value: "-15.9261"; } + ListElement{ radius: "30"; angle: "300"; value: "-13.912"; } + ListElement{ radius: "30"; angle: "305"; value: "-11.5683"; } + ListElement{ radius: "30"; angle: "310"; value: "-8.91296"; } + ListElement{ radius: "30"; angle: "315"; value: "-5.96608"; } + ListElement{ radius: "30"; angle: "320"; value: "-2.75012"; } + ListElement{ radius: "30"; angle: "325"; value: "0.710441"; } + ListElement{ radius: "30"; angle: "330"; value: "4.38926"; } + ListElement{ radius: "30"; angle: "335"; value: "8.25835"; } + ListElement{ radius: "30"; angle: "340"; value: "12.2883"; } + ListElement{ radius: "30"; angle: "345"; value: "16.4483"; } + ListElement{ radius: "30"; angle: "350"; value: "20.7069"; } + ListElement{ radius: "30"; angle: "355"; value: "25.0315"; } + ListElement{ radius: "30"; angle: "360"; value: "29.3893"; } + ListElement{ radius: "35"; angle: "0"; value: "22.6995"; } + ListElement{ radius: "35"; angle: "5"; value: "27.0573"; } + ListElement{ radius: "35"; angle: "10"; value: "31.3819"; } + ListElement{ radius: "35"; angle: "15"; value: "35.6405"; } + ListElement{ radius: "35"; angle: "20"; value: "39.8005"; } + ListElement{ radius: "35"; angle: "25"; value: "43.8304"; } + ListElement{ radius: "35"; angle: "30"; value: "47.6995"; } + ListElement{ radius: "35"; angle: "35"; value: "51.3783"; } + ListElement{ radius: "35"; angle: "40"; value: "54.8389"; } + ListElement{ radius: "35"; angle: "45"; value: "58.0549"; } + ListElement{ radius: "35"; angle: "50"; value: "61.0017"; } + ListElement{ radius: "35"; angle: "55"; value: "63.6571"; } + ListElement{ radius: "35"; angle: "60"; value: "66.0008"; } + ListElement{ radius: "35"; angle: "65"; value: "68.0149"; } + ListElement{ radius: "35"; angle: "70"; value: "69.6842"; } + ListElement{ radius: "35"; angle: "75"; value: "70.9958"; } + ListElement{ radius: "35"; angle: "80"; value: "71.9399"; } + ListElement{ radius: "35"; angle: "85"; value: "72.5093"; } + ListElement{ radius: "35"; angle: "90"; value: "72.6995"; } + ListElement{ radius: "35"; angle: "95"; value: "72.5093"; } + ListElement{ radius: "35"; angle: "100"; value: "71.9399"; } + ListElement{ radius: "35"; angle: "105"; value: "70.9958"; } + ListElement{ radius: "35"; angle: "110"; value: "69.6842"; } + ListElement{ radius: "35"; angle: "115"; value: "68.0149"; } + ListElement{ radius: "35"; angle: "120"; value: "66.0008"; } + ListElement{ radius: "35"; angle: "125"; value: "63.6571"; } + ListElement{ radius: "35"; angle: "130"; value: "61.0017"; } + ListElement{ radius: "35"; angle: "135"; value: "58.0549"; } + ListElement{ radius: "35"; angle: "140"; value: "54.8389"; } + ListElement{ radius: "35"; angle: "145"; value: "51.3783"; } + ListElement{ radius: "35"; angle: "150"; value: "47.6995"; } + ListElement{ radius: "35"; angle: "155"; value: "43.8304"; } + ListElement{ radius: "35"; angle: "160"; value: "39.8005"; } + ListElement{ radius: "35"; angle: "165"; value: "35.6405"; } + ListElement{ radius: "35"; angle: "170"; value: "31.3819"; } + ListElement{ radius: "35"; angle: "175"; value: "27.0573"; } + ListElement{ radius: "35"; angle: "180"; value: "22.6995"; } + ListElement{ radius: "35"; angle: "185"; value: "18.3417"; } + ListElement{ radius: "35"; angle: "190"; value: "14.0171"; } + ListElement{ radius: "35"; angle: "195"; value: "9.75857"; } + ListElement{ radius: "35"; angle: "200"; value: "5.59852"; } + ListElement{ radius: "35"; angle: "205"; value: "1.56861"; } + ListElement{ radius: "35"; angle: "210"; value: "-2.30048"; } + ListElement{ radius: "35"; angle: "215"; value: "-5.9793"; } + ListElement{ radius: "35"; angle: "220"; value: "-9.43986"; } + ListElement{ radius: "35"; angle: "225"; value: "-12.6558"; } + ListElement{ radius: "35"; angle: "230"; value: "-15.6027"; } + ListElement{ radius: "35"; angle: "235"; value: "-18.2581"; } + ListElement{ radius: "35"; angle: "240"; value: "-20.6017"; } + ListElement{ radius: "35"; angle: "245"; value: "-22.6159"; } + ListElement{ radius: "35"; angle: "250"; value: "-24.2851"; } + ListElement{ radius: "35"; angle: "255"; value: "-25.5968"; } + ListElement{ radius: "35"; angle: "260"; value: "-26.5409"; } + ListElement{ radius: "35"; angle: "265"; value: "-27.1102"; } + ListElement{ radius: "35"; angle: "270"; value: "-27.3005"; } + ListElement{ radius: "35"; angle: "275"; value: "-27.1102"; } + ListElement{ radius: "35"; angle: "280"; value: "-26.5409"; } + ListElement{ radius: "35"; angle: "285"; value: "-25.5968"; } + ListElement{ radius: "35"; angle: "290"; value: "-24.2851"; } + ListElement{ radius: "35"; angle: "295"; value: "-22.6159"; } + ListElement{ radius: "35"; angle: "300"; value: "-20.6017"; } + ListElement{ radius: "35"; angle: "305"; value: "-18.2581"; } + ListElement{ radius: "35"; angle: "310"; value: "-15.6027"; } + ListElement{ radius: "35"; angle: "315"; value: "-12.6558"; } + ListElement{ radius: "35"; angle: "320"; value: "-9.43986"; } + ListElement{ radius: "35"; angle: "325"; value: "-5.9793"; } + ListElement{ radius: "35"; angle: "330"; value: "-2.30048"; } + ListElement{ radius: "35"; angle: "335"; value: "1.56861"; } + ListElement{ radius: "35"; angle: "340"; value: "5.59852"; } + ListElement{ radius: "35"; angle: "345"; value: "9.75857"; } + ListElement{ radius: "35"; angle: "350"; value: "14.0171"; } + ListElement{ radius: "35"; angle: "355"; value: "18.3417"; } + ListElement{ radius: "35"; angle: "360"; value: "22.6995"; } + ListElement{ radius: "40"; angle: "0"; value: "15.4508"; } + ListElement{ radius: "40"; angle: "5"; value: "19.8086"; } + ListElement{ radius: "40"; angle: "10"; value: "24.1333"; } + ListElement{ radius: "40"; angle: "15"; value: "28.3918"; } + ListElement{ radius: "40"; angle: "20"; value: "32.5519"; } + ListElement{ radius: "40"; angle: "25"; value: "36.5818"; } + ListElement{ radius: "40"; angle: "30"; value: "40.4508"; } + ListElement{ radius: "40"; angle: "35"; value: "44.1297"; } + ListElement{ radius: "40"; angle: "40"; value: "47.5902"; } + ListElement{ radius: "40"; angle: "45"; value: "50.8062"; } + ListElement{ radius: "40"; angle: "50"; value: "53.7531"; } + ListElement{ radius: "40"; angle: "55"; value: "56.4085"; } + ListElement{ radius: "40"; angle: "60"; value: "58.7521"; } + ListElement{ radius: "40"; angle: "65"; value: "60.7662"; } + ListElement{ radius: "40"; angle: "70"; value: "62.4355"; } + ListElement{ radius: "40"; angle: "75"; value: "63.7471"; } + ListElement{ radius: "40"; angle: "80"; value: "64.6912"; } + ListElement{ radius: "40"; angle: "85"; value: "65.2606"; } + ListElement{ radius: "40"; angle: "90"; value: "65.4508"; } + ListElement{ radius: "40"; angle: "95"; value: "65.2606"; } + ListElement{ radius: "40"; angle: "100"; value: "64.6912"; } + ListElement{ radius: "40"; angle: "105"; value: "63.7471"; } + ListElement{ radius: "40"; angle: "110"; value: "62.4355"; } + ListElement{ radius: "40"; angle: "115"; value: "60.7662"; } + ListElement{ radius: "40"; angle: "120"; value: "58.7521"; } + ListElement{ radius: "40"; angle: "125"; value: "56.4085"; } + ListElement{ radius: "40"; angle: "130"; value: "53.7531"; } + ListElement{ radius: "40"; angle: "135"; value: "50.8062"; } + ListElement{ radius: "40"; angle: "140"; value: "47.5902"; } + ListElement{ radius: "40"; angle: "145"; value: "44.1297"; } + ListElement{ radius: "40"; angle: "150"; value: "40.4508"; } + ListElement{ radius: "40"; angle: "155"; value: "36.5818"; } + ListElement{ radius: "40"; angle: "160"; value: "32.5519"; } + ListElement{ radius: "40"; angle: "165"; value: "28.3918"; } + ListElement{ radius: "40"; angle: "170"; value: "24.1333"; } + ListElement{ radius: "40"; angle: "175"; value: "19.8086"; } + ListElement{ radius: "40"; angle: "180"; value: "15.4508"; } + ListElement{ radius: "40"; angle: "185"; value: "11.0931"; } + ListElement{ radius: "40"; angle: "190"; value: "6.76844"; } + ListElement{ radius: "40"; angle: "195"; value: "2.5099"; } + ListElement{ radius: "40"; angle: "200"; value: "-1.65016"; } + ListElement{ radius: "40"; angle: "205"; value: "-5.68006"; } + ListElement{ radius: "40"; angle: "210"; value: "-9.54915"; } + ListElement{ radius: "40"; angle: "215"; value: "-13.228"; } + ListElement{ radius: "40"; angle: "220"; value: "-16.6885"; } + ListElement{ radius: "40"; angle: "225"; value: "-19.9045"; } + ListElement{ radius: "40"; angle: "230"; value: "-22.8514"; } + ListElement{ radius: "40"; angle: "235"; value: "-25.5068"; } + ListElement{ radius: "40"; angle: "240"; value: "-27.8504"; } + ListElement{ radius: "40"; angle: "245"; value: "-29.8645"; } + ListElement{ radius: "40"; angle: "250"; value: "-31.5338"; } + ListElement{ radius: "40"; angle: "255"; value: "-32.8454"; } + ListElement{ radius: "40"; angle: "260"; value: "-33.7895"; } + ListElement{ radius: "40"; angle: "265"; value: "-34.3589"; } + ListElement{ radius: "40"; angle: "270"; value: "-34.5492"; } + ListElement{ radius: "40"; angle: "275"; value: "-34.3589"; } + ListElement{ radius: "40"; angle: "280"; value: "-33.7895"; } + ListElement{ radius: "40"; angle: "285"; value: "-32.8454"; } + ListElement{ radius: "40"; angle: "290"; value: "-31.5338"; } + ListElement{ radius: "40"; angle: "295"; value: "-29.8645"; } + ListElement{ radius: "40"; angle: "300"; value: "-27.8504"; } + ListElement{ radius: "40"; angle: "305"; value: "-25.5068"; } + ListElement{ radius: "40"; angle: "310"; value: "-22.8514"; } + ListElement{ radius: "40"; angle: "315"; value: "-19.9045"; } + ListElement{ radius: "40"; angle: "320"; value: "-16.6885"; } + ListElement{ radius: "40"; angle: "325"; value: "-13.228"; } + ListElement{ radius: "40"; angle: "330"; value: "-9.54915"; } + ListElement{ radius: "40"; angle: "335"; value: "-5.68006"; } + ListElement{ radius: "40"; angle: "340"; value: "-1.65016"; } + ListElement{ radius: "40"; angle: "345"; value: "2.5099"; } + ListElement{ radius: "40"; angle: "350"; value: "6.76844"; } + ListElement{ radius: "40"; angle: "355"; value: "11.0931"; } + ListElement{ radius: "40"; angle: "360"; value: "15.4508"; } + ListElement{ radius: "45"; angle: "0"; value: "7.82172"; } + ListElement{ radius: "45"; angle: "5"; value: "12.1795"; } + ListElement{ radius: "45"; angle: "10"; value: "16.5041"; } + ListElement{ radius: "45"; angle: "15"; value: "20.7627"; } + ListElement{ radius: "45"; angle: "20"; value: "24.9227"; } + ListElement{ radius: "45"; angle: "25"; value: "28.9526"; } + ListElement{ radius: "45"; angle: "30"; value: "32.8217"; } + ListElement{ radius: "45"; angle: "35"; value: "36.5005"; } + ListElement{ radius: "45"; angle: "40"; value: "39.9611"; } + ListElement{ radius: "45"; angle: "45"; value: "43.1771"; } + ListElement{ radius: "45"; angle: "50"; value: "46.1239"; } + ListElement{ radius: "45"; angle: "55"; value: "48.7793"; } + ListElement{ radius: "45"; angle: "60"; value: "51.123"; } + ListElement{ radius: "45"; angle: "65"; value: "53.1371"; } + ListElement{ radius: "45"; angle: "70"; value: "54.8064"; } + ListElement{ radius: "45"; angle: "75"; value: "56.118"; } + ListElement{ radius: "45"; angle: "80"; value: "57.0621"; } + ListElement{ radius: "45"; angle: "85"; value: "57.6315"; } + ListElement{ radius: "45"; angle: "90"; value: "57.8217"; } + ListElement{ radius: "45"; angle: "95"; value: "57.6315"; } + ListElement{ radius: "45"; angle: "100"; value: "57.0621"; } + ListElement{ radius: "45"; angle: "105"; value: "56.118"; } + ListElement{ radius: "45"; angle: "110"; value: "54.8064"; } + ListElement{ radius: "45"; angle: "115"; value: "53.1371"; } + ListElement{ radius: "45"; angle: "120"; value: "51.123"; } + ListElement{ radius: "45"; angle: "125"; value: "48.7793"; } + ListElement{ radius: "45"; angle: "130"; value: "46.1239"; } + ListElement{ radius: "45"; angle: "135"; value: "43.1771"; } + ListElement{ radius: "45"; angle: "140"; value: "39.9611"; } + ListElement{ radius: "45"; angle: "145"; value: "36.5005"; } + ListElement{ radius: "45"; angle: "150"; value: "32.8217"; } + ListElement{ radius: "45"; angle: "155"; value: "28.9526"; } + ListElement{ radius: "45"; angle: "160"; value: "24.9227"; } + ListElement{ radius: "45"; angle: "165"; value: "20.7627"; } + ListElement{ radius: "45"; angle: "170"; value: "16.5041"; } + ListElement{ radius: "45"; angle: "175"; value: "12.1795"; } + ListElement{ radius: "45"; angle: "180"; value: "7.82172"; } + ListElement{ radius: "45"; angle: "185"; value: "3.46394"; } + ListElement{ radius: "45"; angle: "190"; value: "-0.860686"; } + ListElement{ radius: "45"; angle: "195"; value: "-5.11923"; } + ListElement{ radius: "45"; angle: "200"; value: "-9.27928"; } + ListElement{ radius: "45"; angle: "205"; value: "-13.3092"; } + ListElement{ radius: "45"; angle: "210"; value: "-17.1783"; } + ListElement{ radius: "45"; angle: "215"; value: "-20.8571"; } + ListElement{ radius: "45"; angle: "220"; value: "-24.3177"; } + ListElement{ radius: "45"; angle: "225"; value: "-27.5336"; } + ListElement{ radius: "45"; angle: "230"; value: "-30.4805"; } + ListElement{ radius: "45"; angle: "235"; value: "-33.1359"; } + ListElement{ radius: "45"; angle: "240"; value: "-35.4795"; } + ListElement{ radius: "45"; angle: "245"; value: "-37.4937"; } + ListElement{ radius: "45"; angle: "250"; value: "-39.1629"; } + ListElement{ radius: "45"; angle: "255"; value: "-40.4746"; } + ListElement{ radius: "45"; angle: "260"; value: "-41.4187"; } + ListElement{ radius: "45"; angle: "265"; value: "-41.988"; } + ListElement{ radius: "45"; angle: "270"; value: "-42.1783"; } + ListElement{ radius: "45"; angle: "275"; value: "-41.988"; } + ListElement{ radius: "45"; angle: "280"; value: "-41.4187"; } + ListElement{ radius: "45"; angle: "285"; value: "-40.4746"; } + ListElement{ radius: "45"; angle: "290"; value: "-39.1629"; } + ListElement{ radius: "45"; angle: "295"; value: "-37.4937"; } + ListElement{ radius: "45"; angle: "300"; value: "-35.4795"; } + ListElement{ radius: "45"; angle: "305"; value: "-33.1359"; } + ListElement{ radius: "45"; angle: "310"; value: "-30.4805"; } + ListElement{ radius: "45"; angle: "315"; value: "-27.5336"; } + ListElement{ radius: "45"; angle: "320"; value: "-24.3177"; } + ListElement{ radius: "45"; angle: "325"; value: "-20.8571"; } + ListElement{ radius: "45"; angle: "330"; value: "-17.1783"; } + ListElement{ radius: "45"; angle: "335"; value: "-13.3092"; } + ListElement{ radius: "45"; angle: "340"; value: "-9.27928"; } + ListElement{ radius: "45"; angle: "345"; value: "-5.11923"; } + ListElement{ radius: "45"; angle: "350"; value: "-0.860686"; } + ListElement{ radius: "45"; angle: "355"; value: "3.46394"; } + ListElement{ radius: "45"; angle: "360"; value: "7.82172"; } + ListElement{ radius: "50"; angle: "0"; value: "3.06162e-15"; } + ListElement{ radius: "50"; angle: "5"; value: "4.35779"; } + ListElement{ radius: "50"; angle: "10"; value: "8.68241"; } + ListElement{ radius: "50"; angle: "15"; value: "12.941"; } + ListElement{ radius: "50"; angle: "20"; value: "17.101"; } + ListElement{ radius: "50"; angle: "25"; value: "21.1309"; } + ListElement{ radius: "50"; angle: "30"; value: "25"; } + ListElement{ radius: "50"; angle: "35"; value: "28.6788"; } + ListElement{ radius: "50"; angle: "40"; value: "32.1394"; } + ListElement{ radius: "50"; angle: "45"; value: "35.3553"; } + ListElement{ radius: "50"; angle: "50"; value: "38.3022"; } + ListElement{ radius: "50"; angle: "55"; value: "40.9576"; } + ListElement{ radius: "50"; angle: "60"; value: "43.3013"; } + ListElement{ radius: "50"; angle: "65"; value: "45.3154"; } + ListElement{ radius: "50"; angle: "70"; value: "46.9846"; } + ListElement{ radius: "50"; angle: "75"; value: "48.2963"; } + ListElement{ radius: "50"; angle: "80"; value: "49.2404"; } + ListElement{ radius: "50"; angle: "85"; value: "49.8097"; } + ListElement{ radius: "50"; angle: "90"; value: "50"; } + ListElement{ radius: "50"; angle: "95"; value: "49.8097"; } + ListElement{ radius: "50"; angle: "100"; value: "49.2404"; } + ListElement{ radius: "50"; angle: "105"; value: "48.2963"; } + ListElement{ radius: "50"; angle: "110"; value: "46.9846"; } + ListElement{ radius: "50"; angle: "115"; value: "45.3154"; } + ListElement{ radius: "50"; angle: "120"; value: "43.3013"; } + ListElement{ radius: "50"; angle: "125"; value: "40.9576"; } + ListElement{ radius: "50"; angle: "130"; value: "38.3022"; } + ListElement{ radius: "50"; angle: "135"; value: "35.3553"; } + ListElement{ radius: "50"; angle: "140"; value: "32.1394"; } + ListElement{ radius: "50"; angle: "145"; value: "28.6788"; } + ListElement{ radius: "50"; angle: "150"; value: "25"; } + ListElement{ radius: "50"; angle: "155"; value: "21.1309"; } + ListElement{ radius: "50"; angle: "160"; value: "17.101"; } + ListElement{ radius: "50"; angle: "165"; value: "12.941"; } + ListElement{ radius: "50"; angle: "170"; value: "8.68241"; } + ListElement{ radius: "50"; angle: "175"; value: "4.35779"; } + ListElement{ radius: "50"; angle: "180"; value: "9.18485e-15"; } + ListElement{ radius: "50"; angle: "185"; value: "-4.35779"; } + ListElement{ radius: "50"; angle: "190"; value: "-8.68241"; } + ListElement{ radius: "50"; angle: "195"; value: "-12.941"; } + ListElement{ radius: "50"; angle: "200"; value: "-17.101"; } + ListElement{ radius: "50"; angle: "205"; value: "-21.1309"; } + ListElement{ radius: "50"; angle: "210"; value: "-25"; } + ListElement{ radius: "50"; angle: "215"; value: "-28.6788"; } + ListElement{ radius: "50"; angle: "220"; value: "-32.1394"; } + ListElement{ radius: "50"; angle: "225"; value: "-35.3553"; } + ListElement{ radius: "50"; angle: "230"; value: "-38.3022"; } + ListElement{ radius: "50"; angle: "235"; value: "-40.9576"; } + ListElement{ radius: "50"; angle: "240"; value: "-43.3013"; } + ListElement{ radius: "50"; angle: "245"; value: "-45.3154"; } + ListElement{ radius: "50"; angle: "250"; value: "-46.9846"; } + ListElement{ radius: "50"; angle: "255"; value: "-48.2963"; } + ListElement{ radius: "50"; angle: "260"; value: "-49.2404"; } + ListElement{ radius: "50"; angle: "265"; value: "-49.8097"; } + ListElement{ radius: "50"; angle: "270"; value: "-50"; } + ListElement{ radius: "50"; angle: "275"; value: "-49.8097"; } + ListElement{ radius: "50"; angle: "280"; value: "-49.2404"; } + ListElement{ radius: "50"; angle: "285"; value: "-48.2963"; } + ListElement{ radius: "50"; angle: "290"; value: "-46.9846"; } + ListElement{ radius: "50"; angle: "295"; value: "-45.3154"; } + ListElement{ radius: "50"; angle: "300"; value: "-43.3013"; } + ListElement{ radius: "50"; angle: "305"; value: "-40.9576"; } + ListElement{ radius: "50"; angle: "310"; value: "-38.3022"; } + ListElement{ radius: "50"; angle: "315"; value: "-35.3553"; } + ListElement{ radius: "50"; angle: "320"; value: "-32.1394"; } + ListElement{ radius: "50"; angle: "325"; value: "-28.6788"; } + ListElement{ radius: "50"; angle: "330"; value: "-25"; } + ListElement{ radius: "50"; angle: "335"; value: "-21.1309"; } + ListElement{ radius: "50"; angle: "340"; value: "-17.101"; } + ListElement{ radius: "50"; angle: "345"; value: "-12.941"; } + ListElement{ radius: "50"; angle: "350"; value: "-8.68241"; } + ListElement{ radius: "50"; angle: "355"; value: "-4.35779"; } + ListElement{ radius: "50"; angle: "360"; value: "-9.18485e-15"; } + ListElement{ radius: "55"; angle: "0"; value: "-7.82172"; } + ListElement{ radius: "55"; angle: "5"; value: "-3.46394"; } + ListElement{ radius: "55"; angle: "10"; value: "0.860686"; } + ListElement{ radius: "55"; angle: "15"; value: "5.11923"; } + ListElement{ radius: "55"; angle: "20"; value: "9.27928"; } + ListElement{ radius: "55"; angle: "25"; value: "13.3092"; } + ListElement{ radius: "55"; angle: "30"; value: "17.1783"; } + ListElement{ radius: "55"; angle: "35"; value: "20.8571"; } + ListElement{ radius: "55"; angle: "40"; value: "24.3177"; } + ListElement{ radius: "55"; angle: "45"; value: "27.5336"; } + ListElement{ radius: "55"; angle: "50"; value: "30.4805"; } + ListElement{ radius: "55"; angle: "55"; value: "33.1359"; } + ListElement{ radius: "55"; angle: "60"; value: "35.4795"; } + ListElement{ radius: "55"; angle: "65"; value: "37.4937"; } + ListElement{ radius: "55"; angle: "70"; value: "39.1629"; } + ListElement{ radius: "55"; angle: "75"; value: "40.4746"; } + ListElement{ radius: "55"; angle: "80"; value: "41.4187"; } + ListElement{ radius: "55"; angle: "85"; value: "41.988"; } + ListElement{ radius: "55"; angle: "90"; value: "42.1783"; } + ListElement{ radius: "55"; angle: "95"; value: "41.988"; } + ListElement{ radius: "55"; angle: "100"; value: "41.4187"; } + ListElement{ radius: "55"; angle: "105"; value: "40.4746"; } + ListElement{ radius: "55"; angle: "110"; value: "39.1629"; } + ListElement{ radius: "55"; angle: "115"; value: "37.4937"; } + ListElement{ radius: "55"; angle: "120"; value: "35.4795"; } + ListElement{ radius: "55"; angle: "125"; value: "33.1359"; } + ListElement{ radius: "55"; angle: "130"; value: "30.4805"; } + ListElement{ radius: "55"; angle: "135"; value: "27.5336"; } + ListElement{ radius: "55"; angle: "140"; value: "24.3177"; } + ListElement{ radius: "55"; angle: "145"; value: "20.8571"; } + ListElement{ radius: "55"; angle: "150"; value: "17.1783"; } + ListElement{ radius: "55"; angle: "155"; value: "13.3092"; } + ListElement{ radius: "55"; angle: "160"; value: "9.27928"; } + ListElement{ radius: "55"; angle: "165"; value: "5.11923"; } + ListElement{ radius: "55"; angle: "170"; value: "0.860686"; } + ListElement{ radius: "55"; angle: "175"; value: "-3.46394"; } + ListElement{ radius: "55"; angle: "180"; value: "-7.82172"; } + ListElement{ radius: "55"; angle: "185"; value: "-12.1795"; } + ListElement{ radius: "55"; angle: "190"; value: "-16.5041"; } + ListElement{ radius: "55"; angle: "195"; value: "-20.7627"; } + ListElement{ radius: "55"; angle: "200"; value: "-24.9227"; } + ListElement{ radius: "55"; angle: "205"; value: "-28.9526"; } + ListElement{ radius: "55"; angle: "210"; value: "-32.8217"; } + ListElement{ radius: "55"; angle: "215"; value: "-36.5005"; } + ListElement{ radius: "55"; angle: "220"; value: "-39.9611"; } + ListElement{ radius: "55"; angle: "225"; value: "-43.1771"; } + ListElement{ radius: "55"; angle: "230"; value: "-46.1239"; } + ListElement{ radius: "55"; angle: "235"; value: "-48.7793"; } + ListElement{ radius: "55"; angle: "240"; value: "-51.123"; } + ListElement{ radius: "55"; angle: "245"; value: "-53.1371"; } + ListElement{ radius: "55"; angle: "250"; value: "-54.8064"; } + ListElement{ radius: "55"; angle: "255"; value: "-56.118"; } + ListElement{ radius: "55"; angle: "260"; value: "-57.0621"; } + ListElement{ radius: "55"; angle: "265"; value: "-57.6315"; } + ListElement{ radius: "55"; angle: "270"; value: "-57.8217"; } + ListElement{ radius: "55"; angle: "275"; value: "-57.6315"; } + ListElement{ radius: "55"; angle: "280"; value: "-57.0621"; } + ListElement{ radius: "55"; angle: "285"; value: "-56.118"; } + ListElement{ radius: "55"; angle: "290"; value: "-54.8064"; } + ListElement{ radius: "55"; angle: "295"; value: "-53.1371"; } + ListElement{ radius: "55"; angle: "300"; value: "-51.123"; } + ListElement{ radius: "55"; angle: "305"; value: "-48.7793"; } + ListElement{ radius: "55"; angle: "310"; value: "-46.1239"; } + ListElement{ radius: "55"; angle: "315"; value: "-43.1771"; } + ListElement{ radius: "55"; angle: "320"; value: "-39.9611"; } + ListElement{ radius: "55"; angle: "325"; value: "-36.5005"; } + ListElement{ radius: "55"; angle: "330"; value: "-32.8217"; } + ListElement{ radius: "55"; angle: "335"; value: "-28.9526"; } + ListElement{ radius: "55"; angle: "340"; value: "-24.9227"; } + ListElement{ radius: "55"; angle: "345"; value: "-20.7627"; } + ListElement{ radius: "55"; angle: "350"; value: "-16.5041"; } + ListElement{ radius: "55"; angle: "355"; value: "-12.1795"; } + ListElement{ radius: "55"; angle: "360"; value: "-7.82172"; } + ListElement{ radius: "60"; angle: "0"; value: "-15.4508"; } + ListElement{ radius: "60"; angle: "5"; value: "-11.0931"; } + ListElement{ radius: "60"; angle: "10"; value: "-6.76844"; } + ListElement{ radius: "60"; angle: "15"; value: "-2.5099"; } + ListElement{ radius: "60"; angle: "20"; value: "1.65016"; } + ListElement{ radius: "60"; angle: "25"; value: "5.68006"; } + ListElement{ radius: "60"; angle: "30"; value: "9.54915"; } + ListElement{ radius: "60"; angle: "35"; value: "13.228"; } + ListElement{ radius: "60"; angle: "40"; value: "16.6885"; } + ListElement{ radius: "60"; angle: "45"; value: "19.9045"; } + ListElement{ radius: "60"; angle: "50"; value: "22.8514"; } + ListElement{ radius: "60"; angle: "55"; value: "25.5068"; } + ListElement{ radius: "60"; angle: "60"; value: "27.8504"; } + ListElement{ radius: "60"; angle: "65"; value: "29.8645"; } + ListElement{ radius: "60"; angle: "70"; value: "31.5338"; } + ListElement{ radius: "60"; angle: "75"; value: "32.8454"; } + ListElement{ radius: "60"; angle: "80"; value: "33.7895"; } + ListElement{ radius: "60"; angle: "85"; value: "34.3589"; } + ListElement{ radius: "60"; angle: "90"; value: "34.5492"; } + ListElement{ radius: "60"; angle: "95"; value: "34.3589"; } + ListElement{ radius: "60"; angle: "100"; value: "33.7895"; } + ListElement{ radius: "60"; angle: "105"; value: "32.8454"; } + ListElement{ radius: "60"; angle: "110"; value: "31.5338"; } + ListElement{ radius: "60"; angle: "115"; value: "29.8645"; } + ListElement{ radius: "60"; angle: "120"; value: "27.8504"; } + ListElement{ radius: "60"; angle: "125"; value: "25.5068"; } + ListElement{ radius: "60"; angle: "130"; value: "22.8514"; } + ListElement{ radius: "60"; angle: "135"; value: "19.9045"; } + ListElement{ radius: "60"; angle: "140"; value: "16.6885"; } + ListElement{ radius: "60"; angle: "145"; value: "13.228"; } + ListElement{ radius: "60"; angle: "150"; value: "9.54915"; } + ListElement{ radius: "60"; angle: "155"; value: "5.68006"; } + ListElement{ radius: "60"; angle: "160"; value: "1.65016"; } + ListElement{ radius: "60"; angle: "165"; value: "-2.5099"; } + ListElement{ radius: "60"; angle: "170"; value: "-6.76844"; } + ListElement{ radius: "60"; angle: "175"; value: "-11.0931"; } + ListElement{ radius: "60"; angle: "180"; value: "-15.4508"; } + ListElement{ radius: "60"; angle: "185"; value: "-19.8086"; } + ListElement{ radius: "60"; angle: "190"; value: "-24.1333"; } + ListElement{ radius: "60"; angle: "195"; value: "-28.3918"; } + ListElement{ radius: "60"; angle: "200"; value: "-32.5519"; } + ListElement{ radius: "60"; angle: "205"; value: "-36.5818"; } + ListElement{ radius: "60"; angle: "210"; value: "-40.4508"; } + ListElement{ radius: "60"; angle: "215"; value: "-44.1297"; } + ListElement{ radius: "60"; angle: "220"; value: "-47.5902"; } + ListElement{ radius: "60"; angle: "225"; value: "-50.8062"; } + ListElement{ radius: "60"; angle: "230"; value: "-53.7531"; } + ListElement{ radius: "60"; angle: "235"; value: "-56.4085"; } + ListElement{ radius: "60"; angle: "240"; value: "-58.7521"; } + ListElement{ radius: "60"; angle: "245"; value: "-60.7662"; } + ListElement{ radius: "60"; angle: "250"; value: "-62.4355"; } + ListElement{ radius: "60"; angle: "255"; value: "-63.7471"; } + ListElement{ radius: "60"; angle: "260"; value: "-64.6912"; } + ListElement{ radius: "60"; angle: "265"; value: "-65.2606"; } + ListElement{ radius: "60"; angle: "270"; value: "-65.4508"; } + ListElement{ radius: "60"; angle: "275"; value: "-65.2606"; } + ListElement{ radius: "60"; angle: "280"; value: "-64.6912"; } + ListElement{ radius: "60"; angle: "285"; value: "-63.7471"; } + ListElement{ radius: "60"; angle: "290"; value: "-62.4355"; } + ListElement{ radius: "60"; angle: "295"; value: "-60.7662"; } + ListElement{ radius: "60"; angle: "300"; value: "-58.7521"; } + ListElement{ radius: "60"; angle: "305"; value: "-56.4085"; } + ListElement{ radius: "60"; angle: "310"; value: "-53.7531"; } + ListElement{ radius: "60"; angle: "315"; value: "-50.8062"; } + ListElement{ radius: "60"; angle: "320"; value: "-47.5902"; } + ListElement{ radius: "60"; angle: "325"; value: "-44.1297"; } + ListElement{ radius: "60"; angle: "330"; value: "-40.4508"; } + ListElement{ radius: "60"; angle: "335"; value: "-36.5818"; } + ListElement{ radius: "60"; angle: "340"; value: "-32.5519"; } + ListElement{ radius: "60"; angle: "345"; value: "-28.3918"; } + ListElement{ radius: "60"; angle: "350"; value: "-24.1333"; } + ListElement{ radius: "60"; angle: "355"; value: "-19.8086"; } + ListElement{ radius: "60"; angle: "360"; value: "-15.4508"; } + ListElement{ radius: "65"; angle: "0"; value: "-22.6995"; } + ListElement{ radius: "65"; angle: "5"; value: "-18.3417"; } + ListElement{ radius: "65"; angle: "10"; value: "-14.0171"; } + ListElement{ radius: "65"; angle: "15"; value: "-9.75857"; } + ListElement{ radius: "65"; angle: "20"; value: "-5.59852"; } + ListElement{ radius: "65"; angle: "25"; value: "-1.56861"; } + ListElement{ radius: "65"; angle: "30"; value: "2.30048"; } + ListElement{ radius: "65"; angle: "35"; value: "5.9793"; } + ListElement{ radius: "65"; angle: "40"; value: "9.43986"; } + ListElement{ radius: "65"; angle: "45"; value: "12.6558"; } + ListElement{ radius: "65"; angle: "50"; value: "15.6027"; } + ListElement{ radius: "65"; angle: "55"; value: "18.2581"; } + ListElement{ radius: "65"; angle: "60"; value: "20.6017"; } + ListElement{ radius: "65"; angle: "65"; value: "22.6159"; } + ListElement{ radius: "65"; angle: "70"; value: "24.2851"; } + ListElement{ radius: "65"; angle: "75"; value: "25.5968"; } + ListElement{ radius: "65"; angle: "80"; value: "26.5409"; } + ListElement{ radius: "65"; angle: "85"; value: "27.1102"; } + ListElement{ radius: "65"; angle: "90"; value: "27.3005"; } + ListElement{ radius: "65"; angle: "95"; value: "27.1102"; } + ListElement{ radius: "65"; angle: "100"; value: "26.5409"; } + ListElement{ radius: "65"; angle: "105"; value: "25.5968"; } + ListElement{ radius: "65"; angle: "110"; value: "24.2851"; } + ListElement{ radius: "65"; angle: "115"; value: "22.6159"; } + ListElement{ radius: "65"; angle: "120"; value: "20.6017"; } + ListElement{ radius: "65"; angle: "125"; value: "18.2581"; } + ListElement{ radius: "65"; angle: "130"; value: "15.6027"; } + ListElement{ radius: "65"; angle: "135"; value: "12.6558"; } + ListElement{ radius: "65"; angle: "140"; value: "9.43986"; } + ListElement{ radius: "65"; angle: "145"; value: "5.9793"; } + ListElement{ radius: "65"; angle: "150"; value: "2.30048"; } + ListElement{ radius: "65"; angle: "155"; value: "-1.56861"; } + ListElement{ radius: "65"; angle: "160"; value: "-5.59852"; } + ListElement{ radius: "65"; angle: "165"; value: "-9.75857"; } + ListElement{ radius: "65"; angle: "170"; value: "-14.0171"; } + ListElement{ radius: "65"; angle: "175"; value: "-18.3417"; } + ListElement{ radius: "65"; angle: "180"; value: "-22.6995"; } + ListElement{ radius: "65"; angle: "185"; value: "-27.0573"; } + ListElement{ radius: "65"; angle: "190"; value: "-31.3819"; } + ListElement{ radius: "65"; angle: "195"; value: "-35.6405"; } + ListElement{ radius: "65"; angle: "200"; value: "-39.8005"; } + ListElement{ radius: "65"; angle: "205"; value: "-43.8304"; } + ListElement{ radius: "65"; angle: "210"; value: "-47.6995"; } + ListElement{ radius: "65"; angle: "215"; value: "-51.3783"; } + ListElement{ radius: "65"; angle: "220"; value: "-54.8389"; } + ListElement{ radius: "65"; angle: "225"; value: "-58.0549"; } + ListElement{ radius: "65"; angle: "230"; value: "-61.0017"; } + ListElement{ radius: "65"; angle: "235"; value: "-63.6571"; } + ListElement{ radius: "65"; angle: "240"; value: "-66.0008"; } + ListElement{ radius: "65"; angle: "245"; value: "-68.0149"; } + ListElement{ radius: "65"; angle: "250"; value: "-69.6842"; } + ListElement{ radius: "65"; angle: "255"; value: "-70.9958"; } + ListElement{ radius: "65"; angle: "260"; value: "-71.9399"; } + ListElement{ radius: "65"; angle: "265"; value: "-72.5093"; } + ListElement{ radius: "65"; angle: "270"; value: "-72.6995"; } + ListElement{ radius: "65"; angle: "275"; value: "-72.5093"; } + ListElement{ radius: "65"; angle: "280"; value: "-71.9399"; } + ListElement{ radius: "65"; angle: "285"; value: "-70.9958"; } + ListElement{ radius: "65"; angle: "290"; value: "-69.6842"; } + ListElement{ radius: "65"; angle: "295"; value: "-68.0149"; } + ListElement{ radius: "65"; angle: "300"; value: "-66.0008"; } + ListElement{ radius: "65"; angle: "305"; value: "-63.6571"; } + ListElement{ radius: "65"; angle: "310"; value: "-61.0017"; } + ListElement{ radius: "65"; angle: "315"; value: "-58.0549"; } + ListElement{ radius: "65"; angle: "320"; value: "-54.8389"; } + ListElement{ radius: "65"; angle: "325"; value: "-51.3783"; } + ListElement{ radius: "65"; angle: "330"; value: "-47.6995"; } + ListElement{ radius: "65"; angle: "335"; value: "-43.8304"; } + ListElement{ radius: "65"; angle: "340"; value: "-39.8005"; } + ListElement{ radius: "65"; angle: "345"; value: "-35.6405"; } + ListElement{ radius: "65"; angle: "350"; value: "-31.3819"; } + ListElement{ radius: "65"; angle: "355"; value: "-27.0573"; } + ListElement{ radius: "65"; angle: "360"; value: "-22.6995"; } + ListElement{ radius: "70"; angle: "0"; value: "-29.3893"; } + ListElement{ radius: "70"; angle: "5"; value: "-25.0315"; } + ListElement{ radius: "70"; angle: "10"; value: "-20.7069"; } + ListElement{ radius: "70"; angle: "15"; value: "-16.4483"; } + ListElement{ radius: "70"; angle: "20"; value: "-12.2883"; } + ListElement{ radius: "70"; angle: "25"; value: "-8.25835"; } + ListElement{ radius: "70"; angle: "30"; value: "-4.38926"; } + ListElement{ radius: "70"; angle: "35"; value: "-0.710441"; } + ListElement{ radius: "70"; angle: "40"; value: "2.75012"; } + ListElement{ radius: "70"; angle: "45"; value: "5.96608"; } + ListElement{ radius: "70"; angle: "50"; value: "8.91296"; } + ListElement{ radius: "70"; angle: "55"; value: "11.5683"; } + ListElement{ radius: "70"; angle: "60"; value: "13.912"; } + ListElement{ radius: "70"; angle: "65"; value: "15.9261"; } + ListElement{ radius: "70"; angle: "70"; value: "17.5954"; } + ListElement{ radius: "70"; angle: "75"; value: "18.907"; } + ListElement{ radius: "70"; angle: "80"; value: "19.8511"; } + ListElement{ radius: "70"; angle: "85"; value: "20.4205"; } + ListElement{ radius: "70"; angle: "90"; value: "20.6107"; } + ListElement{ radius: "70"; angle: "95"; value: "20.4205"; } + ListElement{ radius: "70"; angle: "100"; value: "19.8511"; } + ListElement{ radius: "70"; angle: "105"; value: "18.907"; } + ListElement{ radius: "70"; angle: "110"; value: "17.5954"; } + ListElement{ radius: "70"; angle: "115"; value: "15.9261"; } + ListElement{ radius: "70"; angle: "120"; value: "13.912"; } + ListElement{ radius: "70"; angle: "125"; value: "11.5683"; } + ListElement{ radius: "70"; angle: "130"; value: "8.91296"; } + ListElement{ radius: "70"; angle: "135"; value: "5.96608"; } + ListElement{ radius: "70"; angle: "140"; value: "2.75012"; } + ListElement{ radius: "70"; angle: "145"; value: "-0.710441"; } + ListElement{ radius: "70"; angle: "150"; value: "-4.38926"; } + ListElement{ radius: "70"; angle: "155"; value: "-8.25835"; } + ListElement{ radius: "70"; angle: "160"; value: "-12.2883"; } + ListElement{ radius: "70"; angle: "165"; value: "-16.4483"; } + ListElement{ radius: "70"; angle: "170"; value: "-20.7069"; } + ListElement{ radius: "70"; angle: "175"; value: "-25.0315"; } + ListElement{ radius: "70"; angle: "180"; value: "-29.3893"; } + ListElement{ radius: "70"; angle: "185"; value: "-33.747"; } + ListElement{ radius: "70"; angle: "190"; value: "-38.0717"; } + ListElement{ radius: "70"; angle: "195"; value: "-42.3302"; } + ListElement{ radius: "70"; angle: "200"; value: "-46.4903"; } + ListElement{ radius: "70"; angle: "205"; value: "-50.5202"; } + ListElement{ radius: "70"; angle: "210"; value: "-54.3893"; } + ListElement{ radius: "70"; angle: "215"; value: "-58.0681"; } + ListElement{ radius: "70"; angle: "220"; value: "-61.5286"; } + ListElement{ radius: "70"; angle: "225"; value: "-64.7446"; } + ListElement{ radius: "70"; angle: "230"; value: "-67.6915"; } + ListElement{ radius: "70"; angle: "235"; value: "-70.3469"; } + ListElement{ radius: "70"; angle: "240"; value: "-72.6905"; } + ListElement{ radius: "70"; angle: "245"; value: "-74.7047"; } + ListElement{ radius: "70"; angle: "250"; value: "-76.3739"; } + ListElement{ radius: "70"; angle: "255"; value: "-77.6856"; } + ListElement{ radius: "70"; angle: "260"; value: "-78.6297"; } + ListElement{ radius: "70"; angle: "265"; value: "-79.199"; } + ListElement{ radius: "70"; angle: "270"; value: "-79.3893"; } + ListElement{ radius: "70"; angle: "275"; value: "-79.199"; } + ListElement{ radius: "70"; angle: "280"; value: "-78.6297"; } + ListElement{ radius: "70"; angle: "285"; value: "-77.6856"; } + ListElement{ radius: "70"; angle: "290"; value: "-76.3739"; } + ListElement{ radius: "70"; angle: "295"; value: "-74.7047"; } + ListElement{ radius: "70"; angle: "300"; value: "-72.6905"; } + ListElement{ radius: "70"; angle: "305"; value: "-70.3469"; } + ListElement{ radius: "70"; angle: "310"; value: "-67.6915"; } + ListElement{ radius: "70"; angle: "315"; value: "-64.7446"; } + ListElement{ radius: "70"; angle: "320"; value: "-61.5286"; } + ListElement{ radius: "70"; angle: "325"; value: "-58.0681"; } + ListElement{ radius: "70"; angle: "330"; value: "-54.3893"; } + ListElement{ radius: "70"; angle: "335"; value: "-50.5202"; } + ListElement{ radius: "70"; angle: "340"; value: "-46.4903"; } + ListElement{ radius: "70"; angle: "345"; value: "-42.3302"; } + ListElement{ radius: "70"; angle: "350"; value: "-38.0717"; } + ListElement{ radius: "70"; angle: "355"; value: "-33.747"; } + ListElement{ radius: "70"; angle: "360"; value: "-29.3893"; } + ListElement{ radius: "75"; angle: "0"; value: "-35.3553"; } + ListElement{ radius: "75"; angle: "5"; value: "-30.9976"; } + ListElement{ radius: "75"; angle: "10"; value: "-26.6729"; } + ListElement{ radius: "75"; angle: "15"; value: "-22.4144"; } + ListElement{ radius: "75"; angle: "20"; value: "-18.2543"; } + ListElement{ radius: "75"; angle: "25"; value: "-14.2244"; } + ListElement{ radius: "75"; angle: "30"; value: "-10.3553"; } + ListElement{ radius: "75"; angle: "35"; value: "-6.67652"; } + ListElement{ radius: "75"; angle: "40"; value: "-3.21596"; } + ListElement{ radius: "75"; angle: "45"; value: "5.55112e-15"; } + ListElement{ radius: "75"; angle: "50"; value: "2.94688"; } + ListElement{ radius: "75"; angle: "55"; value: "5.60226"; } + ListElement{ radius: "75"; angle: "60"; value: "7.94593"; } + ListElement{ radius: "75"; angle: "65"; value: "9.96005"; } + ListElement{ radius: "75"; angle: "70"; value: "11.6293"; } + ListElement{ radius: "75"; angle: "75"; value: "12.941"; } + ListElement{ radius: "75"; angle: "80"; value: "13.885"; } + ListElement{ radius: "75"; angle: "85"; value: "14.4544"; } + ListElement{ radius: "75"; angle: "90"; value: "14.6447"; } + ListElement{ radius: "75"; angle: "95"; value: "14.4544"; } + ListElement{ radius: "75"; angle: "100"; value: "13.885"; } + ListElement{ radius: "75"; angle: "105"; value: "12.941"; } + ListElement{ radius: "75"; angle: "110"; value: "11.6293"; } + ListElement{ radius: "75"; angle: "115"; value: "9.96005"; } + ListElement{ radius: "75"; angle: "120"; value: "7.94593"; } + ListElement{ radius: "75"; angle: "125"; value: "5.60226"; } + ListElement{ radius: "75"; angle: "130"; value: "2.94688"; } + ListElement{ radius: "75"; angle: "135"; value: "5.55112e-15"; } + ListElement{ radius: "75"; angle: "140"; value: "-3.21596"; } + ListElement{ radius: "75"; angle: "145"; value: "-6.67652"; } + ListElement{ radius: "75"; angle: "150"; value: "-10.3553"; } + ListElement{ radius: "75"; angle: "155"; value: "-14.2244"; } + ListElement{ radius: "75"; angle: "160"; value: "-18.2543"; } + ListElement{ radius: "75"; angle: "165"; value: "-22.4144"; } + ListElement{ radius: "75"; angle: "170"; value: "-26.6729"; } + ListElement{ radius: "75"; angle: "175"; value: "-30.9976"; } + ListElement{ radius: "75"; angle: "180"; value: "-35.3553"; } + ListElement{ radius: "75"; angle: "185"; value: "-39.7131"; } + ListElement{ radius: "75"; angle: "190"; value: "-44.0377"; } + ListElement{ radius: "75"; angle: "195"; value: "-48.2963"; } + ListElement{ radius: "75"; angle: "200"; value: "-52.4563"; } + ListElement{ radius: "75"; angle: "205"; value: "-56.4863"; } + ListElement{ radius: "75"; angle: "210"; value: "-60.3553"; } + ListElement{ radius: "75"; angle: "215"; value: "-64.0342"; } + ListElement{ radius: "75"; angle: "220"; value: "-67.4947"; } + ListElement{ radius: "75"; angle: "225"; value: "-70.7107"; } + ListElement{ radius: "75"; angle: "230"; value: "-73.6576"; } + ListElement{ radius: "75"; angle: "235"; value: "-76.3129"; } + ListElement{ radius: "75"; angle: "240"; value: "-78.6566"; } + ListElement{ radius: "75"; angle: "245"; value: "-80.6707"; } + ListElement{ radius: "75"; angle: "250"; value: "-82.34"; } + ListElement{ radius: "75"; angle: "255"; value: "-83.6516"; } + ListElement{ radius: "75"; angle: "260"; value: "-84.5957"; } + ListElement{ radius: "75"; angle: "265"; value: "-85.1651"; } + ListElement{ radius: "75"; angle: "270"; value: "-85.3553"; } + ListElement{ radius: "75"; angle: "275"; value: "-85.1651"; } + ListElement{ radius: "75"; angle: "280"; value: "-84.5957"; } + ListElement{ radius: "75"; angle: "285"; value: "-83.6516"; } + ListElement{ radius: "75"; angle: "290"; value: "-82.34"; } + ListElement{ radius: "75"; angle: "295"; value: "-80.6707"; } + ListElement{ radius: "75"; angle: "300"; value: "-78.6566"; } + ListElement{ radius: "75"; angle: "305"; value: "-76.3129"; } + ListElement{ radius: "75"; angle: "310"; value: "-73.6576"; } + ListElement{ radius: "75"; angle: "315"; value: "-70.7107"; } + ListElement{ radius: "75"; angle: "320"; value: "-67.4947"; } + ListElement{ radius: "75"; angle: "325"; value: "-64.0342"; } + ListElement{ radius: "75"; angle: "330"; value: "-60.3553"; } + ListElement{ radius: "75"; angle: "335"; value: "-56.4863"; } + ListElement{ radius: "75"; angle: "340"; value: "-52.4563"; } + ListElement{ radius: "75"; angle: "345"; value: "-48.2963"; } + ListElement{ radius: "75"; angle: "350"; value: "-44.0377"; } + ListElement{ radius: "75"; angle: "355"; value: "-39.7131"; } + ListElement{ radius: "75"; angle: "360"; value: "-35.3553"; } + ListElement{ radius: "80"; angle: "0"; value: "-40.4508"; } + ListElement{ radius: "80"; angle: "5"; value: "-36.0931"; } + ListElement{ radius: "80"; angle: "10"; value: "-31.7684"; } + ListElement{ radius: "80"; angle: "15"; value: "-27.5099"; } + ListElement{ radius: "80"; angle: "20"; value: "-23.3498"; } + ListElement{ radius: "80"; angle: "25"; value: "-19.3199"; } + ListElement{ radius: "80"; angle: "30"; value: "-15.4508"; } + ListElement{ radius: "80"; angle: "35"; value: "-11.772"; } + ListElement{ radius: "80"; angle: "40"; value: "-8.31147"; } + ListElement{ radius: "80"; angle: "45"; value: "-5.09551"; } + ListElement{ radius: "80"; angle: "50"; value: "-2.14863"; } + ListElement{ radius: "80"; angle: "55"; value: "0.506752"; } + ListElement{ radius: "80"; angle: "60"; value: "2.85042"; } + ListElement{ radius: "80"; angle: "65"; value: "4.86454"; } + ListElement{ radius: "80"; angle: "70"; value: "6.53378"; } + ListElement{ radius: "80"; angle: "75"; value: "7.84544"; } + ListElement{ radius: "80"; angle: "80"; value: "8.78954"; } + ListElement{ radius: "80"; angle: "85"; value: "9.35889"; } + ListElement{ radius: "80"; angle: "90"; value: "9.54915"; } + ListElement{ radius: "80"; angle: "95"; value: "9.35889"; } + ListElement{ radius: "80"; angle: "100"; value: "8.78954"; } + ListElement{ radius: "80"; angle: "105"; value: "7.84544"; } + ListElement{ radius: "80"; angle: "110"; value: "6.53378"; } + ListElement{ radius: "80"; angle: "115"; value: "4.86454"; } + ListElement{ radius: "80"; angle: "120"; value: "2.85042"; } + ListElement{ radius: "80"; angle: "125"; value: "0.506752"; } + ListElement{ radius: "80"; angle: "130"; value: "-2.14863"; } + ListElement{ radius: "80"; angle: "135"; value: "-5.09551"; } + ListElement{ radius: "80"; angle: "140"; value: "-8.31147"; } + ListElement{ radius: "80"; angle: "145"; value: "-11.772"; } + ListElement{ radius: "80"; angle: "150"; value: "-15.4508"; } + ListElement{ radius: "80"; angle: "155"; value: "-19.3199"; } + ListElement{ radius: "80"; angle: "160"; value: "-23.3498"; } + ListElement{ radius: "80"; angle: "165"; value: "-27.5099"; } + ListElement{ radius: "80"; angle: "170"; value: "-31.7684"; } + ListElement{ radius: "80"; angle: "175"; value: "-36.0931"; } + ListElement{ radius: "80"; angle: "180"; value: "-40.4508"; } + ListElement{ radius: "80"; angle: "185"; value: "-44.8086"; } + ListElement{ radius: "80"; angle: "190"; value: "-49.1333"; } + ListElement{ radius: "80"; angle: "195"; value: "-53.3918"; } + ListElement{ radius: "80"; angle: "200"; value: "-57.5519"; } + ListElement{ radius: "80"; angle: "205"; value: "-61.5818"; } + ListElement{ radius: "80"; angle: "210"; value: "-65.4508"; } + ListElement{ radius: "80"; angle: "215"; value: "-69.1297"; } + ListElement{ radius: "80"; angle: "220"; value: "-72.5902"; } + ListElement{ radius: "80"; angle: "225"; value: "-75.8062"; } + ListElement{ radius: "80"; angle: "230"; value: "-78.7531"; } + ListElement{ radius: "80"; angle: "235"; value: "-81.4085"; } + ListElement{ radius: "80"; angle: "240"; value: "-83.7521"; } + ListElement{ radius: "80"; angle: "245"; value: "-85.7662"; } + ListElement{ radius: "80"; angle: "250"; value: "-87.4355"; } + ListElement{ radius: "80"; angle: "255"; value: "-88.7471"; } + ListElement{ radius: "80"; angle: "260"; value: "-89.6912"; } + ListElement{ radius: "80"; angle: "265"; value: "-90.2606"; } + ListElement{ radius: "80"; angle: "270"; value: "-90.4508"; } + ListElement{ radius: "80"; angle: "275"; value: "-90.2606"; } + ListElement{ radius: "80"; angle: "280"; value: "-89.6912"; } + ListElement{ radius: "80"; angle: "285"; value: "-88.7471"; } + ListElement{ radius: "80"; angle: "290"; value: "-87.4355"; } + ListElement{ radius: "80"; angle: "295"; value: "-85.7662"; } + ListElement{ radius: "80"; angle: "300"; value: "-83.7521"; } + ListElement{ radius: "80"; angle: "305"; value: "-81.4085"; } + ListElement{ radius: "80"; angle: "310"; value: "-78.7531"; } + ListElement{ radius: "80"; angle: "315"; value: "-75.8062"; } + ListElement{ radius: "80"; angle: "320"; value: "-72.5902"; } + ListElement{ radius: "80"; angle: "325"; value: "-69.1297"; } + ListElement{ radius: "80"; angle: "330"; value: "-65.4508"; } + ListElement{ radius: "80"; angle: "335"; value: "-61.5818"; } + ListElement{ radius: "80"; angle: "340"; value: "-57.5519"; } + ListElement{ radius: "80"; angle: "345"; value: "-53.3918"; } + ListElement{ radius: "80"; angle: "350"; value: "-49.1333"; } + ListElement{ radius: "80"; angle: "355"; value: "-44.8086"; } + ListElement{ radius: "80"; angle: "360"; value: "-40.4508"; } + ListElement{ radius: "85"; angle: "0"; value: "-44.5503"; } + ListElement{ radius: "85"; angle: "5"; value: "-40.1925"; } + ListElement{ radius: "85"; angle: "10"; value: "-35.8679"; } + ListElement{ radius: "85"; angle: "15"; value: "-31.6094"; } + ListElement{ radius: "85"; angle: "20"; value: "-27.4493"; } + ListElement{ radius: "85"; angle: "25"; value: "-23.4194"; } + ListElement{ radius: "85"; angle: "30"; value: "-19.5503"; } + ListElement{ radius: "85"; angle: "35"; value: "-15.8715"; } + ListElement{ radius: "85"; angle: "40"; value: "-12.4109"; } + ListElement{ radius: "85"; angle: "45"; value: "-9.19499"; } + ListElement{ radius: "85"; angle: "50"; value: "-6.2481"; } + ListElement{ radius: "85"; angle: "55"; value: "-3.59272"; } + ListElement{ radius: "85"; angle: "60"; value: "-1.24906"; } + ListElement{ radius: "85"; angle: "65"; value: "0.765063"; } + ListElement{ radius: "85"; angle: "70"; value: "2.4343"; } + ListElement{ radius: "85"; angle: "75"; value: "3.74597"; } + ListElement{ radius: "85"; angle: "80"; value: "4.69006"; } + ListElement{ radius: "85"; angle: "85"; value: "5.25941"; } + ListElement{ radius: "85"; angle: "90"; value: "5.44967"; } + ListElement{ radius: "85"; angle: "95"; value: "5.25941"; } + ListElement{ radius: "85"; angle: "100"; value: "4.69006"; } + ListElement{ radius: "85"; angle: "105"; value: "3.74597"; } + ListElement{ radius: "85"; angle: "110"; value: "2.4343"; } + ListElement{ radius: "85"; angle: "115"; value: "0.765063"; } + ListElement{ radius: "85"; angle: "120"; value: "-1.24906"; } + ListElement{ radius: "85"; angle: "125"; value: "-3.59272"; } + ListElement{ radius: "85"; angle: "130"; value: "-6.2481"; } + ListElement{ radius: "85"; angle: "135"; value: "-9.19499"; } + ListElement{ radius: "85"; angle: "140"; value: "-12.4109"; } + ListElement{ radius: "85"; angle: "145"; value: "-15.8715"; } + ListElement{ radius: "85"; angle: "150"; value: "-19.5503"; } + ListElement{ radius: "85"; angle: "155"; value: "-23.4194"; } + ListElement{ radius: "85"; angle: "160"; value: "-27.4493"; } + ListElement{ radius: "85"; angle: "165"; value: "-31.6094"; } + ListElement{ radius: "85"; angle: "170"; value: "-35.8679"; } + ListElement{ radius: "85"; angle: "175"; value: "-40.1925"; } + ListElement{ radius: "85"; angle: "180"; value: "-44.5503"; } + ListElement{ radius: "85"; angle: "185"; value: "-48.9081"; } + ListElement{ radius: "85"; angle: "190"; value: "-53.2327"; } + ListElement{ radius: "85"; angle: "195"; value: "-57.4913"; } + ListElement{ radius: "85"; angle: "200"; value: "-61.6513"; } + ListElement{ radius: "85"; angle: "205"; value: "-65.6812"; } + ListElement{ radius: "85"; angle: "210"; value: "-69.5503"; } + ListElement{ radius: "85"; angle: "215"; value: "-73.2291"; } + ListElement{ radius: "85"; angle: "220"; value: "-76.6897"; } + ListElement{ radius: "85"; angle: "225"; value: "-79.9057"; } + ListElement{ radius: "85"; angle: "230"; value: "-82.8525"; } + ListElement{ radius: "85"; angle: "235"; value: "-85.5079"; } + ListElement{ radius: "85"; angle: "240"; value: "-87.8516"; } + ListElement{ radius: "85"; angle: "245"; value: "-89.8657"; } + ListElement{ radius: "85"; angle: "250"; value: "-91.535"; } + ListElement{ radius: "85"; angle: "255"; value: "-92.8466"; } + ListElement{ radius: "85"; angle: "260"; value: "-93.7907"; } + ListElement{ radius: "85"; angle: "265"; value: "-94.3601"; } + ListElement{ radius: "85"; angle: "270"; value: "-94.5503"; } + ListElement{ radius: "85"; angle: "275"; value: "-94.3601"; } + ListElement{ radius: "85"; angle: "280"; value: "-93.7907"; } + ListElement{ radius: "85"; angle: "285"; value: "-92.8466"; } + ListElement{ radius: "85"; angle: "290"; value: "-91.535"; } + ListElement{ radius: "85"; angle: "295"; value: "-89.8657"; } + ListElement{ radius: "85"; angle: "300"; value: "-87.8516"; } + ListElement{ radius: "85"; angle: "305"; value: "-85.5079"; } + ListElement{ radius: "85"; angle: "310"; value: "-82.8525"; } + ListElement{ radius: "85"; angle: "315"; value: "-79.9057"; } + ListElement{ radius: "85"; angle: "320"; value: "-76.6897"; } + ListElement{ radius: "85"; angle: "325"; value: "-73.2291"; } + ListElement{ radius: "85"; angle: "330"; value: "-69.5503"; } + ListElement{ radius: "85"; angle: "335"; value: "-65.6812"; } + ListElement{ radius: "85"; angle: "340"; value: "-61.6513"; } + ListElement{ radius: "85"; angle: "345"; value: "-57.4913"; } + ListElement{ radius: "85"; angle: "350"; value: "-53.2327"; } + ListElement{ radius: "85"; angle: "355"; value: "-48.9081"; } + ListElement{ radius: "85"; angle: "360"; value: "-44.5503"; } + ListElement{ radius: "90"; angle: "0"; value: "-47.5528"; } + ListElement{ radius: "90"; angle: "5"; value: "-43.195"; } + ListElement{ radius: "90"; angle: "10"; value: "-38.8704"; } + ListElement{ radius: "90"; angle: "15"; value: "-34.6119"; } + ListElement{ radius: "90"; angle: "20"; value: "-30.4518"; } + ListElement{ radius: "90"; angle: "25"; value: "-26.4219"; } + ListElement{ radius: "90"; angle: "30"; value: "-22.5528"; } + ListElement{ radius: "90"; angle: "35"; value: "-18.874"; } + ListElement{ radius: "90"; angle: "40"; value: "-15.4134"; } + ListElement{ radius: "90"; angle: "45"; value: "-12.1975"; } + ListElement{ radius: "90"; angle: "50"; value: "-9.2506"; } + ListElement{ radius: "90"; angle: "55"; value: "-6.59522"; } + ListElement{ radius: "90"; angle: "60"; value: "-4.25156"; } + ListElement{ radius: "90"; angle: "65"; value: "-2.23744"; } + ListElement{ radius: "90"; angle: "70"; value: "-0.568195"; } + ListElement{ radius: "90"; angle: "75"; value: "0.743465"; } + ListElement{ radius: "90"; angle: "80"; value: "1.68756"; } + ListElement{ radius: "90"; angle: "85"; value: "2.25691"; } + ListElement{ radius: "90"; angle: "90"; value: "2.44717"; } + ListElement{ radius: "90"; angle: "95"; value: "2.25691"; } + ListElement{ radius: "90"; angle: "100"; value: "1.68756"; } + ListElement{ radius: "90"; angle: "105"; value: "0.743465"; } + ListElement{ radius: "90"; angle: "110"; value: "-0.568195"; } + ListElement{ radius: "90"; angle: "115"; value: "-2.23744"; } + ListElement{ radius: "90"; angle: "120"; value: "-4.25156"; } + ListElement{ radius: "90"; angle: "125"; value: "-6.59522"; } + ListElement{ radius: "90"; angle: "130"; value: "-9.2506"; } + ListElement{ radius: "90"; angle: "135"; value: "-12.1975"; } + ListElement{ radius: "90"; angle: "140"; value: "-15.4134"; } + ListElement{ radius: "90"; angle: "145"; value: "-18.874"; } + ListElement{ radius: "90"; angle: "150"; value: "-22.5528"; } + ListElement{ radius: "90"; angle: "155"; value: "-26.4219"; } + ListElement{ radius: "90"; angle: "160"; value: "-30.4518"; } + ListElement{ radius: "90"; angle: "165"; value: "-34.6119"; } + ListElement{ radius: "90"; angle: "170"; value: "-38.8704"; } + ListElement{ radius: "90"; angle: "175"; value: "-43.195"; } + ListElement{ radius: "90"; angle: "180"; value: "-47.5528"; } + ListElement{ radius: "90"; angle: "185"; value: "-51.9106"; } + ListElement{ radius: "90"; angle: "190"; value: "-56.2352"; } + ListElement{ radius: "90"; angle: "195"; value: "-60.4938"; } + ListElement{ radius: "90"; angle: "200"; value: "-64.6538"; } + ListElement{ radius: "90"; angle: "205"; value: "-68.6837"; } + ListElement{ radius: "90"; angle: "210"; value: "-72.5528"; } + ListElement{ radius: "90"; angle: "215"; value: "-76.2316"; } + ListElement{ radius: "90"; angle: "220"; value: "-79.6922"; } + ListElement{ radius: "90"; angle: "225"; value: "-82.9082"; } + ListElement{ radius: "90"; angle: "230"; value: "-85.855"; } + ListElement{ radius: "90"; angle: "235"; value: "-88.5104"; } + ListElement{ radius: "90"; angle: "240"; value: "-90.8541"; } + ListElement{ radius: "90"; angle: "245"; value: "-92.8682"; } + ListElement{ radius: "90"; angle: "250"; value: "-94.5375"; } + ListElement{ radius: "90"; angle: "255"; value: "-95.8491"; } + ListElement{ radius: "90"; angle: "260"; value: "-96.7932"; } + ListElement{ radius: "90"; angle: "265"; value: "-97.3626"; } + ListElement{ radius: "90"; angle: "270"; value: "-97.5528"; } + ListElement{ radius: "90"; angle: "275"; value: "-97.3626"; } + ListElement{ radius: "90"; angle: "280"; value: "-96.7932"; } + ListElement{ radius: "90"; angle: "285"; value: "-95.8491"; } + ListElement{ radius: "90"; angle: "290"; value: "-94.5375"; } + ListElement{ radius: "90"; angle: "295"; value: "-92.8682"; } + ListElement{ radius: "90"; angle: "300"; value: "-90.8541"; } + ListElement{ radius: "90"; angle: "305"; value: "-88.5104"; } + ListElement{ radius: "90"; angle: "310"; value: "-85.855"; } + ListElement{ radius: "90"; angle: "315"; value: "-82.9082"; } + ListElement{ radius: "90"; angle: "320"; value: "-79.6922"; } + ListElement{ radius: "90"; angle: "325"; value: "-76.2316"; } + ListElement{ radius: "90"; angle: "330"; value: "-72.5528"; } + ListElement{ radius: "90"; angle: "335"; value: "-68.6837"; } + ListElement{ radius: "90"; angle: "340"; value: "-64.6538"; } + ListElement{ radius: "90"; angle: "345"; value: "-60.4938"; } + ListElement{ radius: "90"; angle: "350"; value: "-56.2352"; } + ListElement{ radius: "90"; angle: "355"; value: "-51.9106"; } + ListElement{ radius: "90"; angle: "360"; value: "-47.5528"; } + ListElement{ radius: "95"; angle: "0"; value: "-49.3844"; } + ListElement{ radius: "95"; angle: "5"; value: "-45.0266"; } + ListElement{ radius: "95"; angle: "10"; value: "-40.702"; } + ListElement{ radius: "95"; angle: "15"; value: "-36.4435"; } + ListElement{ radius: "95"; angle: "20"; value: "-32.2834"; } + ListElement{ radius: "95"; angle: "25"; value: "-28.2535"; } + ListElement{ radius: "95"; angle: "30"; value: "-24.3844"; } + ListElement{ radius: "95"; angle: "35"; value: "-20.7056"; } + ListElement{ radius: "95"; angle: "40"; value: "-17.245"; } + ListElement{ radius: "95"; angle: "45"; value: "-14.0291"; } + ListElement{ radius: "95"; angle: "50"; value: "-11.0822"; } + ListElement{ radius: "95"; angle: "55"; value: "-8.42681"; } + ListElement{ radius: "95"; angle: "60"; value: "-6.08315"; } + ListElement{ radius: "95"; angle: "65"; value: "-4.06903"; } + ListElement{ radius: "95"; angle: "70"; value: "-2.39979"; } + ListElement{ radius: "95"; angle: "75"; value: "-1.08813"; } + ListElement{ radius: "95"; angle: "80"; value: "-0.144029"; } + ListElement{ radius: "95"; angle: "85"; value: "0.425318"; } + ListElement{ radius: "95"; angle: "90"; value: "0.615583"; } + ListElement{ radius: "95"; angle: "95"; value: "0.425318"; } + ListElement{ radius: "95"; angle: "100"; value: "-0.144029"; } + ListElement{ radius: "95"; angle: "105"; value: "-1.08813"; } + ListElement{ radius: "95"; angle: "110"; value: "-2.39979"; } + ListElement{ radius: "95"; angle: "115"; value: "-4.06903"; } + ListElement{ radius: "95"; angle: "120"; value: "-6.08315"; } + ListElement{ radius: "95"; angle: "125"; value: "-8.42681"; } + ListElement{ radius: "95"; angle: "130"; value: "-11.0822"; } + ListElement{ radius: "95"; angle: "135"; value: "-14.0291"; } + ListElement{ radius: "95"; angle: "140"; value: "-17.245"; } + ListElement{ radius: "95"; angle: "145"; value: "-20.7056"; } + ListElement{ radius: "95"; angle: "150"; value: "-24.3844"; } + ListElement{ radius: "95"; angle: "155"; value: "-28.2535"; } + ListElement{ radius: "95"; angle: "160"; value: "-32.2834"; } + ListElement{ radius: "95"; angle: "165"; value: "-36.4435"; } + ListElement{ radius: "95"; angle: "170"; value: "-40.702"; } + ListElement{ radius: "95"; angle: "175"; value: "-45.0266"; } + ListElement{ radius: "95"; angle: "180"; value: "-49.3844"; } + ListElement{ radius: "95"; angle: "185"; value: "-53.7422"; } + ListElement{ radius: "95"; angle: "190"; value: "-58.0668"; } + ListElement{ radius: "95"; angle: "195"; value: "-62.3254"; } + ListElement{ radius: "95"; angle: "200"; value: "-66.4854"; } + ListElement{ radius: "95"; angle: "205"; value: "-70.5153"; } + ListElement{ radius: "95"; angle: "210"; value: "-74.3844"; } + ListElement{ radius: "95"; angle: "215"; value: "-78.0632"; } + ListElement{ radius: "95"; angle: "220"; value: "-81.5238"; } + ListElement{ radius: "95"; angle: "225"; value: "-84.7398"; } + ListElement{ radius: "95"; angle: "230"; value: "-87.6866"; } + ListElement{ radius: "95"; angle: "235"; value: "-90.342"; } + ListElement{ radius: "95"; angle: "240"; value: "-92.6857"; } + ListElement{ radius: "95"; angle: "245"; value: "-94.6998"; } + ListElement{ radius: "95"; angle: "250"; value: "-96.369"; } + ListElement{ radius: "95"; angle: "255"; value: "-97.6807"; } + ListElement{ radius: "95"; angle: "260"; value: "-98.6248"; } + ListElement{ radius: "95"; angle: "265"; value: "-99.1942"; } + ListElement{ radius: "95"; angle: "270"; value: "-99.3844"; } + ListElement{ radius: "95"; angle: "275"; value: "-99.1942"; } + ListElement{ radius: "95"; angle: "280"; value: "-98.6248"; } + ListElement{ radius: "95"; angle: "285"; value: "-97.6807"; } + ListElement{ radius: "95"; angle: "290"; value: "-96.369"; } + ListElement{ radius: "95"; angle: "295"; value: "-94.6998"; } + ListElement{ radius: "95"; angle: "300"; value: "-92.6857"; } + ListElement{ radius: "95"; angle: "305"; value: "-90.342"; } + ListElement{ radius: "95"; angle: "310"; value: "-87.6866"; } + ListElement{ radius: "95"; angle: "315"; value: "-84.7398"; } + ListElement{ radius: "95"; angle: "320"; value: "-81.5238"; } + ListElement{ radius: "95"; angle: "325"; value: "-78.0632"; } + ListElement{ radius: "95"; angle: "330"; value: "-74.3844"; } + ListElement{ radius: "95"; angle: "335"; value: "-70.5153"; } + ListElement{ radius: "95"; angle: "340"; value: "-66.4854"; } + ListElement{ radius: "95"; angle: "345"; value: "-62.3254"; } + ListElement{ radius: "95"; angle: "350"; value: "-58.0668"; } + ListElement{ radius: "95"; angle: "355"; value: "-53.7422"; } + ListElement{ radius: "95"; angle: "360"; value: "-49.3844"; } + ListElement{ radius: "100"; angle: "0"; value: "-50"; } + ListElement{ radius: "100"; angle: "5"; value: "-45.6422"; } + ListElement{ radius: "100"; angle: "10"; value: "-41.3176"; } + ListElement{ radius: "100"; angle: "15"; value: "-37.059"; } + ListElement{ radius: "100"; angle: "20"; value: "-32.899"; } + ListElement{ radius: "100"; angle: "25"; value: "-28.8691"; } + ListElement{ radius: "100"; angle: "30"; value: "-25"; } + ListElement{ radius: "100"; angle: "35"; value: "-21.3212"; } + ListElement{ radius: "100"; angle: "40"; value: "-17.8606"; } + ListElement{ radius: "100"; angle: "45"; value: "-14.6447"; } + ListElement{ radius: "100"; angle: "50"; value: "-11.6978"; } + ListElement{ radius: "100"; angle: "55"; value: "-9.0424"; } + ListElement{ radius: "100"; angle: "60"; value: "-6.69873"; } + ListElement{ radius: "100"; angle: "65"; value: "-4.68461"; } + ListElement{ radius: "100"; angle: "70"; value: "-3.01537"; } + ListElement{ radius: "100"; angle: "75"; value: "-1.70371"; } + ListElement{ radius: "100"; angle: "80"; value: "-0.759612"; } + ListElement{ radius: "100"; angle: "85"; value: "-0.190265"; } + ListElement{ radius: "100"; angle: "90"; value: "0"; } + ListElement{ radius: "100"; angle: "95"; value: "-0.190265"; } + ListElement{ radius: "100"; angle: "100"; value: "-0.759612"; } + ListElement{ radius: "100"; angle: "105"; value: "-1.70371"; } + ListElement{ radius: "100"; angle: "110"; value: "-3.01537"; } + ListElement{ radius: "100"; angle: "115"; value: "-4.68461"; } + ListElement{ radius: "100"; angle: "120"; value: "-6.69873"; } + ListElement{ radius: "100"; angle: "125"; value: "-9.0424"; } + ListElement{ radius: "100"; angle: "130"; value: "-11.6978"; } + ListElement{ radius: "100"; angle: "135"; value: "-14.6447"; } + ListElement{ radius: "100"; angle: "140"; value: "-17.8606"; } + ListElement{ radius: "100"; angle: "145"; value: "-21.3212"; } + ListElement{ radius: "100"; angle: "150"; value: "-25"; } + ListElement{ radius: "100"; angle: "155"; value: "-28.8691"; } + ListElement{ radius: "100"; angle: "160"; value: "-32.899"; } + ListElement{ radius: "100"; angle: "165"; value: "-37.059"; } + ListElement{ radius: "100"; angle: "170"; value: "-41.3176"; } + ListElement{ radius: "100"; angle: "175"; value: "-45.6422"; } + ListElement{ radius: "100"; angle: "180"; value: "-50"; } + ListElement{ radius: "100"; angle: "185"; value: "-54.3578"; } + ListElement{ radius: "100"; angle: "190"; value: "-58.6824"; } + ListElement{ radius: "100"; angle: "195"; value: "-62.941"; } + ListElement{ radius: "100"; angle: "200"; value: "-67.101"; } + ListElement{ radius: "100"; angle: "205"; value: "-71.1309"; } + ListElement{ radius: "100"; angle: "210"; value: "-75"; } + ListElement{ radius: "100"; angle: "215"; value: "-78.6788"; } + ListElement{ radius: "100"; angle: "220"; value: "-82.1394"; } + ListElement{ radius: "100"; angle: "225"; value: "-85.3553"; } + ListElement{ radius: "100"; angle: "230"; value: "-88.3022"; } + ListElement{ radius: "100"; angle: "235"; value: "-90.9576"; } + ListElement{ radius: "100"; angle: "240"; value: "-93.3013"; } + ListElement{ radius: "100"; angle: "245"; value: "-95.3154"; } + ListElement{ radius: "100"; angle: "250"; value: "-96.9846"; } + ListElement{ radius: "100"; angle: "255"; value: "-98.2963"; } + ListElement{ radius: "100"; angle: "260"; value: "-99.2404"; } + ListElement{ radius: "100"; angle: "265"; value: "-99.8097"; } + ListElement{ radius: "100"; angle: "270"; value: "-100"; } + ListElement{ radius: "100"; angle: "275"; value: "-99.8097"; } + ListElement{ radius: "100"; angle: "280"; value: "-99.2404"; } + ListElement{ radius: "100"; angle: "285"; value: "-98.2963"; } + ListElement{ radius: "100"; angle: "290"; value: "-96.9846"; } + ListElement{ radius: "100"; angle: "295"; value: "-95.3154"; } + ListElement{ radius: "100"; angle: "300"; value: "-93.3013"; } + ListElement{ radius: "100"; angle: "305"; value: "-90.9576"; } + ListElement{ radius: "100"; angle: "310"; value: "-88.3022"; } + ListElement{ radius: "100"; angle: "315"; value: "-85.3553"; } + ListElement{ radius: "100"; angle: "320"; value: "-82.1394"; } + ListElement{ radius: "100"; angle: "325"; value: "-78.6788"; } + ListElement{ radius: "100"; angle: "330"; value: "-75"; } + ListElement{ radius: "100"; angle: "335"; value: "-71.1309"; } + ListElement{ radius: "100"; angle: "340"; value: "-67.101"; } + ListElement{ radius: "100"; angle: "345"; value: "-62.941"; } + ListElement{ radius: "100"; angle: "350"; value: "-58.6824"; } + ListElement{ radius: "100"; angle: "355"; value: "-54.3578"; } + ListElement{ radius: "100"; angle: "360"; value: "-50"; } + } +} diff --git a/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/NewButton.qml b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/NewButton.qml new file mode 100644 index 00000000..e4fb99d2 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/NewButton.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + +Item { + id: newbutton + + property alias text: buttonText.text + + signal clicked + + implicitWidth: buttonText.implicitWidth + 5 + implicitHeight: buttonText.implicitHeight + 10 + + Button { + id: buttonText + width: parent.width + height: parent.height + + style: ButtonStyle { + label: Component { + Text { + text: buttonText.text + clip: true + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.fill: parent + } + } + } + onClicked: newbutton.clicked() + } +} diff --git a/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/main.qml b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/main.qml new file mode 100644 index 00000000..4c77fd45 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/qml/qmlspectrogram/main.qml @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.0 +import QtQuick.Window 2.1 +import QtDataVisualization 1.2 +import "." + +Window { + id: mainview + title: "Qt Quick 2 Spectrogram Example" + visible: true + width: 1024 + height: 768 + color: surfaceGraph.theme.windowColor + + Data { + id: surfaceData + } + + Item { + id: surfaceView + width: mainview.width + height: mainview.height + anchors.top: mainview.top + anchors.left: mainview.left + + ColorGradient { + id: surfaceGradient + ColorGradientStop { position: 0.0; color: "black" } + ColorGradientStop { position: 0.2; color: "red" } + ColorGradientStop { position: 0.5; color: "blue" } + ColorGradientStop { position: 0.8; color: "yellow" } + ColorGradientStop { position: 1.0; color: "white" } + } + + ValueAxis3D { + id: xAxis + segmentCount: 8 + labelFormat: "%i\u00B0" + title: "Angle" + titleVisible: true + titleFixed: false + } + + ValueAxis3D { + id: yAxis + segmentCount: 8 + labelFormat: "%i \%" + title: "Value" + titleVisible: true + labelAutoRotation: 0 + titleFixed: false + } + + ValueAxis3D { + id: zAxis + segmentCount: 5 + labelFormat: "%i nm" + title: "Radius" + titleVisible: true + titleFixed: false + } + + Theme3D { + id: customTheme + type: Theme3D.ThemeQt + // Don't show specular spotlight as we don't want it to distort the colors + lightStrength: 0.0 + ambientLightStrength: 1.0 + backgroundEnabled: false + gridLineColor: "#AAAAAA" + windowColor: "#EEEEEE" + } + + + //! [5] + TouchInputHandler3D { + id: customInputHandler + rotationEnabled: false + } + //! [5] + + //! [0] + //! [7] + Surface3D { + //! [7] + id: surfaceGraph + width: surfaceView.width + height: surfaceView.height + + shadowQuality: AbstractGraph3D.ShadowQualityNone + selectionMode: AbstractGraph3D.SelectionSlice | AbstractGraph3D.SelectionItemAndColumn + axisX: xAxis + axisY: yAxis + axisZ: zAxis + + theme: customTheme + //! [6] + inputHandler: customInputHandler + //! [6] + + // Remove the perspective and view the graph from top down to achieve 2D effect + //! [1] + orthoProjection: true + scene.activeCamera.cameraPreset: Camera3D.CameraPresetDirectlyAbove + //! [1] + + //! [2] + flipHorizontalGrid: true + //! [2] + + //! [4] + radialLabelOffset: 0.01 + //! [4] + + horizontalAspectRatio: 1 + scene.activeCamera.zoomLevel: 85 + + Surface3DSeries { + id: surfaceSeries + flatShadingEnabled: false + drawMode: Surface3DSeries.DrawSurface + baseGradient: surfaceGradient + colorStyle: Theme3D.ColorStyleRangeGradient + itemLabelFormat: "(@xLabel, @zLabel): @yLabel" + + ItemModelSurfaceDataProxy { + itemModel: surfaceData.model + rowRole: "radius" + columnRole: "angle" + yPosRole: "value" + } + } + } + //! [0] + } + + RowLayout { + id: buttonLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + opacity: 0.5 + + //! [3] + NewButton { + id: polarToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Switch to polar" + onClicked: { + if (surfaceGraph.polar === false) { + surfaceGraph.polar = true + text = "Switch to cartesian" + } else { + surfaceGraph.polar = false + text = "Switch to polar" + } + } + } + //! [3] + + NewButton { + id: orthoToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Switch to perspective" + onClicked: { + if (surfaceGraph.orthoProjection === true) { + surfaceGraph.orthoProjection = false; + xAxis.labelAutoRotation = 30 + yAxis.labelAutoRotation = 30 + zAxis.labelAutoRotation = 30 + customInputHandler.rotationEnabled = true + text = "Switch to orthographic" + } else { + surfaceGraph.orthoProjection = true; + surfaceGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetDirectlyAbove + surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe; + xAxis.labelAutoRotation = 0 + yAxis.labelAutoRotation = 0 + zAxis.labelAutoRotation = 0 + customInputHandler.rotationEnabled = false + text = "Switch to perspective" + } + } + } + + NewButton { + id: flipGridToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Toggle axis grid on top" + onClicked: { + onClicked: { + if (surfaceGraph.flipHorizontalGrid === true) { + surfaceGraph.flipHorizontalGrid = false; + } else { + surfaceGraph.flipHorizontalGrid = true; + } + } + } + } + + NewButton { + id: labelOffsetToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Toggle radial label position" + visible: surfaceGraph.polar + onClicked: { + if (surfaceGraph.radialLabelOffset >= 1.0) { + surfaceGraph.radialLabelOffset = 0.01 + } else { + surfaceGraph.radialLabelOffset = 1.0 + } + } + } + + NewButton { + id: surfaceGridToggle + Layout.fillWidth: true + Layout.fillHeight: true + text: "Toggle surface grid" + visible: !surfaceGraph.orthoProjection + onClicked: { + if (surfaceSeries.drawMode & Surface3DSeries.DrawWireframe) { + surfaceSeries.drawMode &= ~Surface3DSeries.DrawWireframe; + } else { + surfaceSeries.drawMode |= Surface3DSeries.DrawWireframe; + } + } + } + + } + + Rectangle { + id: legend + anchors.margins: 20 + anchors.bottom: parent.bottom + anchors.top: buttonLayout.bottom + anchors.right: parent.right + border.color: "black" + border.width: 1 + width: 50 + rotation: 180 + gradient: Gradient { + GradientStop { position: 0.0; color: "black" } + GradientStop { position: 0.2; color: "red" } + GradientStop { position: 0.5; color: "blue" } + GradientStop { position: 0.8; color: "yellow" } + GradientStop { position: 1.0; color: "white" } + } + } + + Text { + anchors.verticalCenter: legend.bottom + anchors.right: legend.left + anchors.margins: 2 + text: surfaceGraph.axisY.min + "%" + } + + Text { + anchors.verticalCenter: legend.verticalCenter + anchors.right: legend.left + anchors.margins: 2 + text: (surfaceGraph.axisY.max + surfaceGraph.axisY.min) / 2 + "%" + } + + Text { + anchors.verticalCenter: legend.top + anchors.right: legend.left + anchors.margins: 2 + text: surfaceGraph.axisY.max + "%" + } +} diff --git a/examples/datavisualization/qmlspectrogram/qmlspectrogram.pro b/examples/datavisualization/qmlspectrogram/qmlspectrogram.pro new file mode 100644 index 00000000..655fb0b8 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/qmlspectrogram.pro @@ -0,0 +1,12 @@ +!include( ../examples.pri ) { + error( "Couldn't find the examples.pri file!" ) +} + +# The .cpp file which was generated for your project. Feel free to hack it. +SOURCES += main.cpp + +RESOURCES += qmlspectrogram.qrc + +OTHER_FILES += doc/src/* \ + doc/images/* \ + qml/qmlspectrogram/* diff --git a/examples/datavisualization/qmlspectrogram/qmlspectrogram.qrc b/examples/datavisualization/qmlspectrogram/qmlspectrogram.qrc new file mode 100644 index 00000000..9f024404 --- /dev/null +++ b/examples/datavisualization/qmlspectrogram/qmlspectrogram.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/qml"> + <file>qml/qmlspectrogram/Data.qml</file> + <file>qml/qmlspectrogram/main.qml</file> + <file>qml/qmlspectrogram/NewButton.qml</file> + </qresource> +</RCC> diff --git a/examples/datavisualization/qmlsurfacelayers/layer_2.png b/examples/datavisualization/qmlsurfacelayers/layer_2.png Binary files differindex 61631ae8..3af154e2 100644 --- a/examples/datavisualization/qmlsurfacelayers/layer_2.png +++ b/examples/datavisualization/qmlsurfacelayers/layer_2.png diff --git a/examples/datavisualization/texturesurface/custominputhandler.cpp b/examples/datavisualization/texturesurface/custominputhandler.cpp new file mode 100644 index 00000000..2510df54 --- /dev/null +++ b/examples/datavisualization/texturesurface/custominputhandler.cpp @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 "custominputhandler.h" + +#include <QtDataVisualization/Q3DCamera> +#include <QtCore/qmath.h> + +CustomInputHandler::CustomInputHandler(QAbstract3DGraph *graph, QObject *parent) : + Q3DInputHandler(parent), + m_highlight(0), + m_mousePressed(false), + m_state(StateNormal), + m_axisX(0), + m_axisZ(0), + m_speedModifier(20.0f) +{ + // Connect to the item selection signal from graph + connect(graph, &QAbstract3DGraph::selectedElementChanged, this, + &CustomInputHandler::handleElementSelected); +} + +void CustomInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos) +{ + if (Qt::LeftButton == event->button()) { + m_highlight->setVisible(false); + m_mousePressed = true; + } + Q3DInputHandler::mousePressEvent(event, mousePos); +} + +//! [1] +void CustomInputHandler::wheelEvent(QWheelEvent *event) +{ + float delta = float(event->delta()); + + m_axisXMinValue += delta; + m_axisXMaxValue -= delta; + m_axisZMinValue += delta; + m_axisZMaxValue -= delta; + checkConstraints(); + + float y = (m_axisXMaxValue - m_axisXMinValue) * m_aspectRatio; + + m_axisX->setRange(m_axisXMinValue, m_axisXMaxValue); + m_axisY->setRange(100.0f, y); + m_axisZ->setRange(m_axisZMinValue, m_axisZMaxValue); +} +//! [1] + +void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) +{ + // Check if we're trying to drag axis label + if (m_mousePressed && m_state != StateNormal) { + setPreviousInputPos(inputPosition()); + setInputPosition(mousePos); + handleAxisDragging(); + } else { + Q3DInputHandler::mouseMoveEvent(event, mousePos); + } +} + +void CustomInputHandler::mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos) +{ + Q3DInputHandler::mouseReleaseEvent(event, mousePos); + m_mousePressed = false; + m_state = StateNormal; +} + +void CustomInputHandler::handleElementSelected(QAbstract3DGraph::ElementType type) +{ + switch (type) { + case QAbstract3DGraph::ElementAxisXLabel: + m_state = StateDraggingX; + break; + case QAbstract3DGraph::ElementAxisZLabel: + m_state = StateDraggingZ; + break; + default: + m_state = StateNormal; + break; + } +} + +void CustomInputHandler::handleAxisDragging() +{ + float distance = 0.0f; + + // Get scene orientation from active camera + float xRotation = scene()->activeCamera()->xRotation(); + + // Calculate directional drag multipliers based on rotation + float xMulX = qCos(qDegreesToRadians(xRotation)); + float xMulY = qSin(qDegreesToRadians(xRotation)); + float zMulX = qSin(qDegreesToRadians(xRotation)); + float zMulY = qCos(qDegreesToRadians(xRotation)); + + // Get the drag amount + QPoint move = inputPosition() - previousInputPos(); + + // Adjust axes + switch (m_state) { +//! [0] + case StateDraggingX: + distance = (move.x() * xMulX - move.y() * xMulY) * m_speedModifier; + m_axisXMinValue -= distance; + m_axisXMaxValue -= distance; + if (m_axisXMinValue < m_areaMinValue) { + float dist = m_axisXMaxValue - m_axisXMinValue; + m_axisXMinValue = m_areaMinValue; + m_axisXMaxValue = m_axisXMinValue + dist; + } + if (m_axisXMaxValue > m_areaMaxValue) { + float dist = m_axisXMaxValue - m_axisXMinValue; + m_axisXMaxValue = m_areaMaxValue; + m_axisXMinValue = m_axisXMaxValue - dist; + } + m_axisX->setRange(m_axisXMinValue, m_axisXMaxValue); + break; +//! [0] + case StateDraggingZ: + distance = (move.x() * zMulX + move.y() * zMulY) * m_speedModifier; + m_axisZMinValue += distance; + m_axisZMaxValue += distance; + if (m_axisZMinValue < m_areaMinValue) { + float dist = m_axisZMaxValue - m_axisZMinValue; + m_axisZMinValue = m_areaMinValue; + m_axisZMaxValue = m_axisZMinValue + dist; + } + if (m_axisZMaxValue > m_areaMaxValue) { + float dist = m_axisZMaxValue - m_axisZMinValue; + m_axisZMaxValue = m_areaMaxValue; + m_axisZMinValue = m_axisZMaxValue - dist; + } + m_axisZ->setRange(m_axisZMinValue, m_axisZMaxValue); + break; + default: + break; + } +} + +void CustomInputHandler::checkConstraints() +{ +//! [2] + if (m_axisXMinValue < m_areaMinValue) + m_axisXMinValue = m_areaMinValue; + if (m_axisXMaxValue > m_areaMaxValue) + m_axisXMaxValue = m_areaMaxValue; + // Don't allow too much zoom in + if ((m_axisXMaxValue - m_axisXMinValue) < m_axisXMinRange) { + float adjust = (m_axisXMinRange - (m_axisXMaxValue - m_axisXMinValue)) / 2.0f; + m_axisXMinValue -= adjust; + m_axisXMaxValue += adjust; + } +//! [2] + + if (m_axisZMinValue < m_areaMinValue) + m_axisZMinValue = m_areaMinValue; + if (m_axisZMaxValue > m_areaMaxValue) + m_axisZMaxValue = m_areaMaxValue; + // Don't allow too much zoom in + if ((m_axisZMaxValue - m_axisZMinValue) < m_axisZMinRange) { + float adjust = (m_axisZMinRange - (m_axisZMaxValue - m_axisZMinValue)) / 2.0f; + m_axisZMinValue -= adjust; + m_axisZMaxValue += adjust; + } +} diff --git a/examples/datavisualization/texturesurface/custominputhandler.h b/examples/datavisualization/texturesurface/custominputhandler.h new file mode 100644 index 00000000..8bef990e --- /dev/null +++ b/examples/datavisualization/texturesurface/custominputhandler.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 CUSTOMINPUTHANDLER_H +#define CUSTOMINPUTHANDLER_H + +#include <QtDataVisualization/Q3DInputHandler> +#include <QtDataVisualization/QAbstract3DGraph> +#include <QtDataVisualization/QValue3DAxis> +#include "highlightseries.h" + +using namespace QtDataVisualization; + +class CustomInputHandler : public Q3DInputHandler +{ + Q_OBJECT + + enum InputState { + StateNormal = 0, + StateDraggingX, + StateDraggingZ, + StateDraggingY + }; + +public: + explicit CustomInputHandler(QAbstract3DGraph *graph, QObject *parent = 0); + + inline void setLimits(float min, float max, float minRange) { + m_areaMinValue = min; + m_areaMaxValue = max; + m_axisXMinValue = m_areaMinValue; + m_axisXMaxValue = m_areaMaxValue; + m_axisZMinValue = m_areaMinValue; + m_axisZMaxValue = m_areaMaxValue; + m_axisXMinRange = minRange; + m_axisZMinRange = minRange; + } + inline void setAxes(QValue3DAxis *axisX, QValue3DAxis *axisY, QValue3DAxis *axisZ) { + m_axisX = axisX; + m_axisY = axisY; + m_axisZ = axisZ; + } + inline void setAspectRatio(float ratio) { m_aspectRatio = ratio; } + inline void setHighlightSeries(HighlightSeries *series) { m_highlight = series; } + inline void setDragSpeedModifier(float modifier) { m_speedModifier = modifier; } + + virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos); + virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos); + virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos); + virtual void wheelEvent(QWheelEvent *event); + +private: + void handleElementSelected(QAbstract3DGraph::ElementType type); + void handleAxisDragging(); + void checkConstraints(); + +private: + HighlightSeries *m_highlight; + bool m_mousePressed; + InputState m_state; + QValue3DAxis *m_axisX; + QValue3DAxis *m_axisY; + QValue3DAxis *m_axisZ; + float m_speedModifier; + float m_aspectRatio; + float m_axisXMinValue; + float m_axisXMaxValue; + float m_axisXMinRange; + float m_axisZMinValue; + float m_axisZMaxValue; + float m_axisZMinRange; + float m_areaMinValue; + float m_areaMaxValue; +}; + +#endif diff --git a/examples/datavisualization/texturesurface/doc/images/texturesurface-example.png b/examples/datavisualization/texturesurface/doc/images/texturesurface-example.png Binary files differnew file mode 100644 index 00000000..76819607 --- /dev/null +++ b/examples/datavisualization/texturesurface/doc/images/texturesurface-example.png diff --git a/examples/datavisualization/texturesurface/doc/src/texturesurface.qdoc b/examples/datavisualization/texturesurface/doc/src/texturesurface.qdoc new file mode 100644 index 00000000..483b8110 --- /dev/null +++ b/examples/datavisualization/texturesurface/doc/src/texturesurface.qdoc @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 texturesurface + \title Textured Surface Example + \ingroup qtdatavisualization_examples + \brief Using texture with Q3DSurface. + \since QtDataVisualization 1.2 + + The textured surface example shows how to add an image as a texture for a surface. The example + shows also how to: + + \list + \li Create a surface series from an image + \li Use custom input handler to enable zooming and panning + \li Highlight an area of the surface + \endlist + + \image texturesurface-example.png + + \section1 Texture to a surface series + + The image to be set as a texture to a surface can be set using QSurface3DSeries::setTextureFile(). + In this example we have added a check box to control if the texture is set or not. The + following code extract is for reacting to the check box selections. The image in this + example is read from the resource file where it is as a JPG file. Setting an empty file + with the method clears the texture, and the surface uses the gradients or colors from the theme. + + \snippet texturesurface/surfacegraph.cpp 0 + + \section1 Topographic surface series + + The topographic data for this example is obtained from National Land Survey of Finland. It + provides a product called \c{Elevation Model 2 m}, which was suitable for our needs. We selected + Levi fell to be shown. The accuracy of the data was well beyond our needs and therefore it + is compressed and encoded into a PNG file. The height value from the original ASCII data is + encoded into RGB format using a multiplier, which you will see later on a code extract. + The multiplier is calculated simply by dividing the largest 24 bit value with the highest point + in Finland. + + Qt Data Visualization has a special proxy for height map image files, but it converts + only one byte values. So to utilize the bigger accuracy on the data from National Land + Survey of Finland, we read the data from the PNG file and decode it into QSurface3DSeries. + The following code samples show how this is done. + + First the encoding multiplier. + \snippet texturesurface/topographicseries.cpp 0 + + And then the actual decoding. + \snippet texturesurface/topographicseries.cpp 1 + + \section1 Use custom input handler to enable zooming and panning + + For the panning the implementation is similar to the \l{Axis Range Dragging With Labels Example}. + The difference is that in this example we follow only dragging of X and Z axis and we don't + allow dragging the surface outside the graph. The control for this is very simple and done as + on the following example for the X axis. + + \snippet texturesurface/custominputhandler.cpp 0 + + For the zooming we catch the \c wheelEvent and adjust the X and Y axis ranges according to delta + value on QWheelEvent. The Y axis is also adjusted so that the aspect ratio between Y axis and + XZ plane stays the same, and we don't get silly looking graph with height exaggerated too much. + + \snippet texturesurface/custominputhandler.cpp 1 + + In this case we want to control the zoom level so that it won't get too near to or far from the + surface. For instance, if the value for the X axis gets below the allowed, i.e. zooming gets too + far, the value is set to the minimum allowed value. If the range is going to below the range + minimum, both ends of the axis are adjusted so that the range stays at the limit. + + \snippet texturesurface/custominputhandler.cpp 2 + + \section1 Highlight an area of the surface + + The main idea on creating a highlight on the surface is to create a copy of the series and add + a bit of offset to the y value. On this example the class \c HighlightSeries implements the + creation of the copy on its \c handlePositionChange method. Firstly the \c HighlightSeries + needs to get the pointer to the original series and then it starts to listen the + QSurface3DSeries::selectedPointChanged signal. + + \snippet texturesurface/highlightseries.cpp 0 + + When the signal arrives, first thing is to check that the position is valid. Then the ranges + for the copied area are calculated and checked that they stay within the bounds. Finally + we simply fill the data array of the highlight series with the range from the data array of + topography series. + + \snippet texturesurface/highlightseries.cpp 1 + + \section1 A gradient to the highlight series + + Since the \c HighlightSeries is QSurface3DSeries, we can use all the decoration methods series can + have. In this example we added a gradient to emphasize the elevation. Because the suitable gradient + style depends on the range of the Y axis and we change the range when zooming, we need to adjust + the gradient color positions as the range change. + + For the gradient color positions we define proportional values. + + \snippet texturesurface/highlightseries.cpp 2 + + The gradient modification is done on \c handleGradientChange method and we connect it to react to + changes on Y axis. + + \snippet texturesurface/surfacegraph.cpp 1 + + When a change on Y axis max value happens, we calculate the gradient color positions. + + \snippet texturesurface/highlightseries.cpp 3 +*/ diff --git a/examples/datavisualization/texturesurface/highlightseries.cpp b/examples/datavisualization/texturesurface/highlightseries.cpp new file mode 100644 index 00000000..13d1fba3 --- /dev/null +++ b/examples/datavisualization/texturesurface/highlightseries.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 "highlightseries.h" + +using namespace QtDataVisualization; + +//! [2] +const float darkRedPos = 1.0f; +const float redPos = 0.8f; +const float yellowPos = 0.6f; +const float greenPos = 0.4f; +const float darkGreenPos = 0.2f; +//! [2] + +HighlightSeries::HighlightSeries() + : m_width(100), + m_height(100) +{ + setDrawMode(QSurface3DSeries::DrawSurface); + setFlatShadingEnabled(true); + setVisible(false); +} + +HighlightSeries::~HighlightSeries() +{ +} + +//! [0] +void HighlightSeries::setTopographicSeries(TopographicSeries *series) +{ + m_topographicSeries = series; + m_srcWidth = m_topographicSeries->dataProxy()->array()->at(0)->size(); + m_srcHeight = m_topographicSeries->dataProxy()->array()->size(); + + QObject::connect(m_topographicSeries, &QSurface3DSeries::selectedPointChanged, + this, &HighlightSeries::handlePositionChange); +} +//! [0] + +//! [1] +void HighlightSeries::handlePositionChange(const QPoint &position) +{ + m_position = position; + + if (position == invalidSelectionPosition()) { + setVisible(false); + + return; + } + + int halfWidth = m_width / 2; + int halfHeight = m_height / 2; + + int startX = position.y() - halfWidth; + if (startX < 0 ) + startX = 0; + int endX = position.y() + halfWidth; + if (endX > (m_srcWidth - 1)) + endX = m_srcWidth - 1; + int startZ = position.x() - halfHeight; + if (startZ < 0 ) + startZ = 0; + int endZ = position.x() + halfHeight; + if (endZ > (m_srcHeight - 1)) + endZ = m_srcHeight - 1; + + QSurfaceDataProxy *srcProxy = m_topographicSeries->dataProxy(); + const QSurfaceDataArray &srcArray = *srcProxy->array(); + + QSurfaceDataArray *dataArray = new QSurfaceDataArray; + dataArray->reserve(endZ - startZ); + for (int i = startZ; i < endZ; i++) { + QSurfaceDataRow *newRow = new QSurfaceDataRow(endX - startX); + QSurfaceDataRow *srcRow = srcArray.at(i); + for (int j = startX, p = 0; j < endX; j++, p++) { + QVector3D pos = srcRow->at(j).position(); + (*newRow)[p].setPosition(QVector3D(pos.x(), pos.y() + 0.1f, pos.z())); + } + *dataArray << newRow; + } + + dataProxy()->resetArray(dataArray); + setVisible(true); +} +//! [1] + +//! [3] +void HighlightSeries::handleGradientChange(float value) +{ + float ratio = m_minHeight / value; + + QLinearGradient gr; + gr.setColorAt(0.0f, Qt::black); + gr.setColorAt(darkGreenPos * ratio, Qt::darkGreen); + gr.setColorAt(greenPos * ratio, Qt::green); + gr.setColorAt(yellowPos * ratio, Qt::yellow); + gr.setColorAt(redPos * ratio, Qt::red); + gr.setColorAt(darkRedPos * ratio, Qt::darkRed); + + setBaseGradient(gr); + setColorStyle(Q3DTheme::ColorStyleRangeGradient); +} +//! [3] diff --git a/examples/datavisualization/texturesurface/highlightseries.h b/examples/datavisualization/texturesurface/highlightseries.h new file mode 100644 index 00000000..aa1590e5 --- /dev/null +++ b/examples/datavisualization/texturesurface/highlightseries.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 HIGHLIGHTSERIES_H +#define HIGHLIGHTSERIES_H + +#include <QtDataVisualization/QSurface3DSeries> + +#include "topographicseries.h" + +using namespace QtDataVisualization; + +class HighlightSeries : public QSurface3DSeries +{ + Q_OBJECT +public: + explicit HighlightSeries(); + ~HighlightSeries(); + + void setTopographicSeries(TopographicSeries *series); + inline void setMinHeight(float height) { m_minHeight = height; } + +public slots: + void handlePositionChange(const QPoint &position); + void handleGradientChange(float value); + +private: + int m_width; + int m_height; + int m_srcWidth; + int m_srcHeight; + QPoint m_position; + TopographicSeries *m_topographicSeries; + float m_minHeight; +}; + +#endif // HIGHLIGHTSERIES_H diff --git a/examples/datavisualization/texturesurface/license.txt b/examples/datavisualization/texturesurface/license.txt new file mode 100644 index 00000000..749daf31 --- /dev/null +++ b/examples/datavisualization/texturesurface/license.txt @@ -0,0 +1,77 @@ +License information regarding the data obtained from National Land Survey of +Finland http://www.maanmittauslaitos.fi/en +- topographic model from Elevation model 2 m (U4421B, U4421D, U4422A and + U4422C) 08/2014 +- map image extracted from Topographic map raster 1:50 000 (U442) 08/2014 + +National Land Survey open data licence - version 1.0 - 1 May 2012 + +1. General information + +The National Land Survey of Finland (hereinafter the Licensor), as the holder +of the immaterial rights to the data, has granted on the terms mentioned below +the right to use a copy (hereinafter data or dataset(s)) of the data (or a part +of it). + +The Licensee is a natural or legal person who makes use of the data covered by +this licence. The Licensee accepts the terms of this licence by receiving the +dataset(s) covered by the licence. + +This Licence agreement does not create a co-operation or business relationship +between the Licensee and the Licensor. + +2. Terms of the licence + +2.1. Right of use + +This licence grants a worldwide, free of charge and irrevocable parallel right +of use to open data. According to the terms of the licence, data received by +the Licensee can be freely: + - copied, distributed and published, + - modified and utilised commercially and non-commercially, + - inserted into other products and + - used as a part of a software application or service. + +2.2. Duties and responsibilities of the Licensee + +Through reasonable means suitable to the distribution medium or method which is +used in conjunction with a product containing data or a service utilising data +covered by this licence or while distributing data, the Licensee shall: + - mention the name of the Licensor, the name of the dataset(s) and the time + when the National Land Survey has delivered the dataset(s) (e.g.: contains + data from the National Land Survey of Finland Topographic Database 06/2012) + - provide a copy of this licence or a link to it, as well as + - require third parties to provide the same information when granting rights + to copies of dataset(s) or products and services containing such data and + - remove the name of the Licensor from the product or service, if required to + do so by the Licensor. + +The terms of this licence do not allow the Licensee to state in conjunction +with the use of dataset(s) that the Licensor supports or recommends such use. + +2.3. Duties and responsibilities of the Licensor + +The Licensor shall ensure that + - the Licensor has the right to grant rights to the dataset(s) in accordance + with this licence. + +The data has been licensed "as is" and the Licensor + - shall not be held responsible for any errors or omissions in the data, + disclaims any warranty for the validity or up to date status of the data and + shall be free from liability for direct or consequential damages arising + from the use of data provided by the Licensor, + - and is not obligated to ensure the continuous availability of the data, nor + to announce in advance the interruption or cessation of availability, and + the Licensor shall be free from liability for direct or consequential + damages arising from any such interruption or cessation. + +3. Jurisdiction + +Finnish law shall apply to this licence. + +4. Changes to this licence + +The Licensor may at any time change the terms of the licence or apply a +different licence to the data. The terms of this licence shall, however, still +apply to such data that has been received prior to the change of the terms of +the licence or the licence itself. diff --git a/examples/datavisualization/texturesurface/main.cpp b/examples/datavisualization/texturesurface/main.cpp new file mode 100644 index 00000000..ed1a9be4 --- /dev/null +++ b/examples/datavisualization/texturesurface/main.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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 "surfacegraph.h" + +#include <QtWidgets/QApplication> +#include <QtWidgets/QWidget> +#include <QtWidgets/QHBoxLayout> +#include <QtWidgets/QVBoxLayout> +#include <QtWidgets/QGroupBox> +#include <QtWidgets/QCheckBox> +#include <QtWidgets/QLabel> +#include <QtGui/QScreen> +#include <QtGui/QPainter> + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + Q3DSurface *graph = new Q3DSurface(); + QWidget *container = QWidget::createWindowContainer(graph); + + QSize screenSize = graph->screen()->size(); + container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.6)); + 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); + vLayout->setAlignment(Qt::AlignTop); + + widget->setWindowTitle(QStringLiteral("Textured surface example")); + + QCheckBox *enableTexture = new QCheckBox(widget); + enableTexture->setText(QStringLiteral("Surface texture")); + + int height = 400; + int width = 100; + int border = 10; + QLinearGradient gr(0, 0, 1, height - 2 * border); + gr.setColorAt(1.0f, Qt::black); + gr.setColorAt(0.8f, Qt::darkGreen); + gr.setColorAt(0.6f, Qt::green); + gr.setColorAt(0.4f, Qt::yellow); + gr.setColorAt(0.2f, Qt::red); + gr.setColorAt(0.0f, Qt::darkRed); + + QPixmap pm(width, height); + pm.fill(Qt::transparent); + QPainter pmp(&pm); + pmp.setBrush(QBrush(gr)); + pmp.setPen(Qt::NoPen); + pmp.drawRect(border, border, 35, height - 2 * border); + pmp.setPen(Qt::black); + int step = (height - 2 * border) / 5; + for (int i = 0; i < 6; i++) { + int yPos = i * step + border; + pmp.drawLine(border, yPos, 55, yPos); + pmp.drawText(60, yPos + 2, QString("%1 m").arg(550 - (i * 110))); + } + + QLabel *label = new QLabel(widget); + label->setPixmap(pm); + + QGroupBox *heightMapGroupBox = new QGroupBox(QStringLiteral("Height color map")); + QVBoxLayout *colorMapVBox = new QVBoxLayout; + colorMapVBox->addWidget(label); + heightMapGroupBox->setLayout(colorMapVBox); + + vLayout->addWidget(enableTexture); + vLayout->addWidget(heightMapGroupBox); + + widget->show(); + + SurfaceGraph *modifier = new SurfaceGraph(graph); + + QObject::connect(enableTexture, &QCheckBox::stateChanged, + modifier, &SurfaceGraph::toggleSurfaceTexture); + + enableTexture->setChecked(true); + + return app.exec(); +} diff --git a/examples/datavisualization/texturesurface/maptexture.jpg b/examples/datavisualization/texturesurface/maptexture.jpg Binary files differnew file mode 100644 index 00000000..ae5d66eb --- /dev/null +++ b/examples/datavisualization/texturesurface/maptexture.jpg diff --git a/examples/datavisualization/texturesurface/surfacegraph.cpp b/examples/datavisualization/texturesurface/surfacegraph.cpp new file mode 100644 index 00000000..e01a329d --- /dev/null +++ b/examples/datavisualization/texturesurface/surfacegraph.cpp @@ -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 +** +****************************************************************************/ + +#include "surfacegraph.h" +#include "topographicseries.h" + +#include <QtDataVisualization/QValue3DAxis> +#include <QtDataVisualization/Q3DTheme> + +using namespace QtDataVisualization; + +const float areaWidth = 8000.0f; +const float areaHeight = 8000.0f; +const float aspectRatio = 0.1389f; +const float minRange = areaWidth * 0.49f; + +SurfaceGraph::SurfaceGraph(Q3DSurface *surface) + : m_graph(surface) +{ + m_graph->setAxisX(new QValue3DAxis); + m_graph->setAxisY(new QValue3DAxis); + m_graph->setAxisZ(new QValue3DAxis); + m_graph->axisX()->setLabelFormat("%i"); + m_graph->axisZ()->setLabelFormat("%i"); + m_graph->axisX()->setRange(0.0f, areaWidth); + m_graph->axisY()->setRange(100.0f, areaWidth * aspectRatio); + m_graph->axisZ()->setRange(0.0f, areaHeight); + m_graph->axisX()->setLabelAutoRotation(30); + m_graph->axisY()->setLabelAutoRotation(90); + m_graph->axisZ()->setLabelAutoRotation(30); + m_graph->activeTheme()->setType(Q3DTheme::ThemePrimaryColors); + + QFont font = m_graph->activeTheme()->font(); + font.setPointSize(20); + m_graph->activeTheme()->setFont(font); + + m_topography = new TopographicSeries(); + m_topography->setTopographyFile(":/maps/topography", areaWidth, areaHeight); + m_topography->setItemLabelFormat(QStringLiteral("@yLabel m")); + + m_highlight = new HighlightSeries(); + m_highlight->setTopographicSeries(m_topography); + m_highlight->setMinHeight(minRange * aspectRatio); + m_highlight->handleGradientChange(areaWidth * aspectRatio); +//! [1] + QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged, + m_highlight, &HighlightSeries::handleGradientChange); +//! [1] + + m_graph->addSeries(m_topography); + m_graph->addSeries(m_highlight); + + m_inputHandler = new CustomInputHandler(m_graph); + m_inputHandler->setHighlightSeries(m_highlight); + m_inputHandler->setAxes(m_graph->axisX(), m_graph->axisY(), m_graph->axisZ()); + m_inputHandler->setLimits(0.0f, areaWidth, minRange); + m_inputHandler->setAspectRatio(aspectRatio); + + m_graph->setActiveInputHandler(m_inputHandler); +} + +SurfaceGraph::~SurfaceGraph() +{ + delete m_graph; +} + +//! [0] +void SurfaceGraph::toggleSurfaceTexture(bool enable) +{ + if (enable) + m_topography->setTextureFile(":/maps/maptexture"); + else + m_topography->setTextureFile(""); +} +//! [0] diff --git a/examples/datavisualization/texturesurface/surfacegraph.h b/examples/datavisualization/texturesurface/surfacegraph.h new file mode 100644 index 00000000..c1d81595 --- /dev/null +++ b/examples/datavisualization/texturesurface/surfacegraph.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 SURFACEGRAPH_H +#define SURFACEGRAPH_H + +#include <QtDataVisualization/Q3DSurface> +#include <QtDataVisualization/QSurface3DSeries> +#include <QtWidgets/QSlider> +#include "topographicseries.h" +#include "highlightseries.h" + +#include "custominputhandler.h" + +using namespace QtDataVisualization; + +class SurfaceGraph : public QObject +{ + Q_OBJECT +public: + explicit SurfaceGraph(Q3DSurface *surface); + ~SurfaceGraph(); + + void toggleSurfaceTexture(bool enable); + +private: + Q3DSurface *m_graph; + + TopographicSeries *m_topography; + HighlightSeries *m_highlight; + int m_highlightWidth; + int m_highlightHeight; + + CustomInputHandler *m_inputHandler; +}; + +#endif // SURFACEGRAPH_H diff --git a/examples/datavisualization/texturesurface/texturedsurface.qrc b/examples/datavisualization/texturesurface/texturedsurface.qrc new file mode 100644 index 00000000..94b96d24 --- /dev/null +++ b/examples/datavisualization/texturesurface/texturedsurface.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/maps"> + <file alias="topography">topography.png</file> + <file alias="maptexture">maptexture.jpg</file> + </qresource> +</RCC> diff --git a/examples/datavisualization/texturesurface/texturesurface.pro b/examples/datavisualization/texturesurface/texturesurface.pro new file mode 100644 index 00000000..f24c3d17 --- /dev/null +++ b/examples/datavisualization/texturesurface/texturesurface.pro @@ -0,0 +1,25 @@ +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 \ + surfacegraph.cpp \ + topographicseries.cpp \ + highlightseries.cpp \ + custominputhandler.cpp + +HEADERS += surfacegraph.h \ + topographicseries.h \ + highlightseries.h \ + custominputhandler.h + +QT += widgets + +RESOURCES += texturedsurface.qrc + +OTHER_FILES += doc/src/* \ + doc/images/* diff --git a/examples/datavisualization/texturesurface/topographicseries.cpp b/examples/datavisualization/texturesurface/topographicseries.cpp new file mode 100644 index 00000000..530e56b4 --- /dev/null +++ b/examples/datavisualization/texturesurface/topographicseries.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 "topographicseries.h" + +using namespace QtDataVisualization; + +//! [0] +// Value used to encode height data as RGB value on PNG file +const float packingFactor = 11983.0f; +//! [0] + +TopographicSeries::TopographicSeries() +{ + setDrawMode(QSurface3DSeries::DrawSurface); + setFlatShadingEnabled(true); +} + +TopographicSeries::~TopographicSeries() +{ +} + +void TopographicSeries::setTopographyFile(const QString file, float width, float height) +{ +//! [1] + QImage heightMapImage(file); + uchar *bits = heightMapImage.bits(); + int imageHeight = heightMapImage.height(); + int imageWidth = heightMapImage.width(); + int widthBits = imageWidth * 4; + float stepX = width / float(imageWidth); + float stepZ = height / float(imageHeight); + + QSurfaceDataArray *dataArray = new QSurfaceDataArray; + dataArray->reserve(imageHeight); + for (int i = 0; i < imageHeight; i++) { + int p = i * widthBits; + float z = height - float(i) * stepZ; + QSurfaceDataRow *newRow = new QSurfaceDataRow(imageWidth); + for (int j = 0; j < imageWidth; j++) { + uchar aa = bits[p + 0]; + uchar rr = bits[p + 1]; + uchar gg = bits[p + 2]; + uint color = uint((gg << 16) + (rr << 8) + aa); + float y = float(color) / packingFactor; + (*newRow)[j].setPosition(QVector3D(float(j) * stepX, y, z)); + p = p + 4; + } + *dataArray << newRow; + } + + dataProxy()->resetArray(dataArray); +//! [1] + + m_sampleCountX = float(imageWidth); + m_sampleCountZ = float(imageHeight); +} diff --git a/examples/datavisualization/texturesurface/topographicseries.h b/examples/datavisualization/texturesurface/topographicseries.h new file mode 100644 index 00000000..06530c63 --- /dev/null +++ b/examples/datavisualization/texturesurface/topographicseries.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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 TOPOGRAPHICSERIES_H +#define TOPOGRAPHICSERIES_H + +#include <QtDataVisualization/QSurface3DSeries> + +using namespace QtDataVisualization; + +class TopographicSeries : public QSurface3DSeries +{ + Q_OBJECT +public: + explicit TopographicSeries(); + ~TopographicSeries(); + + void setTopographyFile(const QString file, float width, float height); + + float sampleCountX() { return m_sampleCountX; } + float sampleCountZ() { return m_sampleCountZ; } + +public slots: + +private: + float m_sampleCountX; + float m_sampleCountZ; +}; + +#endif // TOPOGRAPHICSERIES_H diff --git a/examples/datavisualization/texturesurface/topography.png b/examples/datavisualization/texturesurface/topography.png Binary files differnew file mode 100644 index 00000000..9349cdb3 --- /dev/null +++ b/examples/datavisualization/texturesurface/topography.png diff --git a/examples/datavisualization/volumetric/doc/images/volumetric-example.png b/examples/datavisualization/volumetric/doc/images/volumetric-example.png Binary files differnew file mode 100644 index 00000000..277d4fe4 --- /dev/null +++ b/examples/datavisualization/volumetric/doc/images/volumetric-example.png diff --git a/examples/datavisualization/volumetric/doc/src/volumetric.qdoc b/examples/datavisualization/volumetric/doc/src/volumetric.qdoc new file mode 100644 index 00000000..39616670 --- /dev/null +++ b/examples/datavisualization/volumetric/doc/src/volumetric.qdoc @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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. + \since QtDataVisualization 1.2 + + This example shows how to use QCustom3DVolume items to display volumetric data. + + \image volumetric-example.png + + \section1 Initializing volume item + + The QCustom3DVolume items are special custom items (see QCustom3DItem), which can be used + to display volumetric data. The volume items are only supported with orthographic projection, + so first we make sure the graph is using it: + + \snippet volumetric/volumetric.cpp 6 + + The following code shows how to create a volumetric item tied to the data ranges of the axes: + + \snippet volumetric/volumetric.cpp 0 + + By setting the QCustom3DItem::scalingAbsolute property to \c{false}, we indicate that the + scaling of the volume should follow the changes in the data ranges. Next we define the + internal contents of the volume: + + \snippet volumetric/volumetric.cpp 1 + + We use eight bit indexed color for our texture, as it is compact and makes it easy to adjust the + colors without needing to reset the whole texture. For the texture data we use the data we + created earlier based on some height maps. + Typically the data for volume items comes pregenerated in a form of a stack of images, so we are + not going to explain the data generation in detail. Please refer to the example code if you + are interested in the actual data generation process. + + Since we are using eight bit indexed colors, we need a color table to map the eight bit color + indexes to actual colors. We use one we populated on our own, but in a typical use case you + would get the color table from the source images: + + \snippet volumetric/volumetric.cpp 2 + + We want to optionally show slice frames around the volume, so we initialize their properties. + Initially, the frames will be hidden: + + \snippet volumetric/volumetric.cpp 5 + + Finally we add the volume as a custom item to the graph to display it: + + \snippet volumetric/volumetric.cpp 3 + + \section1 Slicing into the volume + + Unless the volume is largely transparent, you can only see the surface of it, which is often + not very helpful. One way to inspect the internal structure of the volume is to view the slices + of the volume. QCustom3DVolume provides two ways to display the slices. The first is to show + the selected slices in place of the volume. For example, to specify a slice perpendicular to + the X-axis, you can use the following method: + + \snippet volumetric/volumetric.cpp 7 + + To actually draw the slice specified above, the QCustom3DVolume::drawSlices property must be + also set: + + \snippet volumetric/volumetric.cpp 8 + + The second way to view slices is to use QCustom3DVolume::renderSlice() method, which produces + a QImage from the specified slice. This image can then be displayed on another widget, such + as a QLabel here: + + \snippet volumetric/volumetric.cpp 9 + + \section1 Adjusting volume transparency + + Sometimes viewing just the slices doesn't give you a good understanding of the volume's internal + structure. QCustom3DVolume provides two properties that can be used to adjust the volume + transparency: + + \snippet volumetric/volumetric.cpp 11 + \dots + \snippet volumetric/volumetric.cpp 10 + + The QCustom3DVolume::alphaMultiplier is a general multiplier that is applied to the alpha value + of each voxel of the volume. It makes it possible to add uniform transparency to the already + somewhat transparent portions of the volume to reveal internal opaque details. This multiplier + doesn't affect colors that are fully opaque, unless the QCustom3DVolume::preserveOpacity + property is set to \c{false}. + + An alternative way to adjust the transparency of the volume is adjust the alpha values of the + voxels directly. For eight bit indexed textures, this is done simply by modifying and + resetting the color table: + + \snippet volumetric/volumetric.cpp 12 + + \section1 High definition vs. low definition shader + + By default the volume rendering uses the high definition shader. It accounts for each + voxel of the volume with correct weight when ray-tracing the volume contents, + providing an accurate representation of even the finer details of the volume. + However, this is computationally very expensive, so the frame rate suffers. + If rendering speed is more important than pixel-perfect + accuracy of the volume contents, you can take the much faster low definition shader into use + by setting \c{false} for QCustom3DVolume::useHighDefShader property. The low definition shader + achieves the speed by making compromises on the accuracy, so it doesn't guarantee each voxel + of the volume will be sampled. This can lead to flickering and/or other rendering artifacts + on the finer details of the volume. + + \snippet volumetric/volumetric.cpp 13 + + \section1 Example contents +*/ diff --git a/examples/datavisualization/volumetric/layer_ground.png b/examples/datavisualization/volumetric/layer_ground.png Binary files differnew file mode 100644 index 00000000..3f96a122 --- /dev/null +++ b/examples/datavisualization/volumetric/layer_ground.png diff --git a/examples/datavisualization/volumetric/layer_magma.png b/examples/datavisualization/volumetric/layer_magma.png Binary files differnew file mode 100644 index 00000000..01434d35 --- /dev/null +++ b/examples/datavisualization/volumetric/layer_magma.png diff --git a/examples/datavisualization/volumetric/layer_water.png b/examples/datavisualization/volumetric/layer_water.png Binary files differnew file mode 100644 index 00000000..4d57563e --- /dev/null +++ b/examples/datavisualization/volumetric/layer_water.png diff --git a/examples/datavisualization/volumetric/main.cpp b/examples/datavisualization/volumetric/main.cpp new file mode 100644 index 00000000..faf379ec --- /dev/null +++ b/examples/datavisualization/volumetric/main.cpp @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** 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 <QtWidgets/QApplication> +#include <QtWidgets/QWidget> +#include <QtWidgets/QHBoxLayout> +#include <QtWidgets/QVBoxLayout> +#include <QtWidgets/QRadioButton> +#include <QtWidgets/QSlider> +#include <QtWidgets/QCheckBox> +#include <QtWidgets/QLabel> +#include <QtWidgets/QGroupBox> +#include <QtGui/QScreen> + +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() / 3, screenSize.height() / 3)); + 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(); + QVBoxLayout *vLayout2 = new QVBoxLayout(); + hLayout->addWidget(container, 1); + hLayout->addLayout(vLayout); + hLayout->addLayout(vLayout2); + + widget->setWindowTitle(QStringLiteral("Volumetric object example - 3D terrain")); + + 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); + + QCheckBox *fpsCheckBox = new QCheckBox(widget); + fpsCheckBox->setText(QStringLiteral("Show FPS")); + fpsCheckBox->setChecked(false); + QLabel *fpsLabel = new QLabel(QStringLiteral(""), widget); + + QGroupBox *textureDetailGroupBox = new QGroupBox(QStringLiteral("Texture detail")); + + QRadioButton *lowDetailRB = new QRadioButton(widget); + lowDetailRB->setText(QStringLiteral("Low (128x64x128)")); + lowDetailRB->setChecked(true); + + QRadioButton *mediumDetailRB = new QRadioButton(widget); + mediumDetailRB->setText(QStringLiteral("Generating...")); + mediumDetailRB->setChecked(false); + mediumDetailRB->setEnabled(false); + + QRadioButton *highDetailRB = new QRadioButton(widget); + highDetailRB->setText(QStringLiteral("Generating...")); + highDetailRB->setChecked(false); + highDetailRB->setEnabled(false); + + QVBoxLayout *textureDetailVBox = new QVBoxLayout; + textureDetailVBox->addWidget(lowDetailRB); + textureDetailVBox->addWidget(mediumDetailRB); + textureDetailVBox->addWidget(highDetailRB); + textureDetailGroupBox->setLayout(textureDetailVBox); + + QGroupBox *areaGroupBox = new QGroupBox(QStringLiteral("Show area")); + + QRadioButton *areaAllRB = new QRadioButton(widget); + areaAllRB->setText(QStringLiteral("Whole region")); + areaAllRB->setChecked(true); + + QRadioButton *areaMineRB = new QRadioButton(widget); + areaMineRB->setText(QStringLiteral("The mine")); + areaMineRB->setChecked(false); + + QRadioButton *areaMountainRB = new QRadioButton(widget); + areaMountainRB->setText(QStringLiteral("The mountain")); + areaMountainRB->setChecked(false); + + QVBoxLayout *areaVBox = new QVBoxLayout; + areaVBox->addWidget(areaAllRB); + areaVBox->addWidget(areaMineRB); + areaVBox->addWidget(areaMountainRB); + areaGroupBox->setLayout(areaVBox); + + QCheckBox *colorTableCheckBox = new QCheckBox(widget); + colorTableCheckBox->setText(QStringLiteral("Alternate color table")); + colorTableCheckBox->setChecked(false); + + QLabel *sliceImageXLabel = new QLabel(widget); + QLabel *sliceImageYLabel = new QLabel(widget); + QLabel *sliceImageZLabel = new QLabel(widget); + sliceImageXLabel->setMinimumSize(QSize(200, 100)); + sliceImageYLabel->setMinimumSize(QSize(200, 200)); + sliceImageZLabel->setMinimumSize(QSize(200, 100)); + sliceImageXLabel->setMaximumSize(QSize(200, 100)); + sliceImageYLabel->setMaximumSize(QSize(200, 200)); + sliceImageZLabel->setMaximumSize(QSize(200, 100)); + sliceImageXLabel->setFrameShape(QFrame::Box); + sliceImageYLabel->setFrameShape(QFrame::Box); + sliceImageZLabel->setFrameShape(QFrame::Box); + sliceImageXLabel->setScaledContents(true); + sliceImageYLabel->setScaledContents(true); + sliceImageZLabel->setScaledContents(true); + + QSlider *alphaMultiplierSlider = new QSlider(Qt::Horizontal, widget); + alphaMultiplierSlider->setMinimum(0); + alphaMultiplierSlider->setMaximum(139); + alphaMultiplierSlider->setValue(100); + alphaMultiplierSlider->setEnabled(true); + QLabel *alphaMultiplierLabel = new QLabel(QStringLiteral("Alpha multiplier: 1.0")); + + QCheckBox *preserveOpacityCheckBox = new QCheckBox(widget); + preserveOpacityCheckBox->setText(QStringLiteral("Preserve opacity")); + preserveOpacityCheckBox->setChecked(true); + + QCheckBox *transparentGroundCheckBox = new QCheckBox(widget); + transparentGroundCheckBox->setText(QStringLiteral("Transparent ground")); + transparentGroundCheckBox->setChecked(false); + + QCheckBox *useHighDefShaderCheckBox = new QCheckBox(widget); + useHighDefShaderCheckBox->setText(QStringLiteral("Use HD shader")); + useHighDefShaderCheckBox->setChecked(true); + + QLabel *performanceNoteLabel = + new QLabel(QStringLiteral( + "Note: A high end graphics card is\nrecommended with the HD shader\nwhen the volume contains a lot of\ntransparent areas.")); + performanceNoteLabel->setFrameShape(QFrame::Box); + + QCheckBox *drawSliceFramesCheckBox = new QCheckBox(widget); + drawSliceFramesCheckBox->setText(QStringLiteral("Draw slice frames")); + drawSliceFramesCheckBox->setChecked(false); + + vLayout->addWidget(sliceXCheckBox); + vLayout->addWidget(sliceXSlider); + vLayout->addWidget(sliceImageXLabel); + vLayout->addWidget(sliceYCheckBox); + vLayout->addWidget(sliceYSlider); + vLayout->addWidget(sliceImageYLabel); + vLayout->addWidget(sliceZCheckBox); + vLayout->addWidget(sliceZSlider); + vLayout->addWidget(sliceImageZLabel); + vLayout->addWidget(drawSliceFramesCheckBox, 1, Qt::AlignTop); + vLayout2->addWidget(fpsCheckBox); + vLayout2->addWidget(fpsLabel); + vLayout2->addWidget(textureDetailGroupBox); + vLayout2->addWidget(areaGroupBox); + vLayout2->addWidget(colorTableCheckBox); + vLayout2->addWidget(alphaMultiplierLabel); + vLayout2->addWidget(alphaMultiplierSlider); + vLayout2->addWidget(preserveOpacityCheckBox); + vLayout2->addWidget(transparentGroundCheckBox); + vLayout2->addWidget(useHighDefShaderCheckBox); + vLayout2->addWidget(performanceNoteLabel, 1, Qt::AlignTop); + + VolumetricModifier *modifier = new VolumetricModifier(graph); + modifier->setFpsLabel(fpsLabel); + modifier->setMediumDetailRB(mediumDetailRB); + modifier->setHighDetailRB(highDetailRB); + modifier->setSliceSliders(sliceXSlider, sliceYSlider, sliceZSlider); + modifier->setSliceLabels(sliceImageXLabel, sliceImageYLabel, sliceImageZLabel); + modifier->setAlphaMultiplierLabel(alphaMultiplierLabel); + modifier->setTransparentGround(transparentGroundCheckBox->isChecked()); + + QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setFpsMeasurement); + 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); + QObject::connect(lowDetailRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleLowDetail); + QObject::connect(mediumDetailRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleMediumDetail); + QObject::connect(highDetailRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleHighDetail); + QObject::connect(colorTableCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::changeColorTable); + QObject::connect(preserveOpacityCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setPreserveOpacity); + QObject::connect(transparentGroundCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setTransparentGround); + QObject::connect(useHighDefShaderCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setUseHighDefShader); + QObject::connect(alphaMultiplierSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustAlphaMultiplier); + QObject::connect(areaAllRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleAreaAll); + QObject::connect(areaMineRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleAreaMine); + QObject::connect(areaMountainRB, &QRadioButton::toggled, modifier, + &VolumetricModifier::toggleAreaMountain); + QObject::connect(drawSliceFramesCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::setDrawSliceFrames); + + 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..20338598 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.cpp @@ -0,0 +1,764 @@ +/**************************************************************************** +** +** 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 <QtDataVisualization/qvalue3daxis.h> +#include <QtDataVisualization/q3dscene.h> +#include <QtDataVisualization/q3dcamera.h> +#include <QtDataVisualization/q3dtheme.h> +#include <QtDataVisualization/qcustom3dlabel.h> +#include <QtDataVisualization/q3dscatter.h> +#include <QtDataVisualization/q3dinputhandler.h> +#include <QtCore/qmath.h> +#include <QtWidgets/QLabel> +#include <QtWidgets/QRadioButton> +#include <QtWidgets/QSlider> +#include <QtCore/QDebug> +#include <QtGui/QOpenGLContext> + +using namespace QtDataVisualization; + +const int lowDetailSize(128); +const int mediumDetailSize(256); +const int highDetailSize(512); +const int colorTableSize(256); +const int layerDataSize(512); +const int mineShaftDiameter(1); + +const int airColorIndex(254); +const int mineShaftColorIndex(255); +const int layerColorThickness(60); +const int heightToColorDiv(140); +const int magmaColorsMin(0); +const int magmaColorsMax(layerColorThickness); +const int aboveWaterGroundColorsMin(magmaColorsMax + 1); +const int aboveWaterGroundColorsMax(aboveWaterGroundColorsMin + layerColorThickness); +const int underWaterGroundColorsMin(aboveWaterGroundColorsMax + 1); +const int underWaterGroundColorsMax(underWaterGroundColorsMin + layerColorThickness); +const int waterColorsMin(underWaterGroundColorsMax + 1); +const int waterColorsMax(waterColorsMin + layerColorThickness); +const int terrainTransparency(12); + +static bool isOpenGLES() +{ +#if defined(QT_OPENGL_ES_2) + return true; +#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) + return false; +#else + return QOpenGLContext::currentContext()->isOpenGLES(); +#endif +} + +VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) + : m_graph(scatter), + m_volumeItem(0), + m_sliceIndexX(lowDetailSize / 2), + m_sliceIndexY(lowDetailSize / 4), + m_sliceIndexZ(lowDetailSize / 2), + m_slicingX(false), + m_slicingY(false), + m_slicingZ(false), + m_mediumDetailRB(0), + m_highDetailRB(0), + m_lowDetailData(0), + m_mediumDetailData(0), + m_highDetailData(0), + m_mediumDetailIndex(0), + m_highDetailIndex(0), + m_mediumDetailShaftIndex(0), + m_highDetailShaftIndex(0), + m_sliceSliderX(0), + m_sliceSliderY(0), + m_sliceSliderZ(0), + m_usingPrimaryTable(true), + m_sliceLabelX(0), + m_sliceLabelY(0), + m_sliceLabelZ(0) +{ + m_graph->activeTheme()->setType(Q3DTheme::ThemeQt); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone); + m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); + //! [6] + m_graph->setOrthoProjection(true); + //! [6] + m_graph->activeTheme()->setBackgroundEnabled(false); + + // Only allow zooming at the center and limit the zoom to 200% to avoid clipping issues + static_cast<Q3DInputHandler *>(m_graph->activeInputHandler())->setZoomAtTargetEnabled(false); + m_graph->scene()->activeCamera()->setMaxZoomLevel(200.0f); + + toggleAreaAll(true); + + if (!isOpenGLES()) { + m_lowDetailData = new QVector<uchar>(lowDetailSize * lowDetailSize * lowDetailSize / 2); + m_mediumDetailData = new QVector<uchar>(mediumDetailSize * mediumDetailSize * mediumDetailSize / 2); + m_highDetailData = new QVector<uchar>(highDetailSize * highDetailSize * highDetailSize / 2); + + initHeightMap(QStringLiteral(":/heightmaps/layer_ground.png"), m_groundLayer); + initHeightMap(QStringLiteral(":/heightmaps/layer_water.png"), m_waterLayer); + initHeightMap(QStringLiteral(":/heightmaps/layer_magma.png"), m_magmaLayer); + + initMineShaftArray(); + + createVolume(lowDetailSize, 0, lowDetailSize, m_lowDetailData); + excavateMineShaft(lowDetailSize, 0, m_mineShaftArray.size(), m_lowDetailData); + + //! [0] + m_volumeItem = new QCustom3DVolume; + // Adjust water level to zero with a minor tweak to y-coordinate position and scaling + m_volumeItem->setScaling( + QVector3D(m_graph->axisX()->max() - m_graph->axisX()->min(), + (m_graph->axisY()->max() - m_graph->axisY()->min()) * 0.91f, + m_graph->axisZ()->max() - m_graph->axisZ()->min())); + m_volumeItem->setPosition( + QVector3D((m_graph->axisX()->max() + m_graph->axisX()->min()) / 2.0f, + -0.045f * (m_graph->axisY()->max() - m_graph->axisY()->min()) + + (m_graph->axisY()->max() + m_graph->axisY()->min()) / 2.0f, + (m_graph->axisZ()->max() + m_graph->axisZ()->min()) / 2.0f)); + m_volumeItem->setScalingAbsolute(false); + //! [0] + //! [1] + m_volumeItem->setTextureWidth(lowDetailSize); + m_volumeItem->setTextureHeight(lowDetailSize / 2); + m_volumeItem->setTextureDepth(lowDetailSize); + m_volumeItem->setTextureFormat(QImage::Format_Indexed8); + m_volumeItem->setTextureData(new QVector<uchar>(*m_lowDetailData)); + //! [1] + + // Generate color tables. + m_colorTable1.resize(colorTableSize); + m_colorTable2.resize(colorTableSize); + + for (int i = 0; i < colorTableSize - 2; i++) { + if (i < magmaColorsMax) { + m_colorTable1[i] = qRgba(130 - (i * 2), 0, 0, 255); + } else if (i < aboveWaterGroundColorsMax) { + m_colorTable1[i] = qRgba((i - magmaColorsMax) * 4, + ((i - magmaColorsMax) * 2) + 120, + (i - magmaColorsMax) * 5, terrainTransparency); + } else if (i < underWaterGroundColorsMax) { + m_colorTable1[i] = qRgba(((layerColorThickness - i - aboveWaterGroundColorsMax)) + 70, + ((layerColorThickness - i - aboveWaterGroundColorsMax) * 2) + 20, + ((layerColorThickness - i - aboveWaterGroundColorsMax)) + 50, + terrainTransparency); + } else if (i < waterColorsMax) { + m_colorTable1[i] = qRgba(0, 0, ((i - underWaterGroundColorsMax) * 2) + 120, + terrainTransparency); + } else { + m_colorTable1[i] = qRgba(0, 0, 0, 0); // Not used + } + } + m_colorTable1[airColorIndex] = qRgba(0, 0, 0, 0); + m_colorTable1[mineShaftColorIndex] = qRgba(50, 50, 50, 255); + + // The alternate color table just has gray gradients for all terrain except water + for (int i = 0; i < colorTableSize - 2; i++) { + if (i < magmaColorsMax) { + m_colorTable2[i] = qRgba(((i - aboveWaterGroundColorsMax) * 2), + ((i - aboveWaterGroundColorsMax) * 2), + ((i - aboveWaterGroundColorsMax) * 2), 255); + } else if (i < underWaterGroundColorsMax) { + m_colorTable2[i] = qRgba(((i - aboveWaterGroundColorsMax) * 2), + ((i - aboveWaterGroundColorsMax) * 2), + ((i - aboveWaterGroundColorsMax) * 2), terrainTransparency); + } else if (i < waterColorsMax) { + m_colorTable2[i] = qRgba(0, 0, ((i - underWaterGroundColorsMax) * 2) + 120, + terrainTransparency); + } else { + m_colorTable2[i] = qRgba(0, 0, 0, 0); // Not used + } + } + m_colorTable2[airColorIndex] = qRgba(0, 0, 0, 0); + m_colorTable2[mineShaftColorIndex] = qRgba(255, 255, 0, 255); + + //! [2] + m_volumeItem->setColorTable(m_colorTable1); + //! [2] + + //! [5] + m_volumeItem->setSliceFrameGaps(QVector3D(0.01f, 0.02f, 0.01f)); + m_volumeItem->setSliceFrameThicknesses(QVector3D(0.0025f, 0.005f, 0.0025f)); + m_volumeItem->setSliceFrameWidths(QVector3D(0.0025f, 0.005f, 0.0025f)); + m_volumeItem->setDrawSliceFrames(false); + //! [5] + handleSlicingChanges(); + + //! [3] + m_graph->addCustomItem(m_volumeItem); + //! [3] + + m_timer.start(0); + } else { + // OpenGL ES2 doesn't support 3D textures, so show a warning label instead + QCustom3DLabel *warningLabel = new QCustom3DLabel( + "QCustom3DVolume is not supported with OpenGL ES2", + QFont(), + QVector3D(0.0f, 0.5f, 0.0f), + QVector3D(1.5f, 1.5f, 0.0f), + QQuaternion()); + warningLabel->setPositionAbsolute(true); + warningLabel->setFacingCamera(true); + m_graph->addCustomItem(warningLabel); + } + + QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, + &VolumetricModifier::handleFpsChange); + QObject::connect(&m_timer, &QTimer::timeout, this, + &VolumetricModifier::handleTimeout); + +} + +VolumetricModifier::~VolumetricModifier() +{ + delete m_graph; +} + +void VolumetricModifier::setFpsLabel(QLabel *fpsLabel) +{ + m_fpsLabel = fpsLabel; +} + +void VolumetricModifier::setMediumDetailRB(QRadioButton *button) +{ + m_mediumDetailRB = button; +} + +void VolumetricModifier::setHighDetailRB(QRadioButton *button) +{ + m_highDetailRB = button; +} + +void VolumetricModifier::setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel) +{ + m_sliceLabelX = xLabel; + m_sliceLabelY = yLabel; + m_sliceLabelZ = zLabel; + + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); +} + +void VolumetricModifier::setAlphaMultiplierLabel(QLabel *label) +{ + m_alphaMultiplierLabel = label; +} + +void VolumetricModifier::sliceX(int enabled) +{ + m_slicingX = enabled; + handleSlicingChanges(); +} + +void VolumetricModifier::sliceY(int enabled) +{ + m_slicingY = enabled; + handleSlicingChanges(); +} + +void VolumetricModifier::sliceZ(int enabled) +{ + m_slicingZ = enabled; + handleSlicingChanges(); +} + +void VolumetricModifier::adjustSliceX(int value) +{ + if (m_volumeItem) { + m_sliceIndexX = value / (1024 / m_volumeItem->textureWidth()); + if (m_sliceIndexX == m_volumeItem->textureWidth()) + m_sliceIndexX--; + if (m_volumeItem->sliceIndexX() != -1) + //! [7] + m_volumeItem->setSliceIndexX(m_sliceIndexX); + //! [7] + //! [9] + m_sliceLabelX->setPixmap( + QPixmap::fromImage(m_volumeItem->renderSlice(Qt::XAxis, m_sliceIndexX))); + //! [9] + } +} + +void VolumetricModifier::adjustSliceY(int value) +{ + if (m_volumeItem) { + m_sliceIndexY = value / (1024 / m_volumeItem->textureHeight()); + if (m_sliceIndexY == m_volumeItem->textureHeight()) + m_sliceIndexY--; + if (m_volumeItem->sliceIndexY() != -1) + m_volumeItem->setSliceIndexY(m_sliceIndexY); + m_sliceLabelY->setPixmap( + QPixmap::fromImage(m_volumeItem->renderSlice(Qt::YAxis, m_sliceIndexY))); + } +} + +void VolumetricModifier::adjustSliceZ(int value) +{ + if (m_volumeItem) { + m_sliceIndexZ = value / (1024 / m_volumeItem->textureDepth()); + if (m_sliceIndexZ == m_volumeItem->textureDepth()) + m_sliceIndexZ--; + if (m_volumeItem->sliceIndexZ() != -1) + m_volumeItem->setSliceIndexZ(m_sliceIndexZ); + m_sliceLabelZ->setPixmap( + QPixmap::fromImage(m_volumeItem->renderSlice(Qt::ZAxis, m_sliceIndexZ))); + } +} + +void VolumetricModifier::handleFpsChange(qreal fps) +{ + const QString fpsFormat = QStringLiteral("FPS: %1"); + int fps10 = int(fps * 10.0); + m_fpsLabel->setText(fpsFormat.arg(qreal(fps10) / 10.0)); +} + +void VolumetricModifier::handleTimeout() +{ + if (!m_mediumDetailRB->isEnabled()) { + if (m_mediumDetailIndex != mediumDetailSize) { + m_mediumDetailIndex = createVolume(mediumDetailSize, m_mediumDetailIndex, 4, + m_mediumDetailData); + } else if (m_mediumDetailShaftIndex != m_mineShaftArray.size()) { + m_mediumDetailShaftIndex = excavateMineShaft(mediumDetailSize, m_mediumDetailShaftIndex, + 1, m_mediumDetailData ); + } else { + m_mediumDetailRB->setEnabled(true); + QString label = QStringLiteral("Medium (%1x%2x%1)"); + m_mediumDetailRB->setText(label.arg(mediumDetailSize).arg(mediumDetailSize / 2)); + } + } else if (!m_highDetailRB->isEnabled()) { + if (m_highDetailIndex != highDetailSize) { + m_highDetailIndex = createVolume(highDetailSize, m_highDetailIndex, 1, + m_highDetailData); + } else if (m_highDetailShaftIndex != m_mineShaftArray.size()) { + m_highDetailShaftIndex = excavateMineShaft(highDetailSize, m_highDetailShaftIndex, 1, + m_highDetailData); + } else { + m_highDetailRB->setEnabled(true); + QString label = QStringLiteral("High (%1x%2x%1)"); + m_highDetailRB->setText(label.arg(highDetailSize).arg(highDetailSize / 2)); + m_timer.stop(); + } + } +} + +void VolumetricModifier::toggleLowDetail(bool enabled) +{ + if (enabled && m_volumeItem) { + m_volumeItem->setTextureData(new QVector<uchar>(*m_lowDetailData)); + m_volumeItem->setTextureDimensions(lowDetailSize, lowDetailSize / 2, lowDetailSize); + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::toggleMediumDetail(bool enabled) +{ + if (enabled && m_volumeItem) { + m_volumeItem->setTextureData(new QVector<uchar>(*m_mediumDetailData)); + m_volumeItem->setTextureDimensions(mediumDetailSize, mediumDetailSize / 2, mediumDetailSize); + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::toggleHighDetail(bool enabled) +{ + if (enabled && m_volumeItem) { + m_volumeItem->setTextureData(new QVector<uchar>(*m_highDetailData)); + m_volumeItem->setTextureDimensions(highDetailSize, highDetailSize / 2, highDetailSize); + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::setFpsMeasurement(bool enabled) +{ + m_graph->setMeasureFps(enabled); + if (enabled) + m_fpsLabel->setText(QStringLiteral("Measuring...")); + else + m_fpsLabel->setText(QString()); +} + +void VolumetricModifier::setSliceSliders(QSlider *sliderX, QSlider *sliderY, QSlider *sliderZ) +{ + m_sliceSliderX = sliderX; + m_sliceSliderY = sliderY; + m_sliceSliderZ = sliderZ; + + // Set sliders to interesting values + m_sliceSliderX->setValue(715); + m_sliceSliderY->setValue(612); + m_sliceSliderZ->setValue(715); +} + +void VolumetricModifier::changeColorTable(int enabled) +{ + if (m_volumeItem) { + if (enabled) + m_volumeItem->setColorTable(m_colorTable2); + else + m_volumeItem->setColorTable(m_colorTable1); + + m_usingPrimaryTable = !enabled; + + // Rerender image labels + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::setPreserveOpacity(bool enabled) +{ + + if (m_volumeItem) { + //! [10] + m_volumeItem->setPreserveOpacity(enabled); + //! [10] + + // Rerender image labels + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::setTransparentGround(bool enabled) +{ + if (m_volumeItem) { + //! [12] + int newAlpha = enabled ? terrainTransparency : 255; + for (int i = aboveWaterGroundColorsMin; i < underWaterGroundColorsMax; i++) { + QRgb oldColor1 = m_colorTable1.at(i); + QRgb oldColor2 = m_colorTable2.at(i); + m_colorTable1[i] = qRgba(qRed(oldColor1), qGreen(oldColor1), qBlue(oldColor1), newAlpha); + m_colorTable2[i] = qRgba(qRed(oldColor2), qGreen(oldColor2), qBlue(oldColor2), newAlpha); + } + if (m_usingPrimaryTable) + m_volumeItem->setColorTable(m_colorTable1); + else + m_volumeItem->setColorTable(m_colorTable2); + //! [12] + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::setUseHighDefShader(bool enabled) +{ + if (m_volumeItem) { + //! [13] + m_volumeItem->setUseHighDefShader(enabled); + //! [13] + } +} + +void VolumetricModifier::adjustAlphaMultiplier(int value) +{ + if (m_volumeItem) { + float mult; + if (value > 100) + mult = float(value - 99) / 2.0f; + else + mult = float(value) / float(500 - value * 4); + //! [11] + m_volumeItem->setAlphaMultiplier(mult); + //! [11] + QString labelFormat = QStringLiteral("Alpha multiplier: %1"); + m_alphaMultiplierLabel->setText(labelFormat.arg( + QString::number(m_volumeItem->alphaMultiplier(), 'f', 3))); + + // Rerender image labels + adjustSliceX(m_sliceSliderX->value()); + adjustSliceY(m_sliceSliderY->value()); + adjustSliceZ(m_sliceSliderZ->value()); + } +} + +void VolumetricModifier::toggleAreaAll(bool enabled) +{ + if (enabled) { + m_graph->axisX()->setRange(0.0f, 1000.0f); + m_graph->axisY()->setRange(-600.0f, 600.0f); + m_graph->axisZ()->setRange(0.0f, 1000.0f); + m_graph->axisX()->setSegmentCount(5); + m_graph->axisY()->setSegmentCount(6); + m_graph->axisZ()->setSegmentCount(5); + } +} + +void VolumetricModifier::toggleAreaMine(bool enabled) +{ + if (enabled) { + m_graph->axisX()->setRange(350.0f, 850.0f); + m_graph->axisY()->setRange(-500.0f, 100.0f); + m_graph->axisZ()->setRange(350.0f, 900.0f); + m_graph->axisX()->setSegmentCount(10); + m_graph->axisY()->setSegmentCount(6); + m_graph->axisZ()->setSegmentCount(11); + } +} + +void VolumetricModifier::toggleAreaMountain(bool enabled) +{ + if (enabled) { + m_graph->axisX()->setRange(300.0f, 600.0f); + m_graph->axisY()->setRange(-100.0f, 400.0f); + m_graph->axisZ()->setRange(300.0f, 600.0f); + m_graph->axisX()->setSegmentCount(9); + m_graph->axisY()->setSegmentCount(5); + m_graph->axisZ()->setSegmentCount(9); + } +} + +void VolumetricModifier::setDrawSliceFrames(int enabled) +{ + if (m_volumeItem) + m_volumeItem->setDrawSliceFrames(enabled); +} + +void VolumetricModifier::initHeightMap(QString fileName, QVector<uchar> &layerData) +{ + QImage heightImage(fileName); + + layerData.resize(layerDataSize * layerDataSize); + const uchar *bits = heightImage.bits(); + int index = 0; + QVector<QRgb> colorTable = heightImage.colorTable(); + for (int i = 0; i < layerDataSize; i++) { + for (int j = 0; j < layerDataSize; j++) { + layerData[index] = qRed(colorTable.at(bits[index])); + index++; + } + } +} + +int VolumetricModifier::createVolume(int textureSize, int startIndex, int count, + QVector<uchar> *textureData) +{ + // Generate volume from layer data. + int index = startIndex * textureSize * textureSize / 2.0f; + int endIndex = startIndex + count; + if (endIndex > textureSize) + endIndex = textureSize; + QVector<uchar> magmaHeights(textureSize); + QVector<uchar> waterHeights(textureSize); + QVector<uchar> groundHeights(textureSize); + float multiplier = float(layerDataSize) / float(textureSize); + for (int i = startIndex; i < endIndex; i++) { + // Generate layer height arrays + for (int l = 0; l < textureSize; l++) { + int layerIndex = (int(i * multiplier) * layerDataSize + int(l * multiplier)); + magmaHeights[l] = int(m_magmaLayer.at(layerIndex)); + waterHeights[l] = int(m_waterLayer.at(layerIndex)); + groundHeights[l] = int(m_groundLayer.at(layerIndex)); + } + for (int j = 0; j < textureSize / 2; j++) { + for (int k = 0; k < textureSize; k++) { + int colorIndex; + int height((layerDataSize - (j * 2 * multiplier)) / 2); + if (height < magmaHeights.at(k)) { + // Magma layer + colorIndex = int((float(height) / heightToColorDiv) + * float(layerColorThickness)) + magmaColorsMin; + } else if (height < groundHeights.at(k) && height < waterHeights.at(k)) { + // Ground layer below water + colorIndex = int((float(waterHeights.at(k) - height) / heightToColorDiv) + * float(layerColorThickness)) + underWaterGroundColorsMin; + } else if (height < waterHeights.at(k)) { + // Water layer where water goes over ground + colorIndex = int((float(height - magmaHeights.at(k)) / heightToColorDiv) + * float(layerColorThickness)) + waterColorsMin; + } else if (height <= groundHeights.at(k)) { + // Ground above water + colorIndex = int((float(height - waterHeights.at(k)) / heightToColorDiv) + * float(layerColorThickness)) + aboveWaterGroundColorsMin; + } else { + // Rest is air + colorIndex = airColorIndex; + } + + (*textureData)[index] = colorIndex; + index++; + } + } + } + return endIndex; +} + +int VolumetricModifier::excavateMineShaft(int textureSize, int startIndex, int count, + QVector<uchar> *textureData) +{ + int endIndex = startIndex + count; + if (endIndex > m_mineShaftArray.size()) + endIndex = m_mineShaftArray.size(); + int shaftSize = mineShaftDiameter * textureSize / lowDetailSize; + for (int i = startIndex; i < endIndex; i++) { + QVector3D shaftStart(m_mineShaftArray.at(i).first); + QVector3D shaftEnd(m_mineShaftArray.at(i).second); + int shaftLen = (shaftEnd - shaftStart).length() * lowDetailSize; + int dataX = shaftStart.x() * textureSize - (shaftSize / 2); + int dataY = (shaftStart.y() * textureSize - (shaftSize / 2)) / 2; + int dataZ = shaftStart.z() * textureSize - (shaftSize / 2); + int dataIndex = dataX + (dataY * textureSize) + dataZ * (textureSize * textureSize / 2); + if (shaftStart.x() != shaftEnd.x()) { + for (int j = 0; j <= shaftLen; j++) { + excavateMineBlock(textureSize, dataIndex, shaftSize, textureData); + dataIndex += shaftSize; + } + } else if (shaftStart.y() != shaftEnd.y()) { + shaftLen /= 2; // Vertical shafts are half as long + for (int j = 0; j <= shaftLen; j++) { + excavateMineBlock(textureSize, dataIndex, shaftSize, textureData); + dataIndex += textureSize * shaftSize; + } + } else { + for (int j = 0; j <= shaftLen; j++) { + excavateMineBlock(textureSize, dataIndex, shaftSize, textureData); + dataIndex += (textureSize * textureSize / 2) * shaftSize; + } + } + + + } + return endIndex; +} + +void VolumetricModifier::excavateMineBlock(int textureSize, int dataIndex, int size, + QVector<uchar> *textureData) +{ + for (int k = 0; k < size; k++) { + int curIndex = dataIndex + (k * textureSize * textureSize / 2); + for (int l = 0; l < size; l++) { + curIndex = dataIndex + (k * textureSize * textureSize / 2) + + (l * textureSize); + for (int m = 0; m < size; m++) { + if (textureData->at(curIndex) != airColorIndex) + (*textureData)[curIndex] = mineShaftColorIndex; + curIndex++; + } + + } + } +} + +void VolumetricModifier::handleSlicingChanges() +{ + if (m_volumeItem) { + if (m_slicingX || m_slicingY || m_slicingZ) { + // Only show slices of selected dimensions + //! [8] + m_volumeItem->setDrawSlices(true); + //! [8] + m_volumeItem->setSliceIndexX(m_slicingX ? m_sliceIndexX : -1); + m_volumeItem->setSliceIndexY(m_slicingY ? m_sliceIndexY : -1); + m_volumeItem->setSliceIndexZ(m_slicingZ ? m_sliceIndexZ : -1); + } else { + // Show slice frames for all dimenstions when not actually slicing + m_volumeItem->setDrawSlices(false); + m_volumeItem->setSliceIndexX(m_sliceIndexX); + m_volumeItem->setSliceIndexY(m_sliceIndexY); + m_volumeItem->setSliceIndexZ(m_sliceIndexZ); + } + } +} + +void VolumetricModifier::initMineShaftArray() +{ + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.1f, 0.7f), + QVector3D(0.7f, 0.8f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.7f, 0.5f), + QVector3D(0.7f, 0.7f, 0.7f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.4f, 0.7f, 0.7f), + QVector3D(0.7f, 0.7f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.4f, 0.7f, 0.7f), + QVector3D(0.4f, 0.7f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.45f, 0.7f, 0.7f), + QVector3D(0.45f, 0.7f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.7f, 0.7f), + QVector3D(0.5f, 0.7f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.55f, 0.7f, 0.7f), + QVector3D(0.55f, 0.7f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.7f), + QVector3D(0.6f, 0.7f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.65f, 0.7f, 0.7f), + QVector3D(0.65f, 0.7f, 0.8f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.6f, 0.7f), + QVector3D(0.7f, 0.6f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.5f, 0.6f, 0.7f), + QVector3D(0.5f, 0.6f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.55f, 0.6f, 0.7f), + QVector3D(0.55f, 0.6f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.7f), + QVector3D(0.6f, 0.6f, 0.8f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.65f, 0.6f, 0.7f), + QVector3D(0.65f, 0.6f, 0.8f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.6f, 0.4f), + QVector3D(0.7f, 0.6f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.45f), + QVector3D(0.8f, 0.6f, 0.45f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.5f), + QVector3D(0.8f, 0.6f, 0.5f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.55f), + QVector3D(0.8f, 0.6f, 0.55f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.6f), + QVector3D(0.8f, 0.6f, 0.6f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.65f), + QVector3D(0.8f, 0.6f, 0.65f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.6f, 0.7f), + QVector3D(0.8f, 0.6f, 0.7f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.7f, 0.4f), + QVector3D(0.7f, 0.7f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.45f), + QVector3D(0.8f, 0.7f, 0.45f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.5f), + QVector3D(0.8f, 0.7f, 0.5f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.55f), + QVector3D(0.8f, 0.7f, 0.55f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.6f), + QVector3D(0.8f, 0.7f, 0.6f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.65f), + QVector3D(0.8f, 0.7f, 0.65f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.7f, 0.7f), + QVector3D(0.8f, 0.7f, 0.7f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.8f, 0.5f), + QVector3D(0.7f, 0.8f, 0.7f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.55f), + QVector3D(0.8f, 0.8f, 0.55f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.6f), + QVector3D(0.8f, 0.8f, 0.6f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.65f), + QVector3D(0.8f, 0.8f, 0.65f)); + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.6f, 0.8f, 0.7f), + QVector3D(0.8f, 0.8f, 0.7f)); + + m_mineShaftArray << QPair<QVector3D, QVector3D>(QVector3D(0.7f, 0.1f, 0.4f), + QVector3D(0.7f, 0.7f, 0.4f)); +} diff --git a/examples/datavisualization/volumetric/volumetric.h b/examples/datavisualization/volumetric/volumetric.h new file mode 100644 index 00000000..8d28b524 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 <QtDataVisualization/q3dscatter.h> +#include <QtDataVisualization/qcustom3dvolume.h> +#include <QtCore/QTimer> +#include <QtGui/QRgb> + +class QLabel; +class QRadioButton; +class QSlider; + +using namespace QtDataVisualization; + +class VolumetricModifier : public QObject +{ + Q_OBJECT +public: + explicit VolumetricModifier(Q3DScatter *scatter); + ~VolumetricModifier(); + + void setFpsLabel(QLabel *fpsLabel); + void setMediumDetailRB(QRadioButton *button); + void setHighDetailRB(QRadioButton *button); + void setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel); + void setAlphaMultiplierLabel(QLabel *label); + +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 handleFpsChange(qreal fps); + void handleTimeout(); + void toggleLowDetail(bool enabled); + void toggleMediumDetail(bool enabled); + void toggleHighDetail(bool enabled); + void setFpsMeasurement(bool enabled); + void setSliceSliders(QSlider *sliderX, QSlider *sliderY, QSlider *sliderZ); + void changeColorTable(int enabled); + void setPreserveOpacity(bool enabled); + void setTransparentGround(bool enabled); + void setUseHighDefShader(bool enabled); + void adjustAlphaMultiplier(int value); + void toggleAreaAll(bool enabled); + void toggleAreaMine(bool enabled); + void toggleAreaMountain(bool enabled); + void setDrawSliceFrames(int enabled); + +private: + + void initHeightMap(QString fileName, QVector<uchar> &layerData); + void initMineShaftArray(); + int createVolume(int textureSize, int startIndex, int count, + QVector<uchar> *textureData); + int excavateMineShaft(int textureSize, int startIndex, int count, + QVector<uchar> *textureData); + void excavateMineBlock(int textureSize, int dataIndex, int size, QVector<uchar> *textureData); + void handleSlicingChanges(); + + Q3DScatter *m_graph; + QCustom3DVolume *m_volumeItem; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + bool m_slicingX; + bool m_slicingY; + bool m_slicingZ; + QLabel *m_fpsLabel; + QRadioButton *m_mediumDetailRB; + QRadioButton *m_highDetailRB; + QVector<uchar> *m_lowDetailData; + QVector<uchar> *m_mediumDetailData; + QVector<uchar> *m_highDetailData; + QTimer m_timer; + int m_mediumDetailIndex; + int m_highDetailIndex; + int m_mediumDetailShaftIndex; + int m_highDetailShaftIndex; + QSlider *m_sliceSliderX; + QSlider *m_sliceSliderY; + QSlider *m_sliceSliderZ; + QVector<QRgb> m_colorTable1; + QVector<QRgb> m_colorTable2; + bool m_usingPrimaryTable; + QLabel *m_sliceLabelX; + QLabel *m_sliceLabelY; + QLabel *m_sliceLabelZ; + QLabel *m_alphaMultiplierLabel; + QVector<uchar> m_magmaLayer; + QVector<uchar> m_waterLayer; + QVector<uchar> m_groundLayer; + QVector<QPair<QVector3D, QVector3D> > m_mineShaftArray; +}; + +#endif diff --git a/examples/datavisualization/volumetric/volumetric.pro b/examples/datavisualization/volumetric/volumetric.pro new file mode 100644 index 00000000..fa355692 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.pro @@ -0,0 +1,17 @@ +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/* + +RESOURCES += volumetric.qrc diff --git a/examples/datavisualization/volumetric/volumetric.qrc b/examples/datavisualization/volumetric/volumetric.qrc new file mode 100644 index 00000000..920fd1d2 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/heightmaps"> + <file>layer_ground.png</file> + <file>layer_magma.png</file> + <file>layer_water.png</file> + </qresource> +</RCC> diff --git a/qtdatavisualization.pro b/qtdatavisualization.pro index bb98435e..faadadbc 100644 --- a/qtdatavisualization.pro +++ b/qtdatavisualization.pro @@ -8,4 +8,4 @@ contains(QT_CONFIG, opengles1) { error(QtDataVisualization does not support OpenGL ES 1!) } -OTHER_FILES += README dist/* +OTHER_FILES += README dist/* .qmake.conf diff --git a/src/datavisualization/axis/axis.pri b/src/datavisualization/axis/axis.pri index 0173b597..4c142c91 100644 --- a/src/datavisualization/axis/axis.pri +++ b/src/datavisualization/axis/axis.pri @@ -16,3 +16,5 @@ SOURCES += \ $$PWD/qcategory3daxis.cpp \ $$PWD/qvalue3daxisformatter.cpp \ $$PWD/qlogvalue3daxisformatter.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/axis/qlogvalue3daxisformatter.cpp b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp index 7367e7c5..85fd5c4f 100644 --- a/src/datavisualization/axis/qlogvalue3daxisformatter.cpp +++ b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp @@ -42,6 +42,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \since QtDataVisualization 1.1 * \ingroup datavisualization_qml * \instantiates QLogValue3DAxisFormatter + * \inherits ValueAxis3DFormatter * \brief LogValueAxis3DFormatter implements logarithmic value axis formatter. * * This type provides formatting rules for a logarithmic ValueAxis3D. diff --git a/src/datavisualization/axis/qvalue3daxis.cpp b/src/datavisualization/axis/qvalue3daxis.cpp index 8207174f..3b9c9e3d 100644 --- a/src/datavisualization/axis/qvalue3daxis.cpp +++ b/src/datavisualization/axis/qvalue3daxis.cpp @@ -18,6 +18,7 @@ #include "qvalue3daxis_p.h" #include "qvalue3daxisformatter_p.h" +#include "abstract3dcontroller_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -68,8 +69,16 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty string ValueAxis3D::labelFormat * - * Defines the label format to be used for the labels on this axis. Supported specifiers are: + * Defines the label format to be used for the labels on this axis. How the format is interpreted + * depends on the axis formatter and the locale in use. Using the default formatter and default + * locale (\c{"C"}), the formatting uses QString::sprintf(). Supported specifiers are: * \c {d, i, o, x, X, f, F, e, E, g, G, c}. See QString::sprintf() for additional details. + * For other locales, the default formatter uses reduced set of printf format specifiers: + * \c {d, i, f, F, e, E, g, G}. In these cases, the only supported modifier is the precision + * modifier for the floating point and exponential formats. The decimal point and other locale + * dependent formatting is done according to the graph locale. + * + * \sa AbstractGraph3D::locale */ /*! @@ -164,12 +173,20 @@ int QValue3DAxis::subSegmentCount() const /*! * \property QValue3DAxis::labelFormat * - * Defines the label \a format to be used for the labels on this axis. Supported specifiers are: + * Defines the label format to be used for the labels on this axis. How the format is interpreted + * depends on the axis formatter and the locale in use. Using the default formatter and default + * locale (\c{"C"}), the formatting uses QString::sprintf(). Supported specifiers are: * \c {d, i, o, x, X, f, F, e, E, g, G, c}. See QString::sprintf() for additional details. + * For other locales, the default formatter uses reduced set of printf format specifiers: + * \c {d, i, f, F, e, E, g, G}. In these cases, the only supported modifier is the precision + * modifier for the floating point and exponential formats. The decimal point and other locale + * dependent formatting is done according to the graph locale. * * Usage example: * * \c {axis->setLabelFormat("%.2f mm");} + * + * \sa formatter, QAbstract3DGraph::locale */ void QValue3DAxis::setLabelFormat(const QString &format) { @@ -201,6 +218,9 @@ void QValue3DAxis::setFormatter(QValue3DAxisFormatter *formatter) dptr()->m_formatter = formatter; formatter->setParent(this); formatter->d_ptr->setAxis(this); + Abstract3DController *controller = qobject_cast<Abstract3DController *>(parent()); + if (controller) + formatter->setLocale(controller->locale()); emit formatterChanged(formatter); emit dptr()->formatterDirty(); } diff --git a/src/datavisualization/axis/qvalue3daxisformatter.cpp b/src/datavisualization/axis/qvalue3daxisformatter.cpp index 56ca3b0f..f6b705a9 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter.cpp +++ b/src/datavisualization/axis/qvalue3daxisformatter.cpp @@ -270,6 +270,28 @@ QStringList &QValue3DAxisFormatter::labelStrings() const return d_ptr->m_labelStrings; } +/*! + * Sets the \a locale that this formatter uses. + * The graph automatically sets the formatter's locale to a graph's locale whenever the parent axis + * is set as an active axis of the graph, the axis formatter is set to an axis attached to + * the graph, or the graph's locale changes. + * + * \sa locale(), QAbstract3DGraph::locale + */ +void QValue3DAxisFormatter::setLocale(const QLocale &locale) +{ + d_ptr->m_cLocaleInUse = (locale == QLocale::c()); + d_ptr->m_locale = locale; + markDirty(true); +} +/*! + * \return the current locale this formatter is using. + */ +QLocale QValue3DAxisFormatter::locale() const +{ + return d_ptr->m_locale; +} + // QValue3DAxisFormatterPrivate QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter *q) : QObject(0), @@ -281,7 +303,10 @@ QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter m_axis(0), m_preparsedParamType(Utils::ParamTypeUnknown), m_allowNegatives(true), - m_allowZero(true) + m_allowZero(true), + m_formatPrecision(6), // 6 and 'g' are defaults in Qt API for format precision and spec + m_formatSpec('g'), + m_cLocaleInUse(true) { } @@ -363,12 +388,19 @@ QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString { if (m_previousLabelFormat.compare(format)) { // Format string different than the previous one used, reparse it - m_previousLabelFormat = format; - m_preparsedParamType = Utils::findFormatParamType(format); m_labelFormatArray = format.toUtf8(); + m_previousLabelFormat = format; + m_preparsedParamType = Utils::preParseFormat(format, m_formatPreStr, m_formatPostStr, + m_formatPrecision, m_formatSpec); } - return Utils::formatLabel(m_labelFormatArray, m_preparsedParamType, value); + if (m_cLocaleInUse) { + return Utils::formatLabelSprintf(m_labelFormatArray, m_preparsedParamType, value); + } else { + return Utils::formatLabelLocalized(m_preparsedParamType, value, m_locale, m_formatPreStr, + m_formatPostStr, m_formatPrecision, m_formatSpec, + m_labelFormatArray); + } } float QValue3DAxisFormatterPrivate::positionAt(float value) const diff --git a/src/datavisualization/axis/qvalue3daxisformatter.h b/src/datavisualization/axis/qvalue3daxisformatter.h index c7b0bac5..82e49f21 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter.h +++ b/src/datavisualization/axis/qvalue3daxisformatter.h @@ -24,6 +24,7 @@ #include <QtCore/QScopedPointer> #include <QtCore/QVector> #include <QtCore/QStringList> +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -60,6 +61,9 @@ protected: QVector<float> &labelPositions() const; QStringList &labelStrings() const; + void setLocale(const QLocale &locale); + QLocale locale() const; + QScopedPointer<QValue3DAxisFormatterPrivate> d_ptr; private: diff --git a/src/datavisualization/axis/qvalue3daxisformatter_p.h b/src/datavisualization/axis/qvalue3daxisformatter_p.h index 2d1dc920..9571d001 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter_p.h +++ b/src/datavisualization/axis/qvalue3daxisformatter_p.h @@ -32,6 +32,7 @@ #include "datavisualizationglobal_p.h" #include "qvalue3daxisformatter.h" #include "utils_p.h" +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -83,6 +84,13 @@ protected: bool m_allowNegatives; bool m_allowZero; + QLocale m_locale; + QString m_formatPreStr; + QString m_formatPostStr; + int m_formatPrecision; + char m_formatSpec; + bool m_cLocaleInUse; + friend class QValue3DAxisFormatter; }; diff --git a/src/datavisualization/common.pri b/src/datavisualization/common.pri deleted file mode 100644 index 9a497c2c..00000000 --- a/src/datavisualization/common.pri +++ /dev/null @@ -1,7 +0,0 @@ -INCLUDEPATH += $$PWD/engine \ - $$PWD/global \ - $$PWD/utils \ - $$PWD/axis \ - $$PWD/data \ - $$PWD/input \ - $$PWD/theme diff --git a/src/datavisualization/data/abstractrenderitem_p.h b/src/datavisualization/data/abstractrenderitem_p.h index 57977a3c..ccee2b00 100644 --- a/src/datavisualization/data/abstractrenderitem_p.h +++ b/src/datavisualization/data/abstractrenderitem_p.h @@ -48,10 +48,12 @@ public: inline void setTranslation(const QVector3D &translation) { m_translation = translation; } inline const QVector3D &translation() const {return m_translation; } - inline QQuaternion rotation() const { return m_rotation; } + inline const QQuaternion &rotation() const { return m_rotation; } inline void setRotation(const QQuaternion &rotation) { - if (m_rotation != rotation) + if (rotation.isNull()) + m_rotation = identityQuaternion; + else m_rotation = rotation; } diff --git a/src/datavisualization/data/customrenderitem.cpp b/src/datavisualization/data/customrenderitem.cpp index a1c70057..f56ca86e 100644 --- a/src/datavisualization/data/customrenderitem.cpp +++ b/src/datavisualization/data/customrenderitem.cpp @@ -23,9 +23,32 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION CustomRenderItem::CustomRenderItem() : AbstractRenderItem(), m_texture(0), + m_positionAbsolute(false), + m_scalingAbsolute(true), m_object(0), + m_needBlend(true), m_visible(true), - m_renderer(0) + m_valid(true), + m_index(0), + m_shadowCasting(false), + m_isFacingCamera(false), + m_item(0), + m_renderer(0), + m_labelItem(false), + m_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_isVolume(false), + m_textureFormat(QImage::Format_ARGB32), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false) + { } @@ -39,4 +62,47 @@ void CustomRenderItem::setMesh(const QString &meshFile) ObjectHelper::resetObjectHelper(m_renderer, m_object, meshFile); } +void CustomRenderItem::setColorTable(const QVector<QRgb> &colors) +{ + m_colorTable.resize(256); + for (int i = 0; i < 256; i++) { + if (i < colors.size()) { + const QRgb &rgb = colors.at(i); + m_colorTable[i] = QVector4D(float(qRed(rgb)) / 255.0f, + float(qGreen(rgb)) / 255.0f, + float(qBlue(rgb)) / 255.0f, + float(qAlpha(rgb)) / 255.0f); + } else { + m_colorTable[i] = QVector4D(0.0f, 0.0f, 0.0f, 0.0f); + } + } +} + +void CustomRenderItem::setMinBounds(const QVector3D &bounds) +{ + m_minBounds = bounds; + m_minBoundsNormal = m_minBounds; + m_minBoundsNormal.setY(-m_minBoundsNormal.y()); + m_minBoundsNormal.setZ(-m_minBoundsNormal.z()); + m_minBoundsNormal = 0.5f * (m_minBoundsNormal + oneVector); +} + +void CustomRenderItem::setMaxBounds(const QVector3D &bounds) +{ + m_maxBounds = bounds; + m_maxBoundsNormal = m_maxBounds; + m_maxBoundsNormal.setY(-m_maxBoundsNormal.y()); + m_maxBoundsNormal.setZ(-m_maxBoundsNormal.z()); + m_maxBoundsNormal = 0.5f * (m_maxBoundsNormal + oneVector); +} + +void CustomRenderItem::setSliceFrameColor(const QColor &color) +{ + const QRgb &rgb = color.rgba(); + m_sliceFrameColor = QVector4D(float(qRed(rgb)) / 255.0f, + float(qGreen(rgb)) / 255.0f, + float(qBlue(rgb)) / 255.0f, + float(1.0f)); // Alpha not supported for frames +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/customrenderitem_p.h b/src/datavisualization/data/customrenderitem_p.h index 4fb94276..ad868fac 100644 --- a/src/datavisualization/data/customrenderitem_p.h +++ b/src/datavisualization/data/customrenderitem_p.h @@ -31,6 +31,9 @@ #include "abstractrenderitem_p.h" #include "objecthelper_p.h" +#include <QtGui/QRgb> +#include <QtGui/QImage> +#include <QtGui/QColor> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -48,11 +51,17 @@ public: void setMesh(const QString &meshFile); inline ObjectHelper *mesh() const { return m_object; } inline void setScaling(const QVector3D &scaling) { m_scaling = scaling; } - inline QVector3D scaling() const { return m_scaling; } + inline const QVector3D &scaling() const { return m_scaling; } + inline void setOrigScaling(const QVector3D &scaling) { m_origScaling = scaling; } + inline const QVector3D &origScaling() const { return m_origScaling; } inline void setPosition(const QVector3D &position) { m_position = position; } - inline QVector3D position() const { return m_position; } - inline void setPositionAbsolute(bool absolute) { m_absolute = absolute; } - inline bool isPositionAbsolute() const { return m_absolute; } + inline const QVector3D &position() const { return m_position; } + inline void setOrigPosition(const QVector3D &position) { m_origPosition = position; } + inline const QVector3D &origPosition() const { return m_origPosition; } + inline void setPositionAbsolute(bool absolute) { m_positionAbsolute = absolute; } + inline bool isPositionAbsolute() const { return m_positionAbsolute; } + inline void setScalingAbsolute(bool absolute) { m_scalingAbsolute = absolute; } + inline bool isScalingAbsolute() const { return m_scalingAbsolute; } inline void setBlendNeeded(bool blend) { m_needBlend = blend; } inline bool isBlendNeeded() const { return m_needBlend; } inline void setVisible(bool visible) { m_visible = visible; } @@ -68,14 +77,78 @@ public: inline void setFacingCamera(bool facing) { m_isFacingCamera = facing; } inline bool isFacingCamera() const { return m_isFacingCamera; } inline void setRenderer(Abstract3DRenderer *renderer) { m_renderer = renderer; } + inline void setLabelItem(bool isLabel) { m_labelItem = isLabel; } + inline bool isLabel() const { return m_labelItem; } + + // Volume specific + inline void setTextureWidth(int width) { m_textureWidth = width; setSliceIndexX(m_sliceIndexX); } + inline int textureWidth() const { return m_textureWidth; } + inline void setTextureHeight(int height) { m_textureHeight = height; setSliceIndexY(m_sliceIndexY); } + inline int textureHeight() const { return m_textureHeight; } + inline void setTextureDepth(int depth) { m_textureDepth = depth; setSliceIndexZ(m_sliceIndexZ); } + inline int textureDepth() const { return m_textureDepth; } + inline int textureSize() const { return m_textureWidth * m_textureHeight * m_textureDepth; } + inline void setColorTable(const QVector<QVector4D> &colors) { m_colorTable = colors; } + void setColorTable(const QVector<QRgb> &colors); + inline const QVector<QVector4D> &colorTable() const { return m_colorTable; } + inline void setVolume(bool volume) { m_isVolume = volume; } + inline bool isVolume() const { return m_isVolume; } + inline void setTextureFormat(QImage::Format format) { m_textureFormat = format; } + inline QImage::Format textureFormat() const { return m_textureFormat; } + inline void setSliceIndexX(int index) + { + m_sliceIndexX = index; + m_sliceFractions.setX((float(index) + 0.5f) / float(m_textureWidth) * 2.0 - 1.0); + } + inline void setSliceIndexY(int index) + { + m_sliceIndexY = index; + m_sliceFractions.setY((float(index) + 0.5f) / float(m_textureHeight) * 2.0 - 1.0); + } + inline void setSliceIndexZ(int index) + { + m_sliceIndexZ = index; + m_sliceFractions.setZ((float(index) + 0.5f) / float(m_textureDepth) * 2.0 - 1.0); + } + inline const QVector3D &sliceFractions() const { return m_sliceFractions; } + inline int sliceIndexX() const { return m_sliceIndexX; } + inline int sliceIndexY() const { return m_sliceIndexY; } + inline int sliceIndexZ() const { return m_sliceIndexZ; } + inline void setAlphaMultiplier(float mult) { m_alphaMultiplier = mult; } + inline float alphaMultiplier() const { return m_alphaMultiplier; } + inline void setPreserveOpacity(bool enable) { m_preserveOpacity = enable; } + inline bool preserveOpacity() const { return m_preserveOpacity; } + inline void setUseHighDefShader(bool enable) { m_useHighDefShader = enable; } + inline bool useHighDefShader() const {return m_useHighDefShader; } + void setMinBounds(const QVector3D &bounds); + inline const QVector3D &minBounds() const { return m_minBounds; } + void setMaxBounds(const QVector3D &bounds); + inline const QVector3D &maxBounds() const { return m_maxBounds; } + inline const QVector3D &minBoundsNormal() const { return m_minBoundsNormal; } + inline const QVector3D &maxBoundsNormal() const { return m_maxBoundsNormal; } + inline void setDrawSlices(bool enable) { m_drawSlices = enable; } + inline bool drawSlices() const {return m_drawSlices; } + inline void setDrawSliceFrames(bool enable) { m_drawSliceFrames = enable; } + inline bool drawSliceFrames() const {return m_drawSliceFrames; } + void setSliceFrameColor(const QColor &color); + inline const QVector4D &sliceFrameColor() const { return m_sliceFrameColor; } + inline void setSliceFrameWidths(const QVector3D &widths) { m_sliceFrameWidths = widths * 2.0f; } + inline const QVector3D &sliceFrameWidths() const { return m_sliceFrameWidths; } + inline void setSliceFrameGaps(const QVector3D &gaps) { m_sliceFrameGaps = gaps * 2.0f; } + inline const QVector3D &sliceFrameGaps() const { return m_sliceFrameGaps; } + inline void setSliceFrameThicknesses(const QVector3D &thicknesses) { m_sliceFrameThicknesses = thicknesses; } + inline const QVector3D &sliceFrameThicknesses() const { return m_sliceFrameThicknesses; } private: Q_DISABLE_COPY(CustomRenderItem) GLuint m_texture; QVector3D m_scaling; + QVector3D m_origScaling; QVector3D m_position; - bool m_absolute; + QVector3D m_origPosition; + bool m_positionAbsolute; + bool m_scalingAbsolute; ObjectHelper *m_object; // shared reference bool m_needBlend; bool m_visible; @@ -85,6 +158,32 @@ private: bool m_isFacingCamera; QCustom3DItem *m_item; Abstract3DRenderer *m_renderer; + bool m_labelItem; + + // Volume specific + int m_textureWidth; + int m_textureHeight; + int m_textureDepth; + QVector<QVector4D> m_colorTable; + bool m_isVolume; + QImage::Format m_textureFormat; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + QVector3D m_sliceFractions; + float m_alphaMultiplier; + bool m_preserveOpacity; + bool m_useHighDefShader; + QVector3D m_minBounds; + QVector3D m_maxBounds; + QVector3D m_minBoundsNormal; + QVector3D m_maxBoundsNormal; + bool m_drawSlices; + bool m_drawSliceFrames; + QVector4D m_sliceFrameColor; + QVector3D m_sliceFrameWidths; + QVector3D m_sliceFrameGaps; + QVector3D m_sliceFrameThicknesses; }; typedef QHash<QCustom3DItem *, CustomRenderItem *> CustomRenderItemArray; diff --git a/src/datavisualization/data/data.pri b/src/datavisualization/data/data.pri index 73f398bf..e2bbd6eb 100644 --- a/src/datavisualization/data/data.pri +++ b/src/datavisualization/data/data.pri @@ -41,7 +41,9 @@ HEADERS += \ $$PWD/qcustom3ditem.h \ $$PWD/qcustom3ditem_p.h \ $$PWD/qcustom3dlabel.h \ - $$PWD/qcustom3dlabel_p.h + $$PWD/qcustom3dlabel_p.h \ + $$PWD/qcustom3dvolume.h \ + $$PWD/qcustom3dvolume_p.h SOURCES += \ $$PWD/labelitem.cpp \ @@ -69,4 +71,7 @@ SOURCES += \ $$PWD/qsurface3dseries.cpp \ $$PWD/customrenderitem.cpp \ $$PWD/qcustom3ditem.cpp \ - $$PWD/qcustom3dlabel.cpp + $$PWD/qcustom3dlabel.cpp \ + $$PWD/qcustom3dvolume.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/data/labelitem.cpp b/src/datavisualization/data/labelitem.cpp index 859b0550..ec8ba3fd 100644 --- a/src/datavisualization/data/labelitem.cpp +++ b/src/datavisualization/data/labelitem.cpp @@ -28,7 +28,7 @@ LabelItem::LabelItem() LabelItem::~LabelItem() { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); } void LabelItem::setSize(const QSize &size) @@ -43,7 +43,7 @@ QSize LabelItem::size() const void LabelItem::setTextureId(GLuint textureId) { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); m_textureId = textureId; } @@ -55,7 +55,7 @@ GLuint LabelItem::textureId() const void LabelItem::clear() { if (m_textureId) { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); m_textureId = 0; } m_size = QSize(0, 0); diff --git a/src/datavisualization/data/qabstract3dseries.cpp b/src/datavisualization/data/qabstract3dseries.cpp index f8fe6b4f..caa95062 100644 --- a/src/datavisualization/data/qabstract3dseries.cpp +++ b/src/datavisualization/data/qabstract3dseries.cpp @@ -657,6 +657,9 @@ QAbstract3DSeriesPrivate::QAbstract3DSeriesPrivate(QAbstract3DSeries *q, m_mesh(QAbstract3DSeries::MeshCube), m_meshSmooth(false), m_colorStyle(Q3DTheme::ColorStyleUniform), + m_baseColor(Qt::black), + m_singleHighlightColor(Qt::black), + m_multiHighlightColor(Qt::black), m_itemLabelDirty(true), m_itemLabelVisible(true) { diff --git a/src/datavisualization/data/qabstract3dseries.h b/src/datavisualization/data/qabstract3dseries.h index 87c4f3c1..c26c40d1 100644 --- a/src/datavisualization/data/qabstract3dseries.h +++ b/src/datavisualization/data/qabstract3dseries.h @@ -153,6 +153,7 @@ private: friend class Bars3DController; friend class Surface3DController; friend class Surface3DRenderer; + friend class Scatter3DRenderer; friend class Scatter3DController; friend class QBar3DSeries; friend class SeriesRenderCache; diff --git a/src/datavisualization/data/qbar3dseries.cpp b/src/datavisualization/data/qbar3dseries.cpp index 19c3bf79..0aa60b66 100644 --- a/src/datavisualization/data/qbar3dseries.cpp +++ b/src/datavisualization/data/qbar3dseries.cpp @@ -43,26 +43,27 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \row * \li @valueTitle \li Title from value axis * \row - * \li @rowIdx \li Visible row index + * \li @rowIdx \li Visible row index. Localized using graph locale. * \row - * \li @colIdx \li Visible Column index + * \li @colIdx \li Visible Column index. Localized using graph locale. * \row * \li @rowLabel \li Label from row axis * \row * \li @colLabel \li Label from column axis * \row - * \li @valueLabel \li Item value formatted using the same format the value axis attached to the graph uses, - * see \l{QValue3DAxis::setLabelFormat()} for more information. + * \li @valueLabel \li Item value formatted using the same format the value axis attached to + * the graph uses. See \l{QValue3DAxis::labelFormat} for more information. * \row * \li @seriesName \li Name of the series * \row - * \li %<format spec> \li Item value in specified format. + * \li %<format spec> \li Item value in specified format. Formatted using the same rules as + * \l{QValue3DAxis::labelFormat}. * \endtable * * For example: * \snippet doc_src_qtdatavisualization.cpp 1 * - * \sa {Qt Data Visualization Data Handling} + * \sa {Qt Data Visualization Data Handling}, QAbstract3DGraph::locale */ /*! @@ -334,6 +335,10 @@ void QBar3DSeriesPrivate::createItemLabel() return; } + QLocale locale(QLocale::c()); + if (m_controller) + locale = m_controller->locale(); + QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_controller->axisZ()); QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_controller->axisX()); QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_controller->axisY()); @@ -344,13 +349,13 @@ void QBar3DSeriesPrivate::createItemLabel() int selBarPosRow = m_selectedBar.x(); int selBarPosCol = m_selectedBar.y(); - m_itemLabel.replace(rowIndexTag, QString::number(selBarPosRow)); + m_itemLabel.replace(rowIndexTag, locale.toString(selBarPosRow)); if (categoryAxisZ->labels().size() > selBarPosRow) m_itemLabel.replace(rowLabelTag, categoryAxisZ->labels().at(selBarPosRow)); else m_itemLabel.replace(rowLabelTag, QString()); m_itemLabel.replace(rowTitleTag, categoryAxisZ->title()); - m_itemLabel.replace(colIndexTag, QString::number(selBarPosCol)); + m_itemLabel.replace(colIndexTag, locale.toString(selBarPosCol)); if (categoryAxisX->labels().size() > selBarPosCol) m_itemLabel.replace(colLabelTag, categoryAxisX->labels().at(selBarPosCol)); else diff --git a/src/datavisualization/data/qcustom3ditem.cpp b/src/datavisualization/data/qcustom3ditem.cpp index efee3b15..b9825732 100644 --- a/src/datavisualization/data/qcustom3ditem.cpp +++ b/src/datavisualization/data/qcustom3ditem.cpp @@ -62,13 +62,15 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * * Holds the item \a position as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}. * - * Item position is either in data coordinates or in absolute coordinates, depending on + * Item position is either in data coordinates or in absolute coordinates, depending on the * positionAbsolute property. When using absolute coordinates, values between \c{-1.0...1.0} are * within axis ranges. * - * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}. + * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}, + * unless the item is a Custom3DVolume that would be partially visible and scalingAbsolute is also + * \c{false}. In that case, the visible portion of the volume will be rendered. * - * \sa positionAbsolute + * \sa positionAbsolute, scalingAbsolute */ /*! \qmlproperty bool Custom3DItem::positionAbsolute @@ -83,8 +85,33 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! \qmlproperty vector3d Custom3DItem::scaling * * Holds the item \a scaling as a vector3d. Defaults to \c {vector3d(0.1, 0.1, 0.1)}. - * The default value sets the item to 10% of the height of the graph, provided the item size is - * normalized. + * + * Item scaling is either in data values or in absolute values, depending on the + * scalingAbsolute property. The default vector interpreted as absolute values sets the item to + * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios + * haven't been changed from the defaults. + * + * \sa scalingAbsolute + */ + +/*! \qmlproperty bool Custom3DItem::scalingAbsolute + * \since QtDataVisualization 1.2 + * + * This property dictates if item scaling is to be handled in data values or in absolute + * values. Defaults to \c{true}. Items with absolute scaling will be rendered at the same + * size, regardless of axis ranges. Items with data scaling will change their apparent size + * according to the axis ranges. If positionAbsolute value is \c{true}, this property is ignored + * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling + * is calculated on the unrotated item. Similarly, for Custom3DVolume items, the range clipping + * is calculated on the unrotated item. + * + * \note: Only absolute scaling is supported for Custom3DLabel items or for custom items used in + * \l{AbstractGraph3D::polar}{polar} graphs. + * + * \note: The custom item's mesh must be normalized to range \c{[-1 ,1]}, or the data + * scaling will not be accurate. + * + * \sa scaling, positionAbsolute */ /*! \qmlproperty quaternion Custom3DItem::rotation @@ -180,7 +207,9 @@ QString QCustom3DItem::meshFile() const * positionAbsolute property. When using absolute coordinates, values between \c{-1.0...1.0} are * within axis ranges. * - * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}. + * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}, + * unless the item is a QCustom3DVolume that would be partially visible and scalingAbsolute is also + * \c{false}. In that case, the visible portion of the volume will be rendered. * * \sa positionAbsolute */ @@ -211,7 +240,7 @@ void QCustom3DItem::setPositionAbsolute(bool positionAbsolute) { if (d_ptr->m_positionAbsolute != positionAbsolute) { d_ptr->m_positionAbsolute = positionAbsolute; - d_ptr->m_dirtyBits.positionAbsoluteDirty = true; + d_ptr->m_dirtyBits.positionDirty = true; emit positionAbsoluteChanged(positionAbsolute); emit d_ptr->needUpdate(); } @@ -225,8 +254,13 @@ bool QCustom3DItem::isPositionAbsolute() const /*! \property QCustom3DItem::scaling * * Holds the item \a scaling as a QVector3D. Defaults to \c {QVector3D(0.1, 0.1, 0.1)}. - * The default value sets the item to 10% of the height of the graph, provided the item size is - * normalized. + * + * Item scaling is either in data values or in absolute values, depending on the + * scalingAbsolute property. The default vector interpreted as absolute values sets the item to + * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios + * haven't been changed from the defaults. + * + * \sa scalingAbsolute */ void QCustom3DItem::setScaling(const QVector3D &scaling) { @@ -243,6 +277,42 @@ QVector3D QCustom3DItem::scaling() const return d_ptr->m_scaling; } +/*! \property QCustom3DItem::scalingAbsolute + * \since QtDataVisualization 1.2 + * + * This property dictates if item scaling is to be handled in data values or in absolute + * values. Defaults to \c{true}. Items with absolute scaling will be rendered at the same + * size, regardless of axis ranges. Items with data scaling will change their apparent size + * according to the axis ranges. If positionAbsolute value is \c{true}, this property is ignored + * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling + * is calculated on the unrotated item. Similarly, for QCustom3DVolume items, the range clipping + * is calculated on the unrotated item. + * + * \note: Only absolute scaling is supported for QCustom3DLabel items or for custom items used in + * \l{QAbstract3DGraph::polar}{polar} graphs. + * + * \note: The custom item's mesh must be normalized to range \c{[-1 ,1]}, or the data + * scaling will not be accurate. + * + * \sa scaling, positionAbsolute + */ +void QCustom3DItem::setScalingAbsolute(bool scalingAbsolute) +{ + if (d_ptr->m_isLabelItem && !scalingAbsolute) { + qWarning() << __FUNCTION__ << "Data bounds are not supported for label items."; + } else if (d_ptr->m_scalingAbsolute != scalingAbsolute) { + d_ptr->m_scalingAbsolute = scalingAbsolute; + d_ptr->m_dirtyBits.scalingDirty = true; + emit scalingAbsoluteChanged(scalingAbsolute); + emit d_ptr->needUpdate(); + } +} + +bool QCustom3DItem::isScalingAbsolute() const +{ + return d_ptr->m_scalingAbsolute; +} + /*! \property QCustom3DItem::rotation * * Holds the item \a rotation as a QQuaternion. Defaults to \c {QQuaternion(0.0, 0.0, 0.0, 0.0)}. @@ -367,13 +437,16 @@ QString QCustom3DItem::textureFile() const QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q) : q_ptr(q), + m_textureImage(QImage(1, 1, QImage::Format_ARGB32)), m_position(QVector3D(0.0f, 0.0f, 0.0f)), m_positionAbsolute(false), m_scaling(QVector3D(0.1f, 0.1f, 0.1f)), - m_rotation(QQuaternion(0.0f, 0.0f, 0.0f, 0.0f)), + m_scalingAbsolute(true), + m_rotation(identityQuaternion), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } @@ -381,14 +454,17 @@ QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q, const QString &mesh const QVector3D &position, const QVector3D &scaling, const QQuaternion &rotation) : q_ptr(q), + m_textureImage(QImage(1, 1, QImage::Format_ARGB32)), m_meshFile(meshFile), m_position(position), m_positionAbsolute(false), m_scaling(scaling), + m_scalingAbsolute(true), m_rotation(rotation), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } @@ -412,7 +488,6 @@ void QCustom3DItemPrivate::resetDirtyBits() m_dirtyBits.textureDirty = false; m_dirtyBits.meshDirty = false; m_dirtyBits.positionDirty = false; - m_dirtyBits.positionAbsoluteDirty = false; m_dirtyBits.scalingDirty = false; m_dirtyBits.rotationDirty = false; m_dirtyBits.visibleDirty = false; diff --git a/src/datavisualization/data/qcustom3ditem.h b/src/datavisualization/data/qcustom3ditem.h index 2f7f37cf..5c880213 100644 --- a/src/datavisualization/data/qcustom3ditem.h +++ b/src/datavisualization/data/qcustom3ditem.h @@ -39,6 +39,7 @@ class QT_DATAVISUALIZATION_EXPORT QCustom3DItem : public QObject Q_PROPERTY(QQuaternion rotation READ rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(bool shadowCasting READ isShadowCasting WRITE setShadowCasting NOTIFY shadowCastingChanged) + Q_PROPERTY(bool scalingAbsolute READ isScalingAbsolute WRITE setScalingAbsolute NOTIFY scalingAbsoluteChanged REVISION 1) public: explicit QCustom3DItem(QObject *parent = 0); @@ -62,6 +63,9 @@ public: void setScaling(const QVector3D &scaling); QVector3D scaling() const; + void setScalingAbsolute(bool scalingAbsolute); + bool isScalingAbsolute() const; + void setRotation(const QQuaternion &rotation); QQuaternion rotation(); @@ -84,6 +88,7 @@ signals: void rotationChanged(const QQuaternion &rotation); void visibleChanged(bool visible); void shadowCastingChanged(bool shadowCasting); + Q_REVISION(1) void scalingAbsoluteChanged(bool scalingAbsolute); protected: QCustom3DItem(QCustom3DItemPrivate *d, QObject *parent = 0); diff --git a/src/datavisualization/data/qcustom3ditem_p.h b/src/datavisualization/data/qcustom3ditem_p.h index c1ce5996..627bf53f 100644 --- a/src/datavisualization/data/qcustom3ditem_p.h +++ b/src/datavisualization/data/qcustom3ditem_p.h @@ -29,6 +29,7 @@ #ifndef QCUSTOM3DITEM_P_H #define QCUSTOM3DITEM_P_H +#include "datavisualizationglobal_p.h" #include "qcustom3ditem.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -37,7 +38,6 @@ struct QCustomItemDirtyBitField { bool textureDirty : 1; bool meshDirty : 1; bool positionDirty : 1; - bool positionAbsoluteDirty : 1; bool scalingDirty : 1; bool rotationDirty : 1; bool visibleDirty : 1; @@ -47,7 +47,6 @@ struct QCustomItemDirtyBitField { : textureDirty(false), meshDirty(false), positionDirty(false), - positionAbsoluteDirty(false), scalingDirty(false), rotationDirty(false), visibleDirty(false), @@ -77,11 +76,13 @@ public: QVector3D m_position; bool m_positionAbsolute; QVector3D m_scaling; + bool m_scalingAbsolute; QQuaternion m_rotation; bool m_visible; bool m_shadowCasting; bool m_isLabelItem; + bool m_isVolumeItem; QCustomItemDirtyBitField m_dirtyBits; diff --git a/src/datavisualization/data/qcustom3dlabel.cpp b/src/datavisualization/data/qcustom3dlabel.cpp index 85a37e2d..bb5d71d0 100644 --- a/src/datavisualization/data/qcustom3dlabel.cpp +++ b/src/datavisualization/data/qcustom3dlabel.cpp @@ -44,6 +44,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \since QtDataVisualization 1.1 * \ingroup datavisualization_qml * \instantiates QCustom3DLabel + * \inherits Custom3DItem * \brief The Custom3DLabel type is for creating custom labels to be added to a graph. * * This type is for creating custom labels to be added to a graph. You can set text, font, diff --git a/src/datavisualization/data/qcustom3dvolume.cpp b/src/datavisualization/data/qcustom3dvolume.cpp new file mode 100644 index 00000000..f8287a81 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.cpp @@ -0,0 +1,1278 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +#include "qcustom3dvolume_p.h" +#include "utils_p.h" + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +/*! + * \class QCustom3DVolume + * \inmodule QtDataVisualization + * \brief The QCustom3DVolume class is for creating volume rendered objects to be added to a graph. + * \since QtDataVisualization 1.2 + * + * This class is for creating volume rendered objects to be added to a graph. A volume rendered + * object is a box with a 3D texture. Three slice planes are supported for the volume, one along + * each main axis of the volume. + * + * Rendering volume objects is very performance intensive, especially when the volume is largely + * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly + * with the amount of pixels that the volume occupies on the screen, so showing the volume in a + * smaller view or limiting the zoom level of the graph are easy ways to improve performance. + * Similarly, the volume texture dimensions have a large impact on performance. + * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider + * turning the high definition shader off by setting useHighDefShader property to \c{false}. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments. + * + * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader + */ + +/*! + * \qmltype Custom3DVolume + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates QCustom3DVolume + * \inherits Custom3DItem + * \brief The Custom3DVolume type is for creating volume rendered objects to be added to a graph. + * + * This class is for creating volume rendered objects to be added to a graph. A volume rendered + * object is a box with a 3D texture. Three slice planes are supported for the volume, one along + * each main axis of the volume. + * + * Rendering volume objects is very performance intensive, especially when the volume is largely + * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly + * with the amount of pixels that the volume occupies on the screen, so showing the volume in a + * smaller view or limiting the zoom level of the graph are easy ways to improve performance. + * Similarly, the volume texture dimensions have a large impact on performance. + * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider + * turning the high definition shader off by setting useHighDefShader property to \c{false}. + * + * \note: Filling in the volume data would not typically be efficient or practical from pure QML, + * so properties directly related to that are not fully supported from QML. + * Make a hybrid QML/C++ application if you want to use volume objects with a QML UI. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments. + * + * \sa AbstractGraph3D::orthoProjection, useHighDefShader + */ + +/*! \qmlproperty int Custom3DVolume::textureWidth + * + * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::textureHeight + * + * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::textureDepth + * + * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexX + * + * The X dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexY + * + * The Y dimension index into the texture data indicating which horizontal slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexZ + * + * The Z dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! + * \qmlproperty real Custom3DVolume::alphaMultiplier + * + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0}. + * + * \sa preserveOpacity + */ + +/*! + * \qmlproperty bool Custom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ + +/*! + * \qmlproperty bool Custom3DVolume::useHighDefShader + * + * If this property value is \c{true}, a high definition shader is used to render the volume. + * If it is \c{false}, a low definition shader is used. + * + * The high definition shader guarantees that every visible texel of the volume texture is sampled + * when the volume is rendered. + * The low definition shader renders only a rough approximation of the volume contents, + * but at much higher frame rate. The low definition shader doesn't guarantee every texel of the + * volume texture is sampled, so there may be flickering if the volume contains distinct thin + * features. + * + * \note This value doesn't affect the level of detail when rendering the slices of the volume. + * + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool Custom3DVolume::drawSlices + * + * If this property value is \c{true}, the slices indicated by slice index properties + * will be drawn instead of the full volume. + * If it is \c{false}, the full volume will always be drawn. + * Defaults to \c{false}. + * + * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are + * rotated as well. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ + */ + +/*! + * \qmlproperty bool Custom3DVolume::drawSliceFrames + * + * If this property value is \c{true}, the frames of slices indicated by slice index properties + * will be drawn around the volume. + * If it is \c{false}, no slice frames will be drawn. + * Drawing slice frames is independent of drawing slices, so you can show the full volume and + * still draw the slice frames around it. + * Defaults to \c{false}. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices + */ + +/*! + * \qmlproperty color Custom3DVolume::sliceFrameColor + * + * Indicates the color of the slice frame. Transparent slice frame color is not supported. + * + * Defaults to black. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameWidths + * + * Indicates the widths of the slice frame. The width can be different on different dimensions, + * so you can for example omit drawing the frames on certain sides of the volume by setting the + * value for that dimension to zero. The values are fractions of the volume thickness in the same + * dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameGaps + * + * Indicates the amount of air gap left between the volume itself and the frame in each dimension. + * The gap can be different on different dimensions. The values are fractions of the volume + * thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses + * + * Indicates the thickness of the slice frames for each dimension. The values are fractions of + * the volume thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * Constructs QCustom3DVolume with given \a parent. + */ +QCustom3DVolume::QCustom3DVolume(QObject *parent) : + QCustom3DItem(new QCustom3DVolumePrivate(this), parent) +{ +} + +/*! + * Constructs QCustom3DVolume with given \a position, \a scaling, \a rotation, + * \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat, + * \a colorTable, and optional \a parent. + * + * \sa textureData, setTextureFormat(), colorTable + */ +QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector<uchar> *textureData, QImage::Format textureFormat, + const QVector<QRgb> &colorTable, QObject *parent) : + QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth, + textureHeight, textureDepth, + textureData, textureFormat, colorTable), parent) +{ +} + + +/*! + * Destroys QCustom3DVolume. + */ +QCustom3DVolume::~QCustom3DVolume() +{ +} + +/*! \property QCustom3DVolume::textureWidth + * + * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth() + */ +void QCustom3DVolume::setTextureWidth(int value) +{ + if (value >= 0) { + if (dptr()->m_textureWidth != value) { + dptr()->m_textureWidth = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureWidthChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } +} + +int QCustom3DVolume::textureWidth() const +{ + return dptrc()->m_textureWidth; +} + +/*! \property QCustom3DVolume::textureHeight + * + * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureWidth, textureDepth, setTextureFormat() + */ +void QCustom3DVolume::setTextureHeight(int value) +{ + if (value >= 0) { + if (dptr()->m_textureHeight != value) { + dptr()->m_textureHeight = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureHeightChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } + +} + +int QCustom3DVolume::textureHeight() const +{ + return dptrc()->m_textureHeight; +} + +/*! \property QCustom3DVolume::textureDepth + * + * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureWidth, textureHeight, setTextureFormat() + */ +void QCustom3DVolume::setTextureDepth(int value) +{ + if (value >= 0) { + if (dptr()->m_textureDepth != value) { + dptr()->m_textureDepth = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureDepthChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } +} + +int QCustom3DVolume::textureDepth() const +{ + return dptrc()->m_textureDepth; +} + +/*! + * A convenience function for setting all three texture dimensions + * (\a width, \a height, and \a depth) at once. + * + * \sa textureData + */ +void QCustom3DVolume::setTextureDimensions(int width, int height, int depth) +{ + setTextureWidth(width); + setTextureHeight(height); + setTextureDepth(depth); +} + +/*! + * \return the actual texture data width. When the texture format is QImage::Format_Indexed8, + * this is textureWidth aligned to 32bit boundary. Otherwise this is four times textureWidth. + */ +int QCustom3DVolume::textureDataWidth() const +{ + int dataWidth = dptrc()->m_textureWidth; + + if (dptrc()->m_textureFormat == QImage::Format_Indexed8) + dataWidth += dataWidth % 4; + else + dataWidth *= 4; + + return dataWidth; +} + +/*! \property QCustom3DVolume::sliceIndexX + * + * The X dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexX(int value) +{ + if (dptr()->m_sliceIndexX != value) { + dptr()->m_sliceIndexX = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexXChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexX() const +{ + return dptrc()->m_sliceIndexX; +} + +/*! \property QCustom3DVolume::sliceIndexY + * + * The Y dimension index into the texture data indicating which horizontal slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexY(int value) +{ + if (dptr()->m_sliceIndexY != value) { + dptr()->m_sliceIndexY = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexYChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexY() const +{ + return dptrc()->m_sliceIndexY; +} + +/*! \property QCustom3DVolume::sliceIndexZ + * + * The Z dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexZ(int value) +{ + if (dptr()->m_sliceIndexZ != value) { + dptr()->m_sliceIndexZ = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexZChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexZ() const +{ + return dptrc()->m_sliceIndexZ; +} + +/*! + * A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once. + * + * \sa textureData + */ +void QCustom3DVolume::setSliceIndices(int x, int y, int z) +{ + setSliceIndexX(x); + setSliceIndexY(y); + setSliceIndexZ(z); +} + +/*! \property QCustom3DVolume::colorTable + * + * The array containing the colors for indexed texture formats. If the texture format is not + * indexed, this array is not used and can be empty. + * + * Defaults to \c{0}. + * + * \sa textureData, setTextureFormat(), QImage::colorTable() + */ +void QCustom3DVolume::setColorTable(const QVector<QRgb> &colors) +{ + if (dptr()->m_colorTable != colors) { + dptr()->m_colorTable = colors; + dptr()->m_dirtyBitsVolume.colorTableDirty = true; + emit colorTableChanged(); + emit dptr()->needUpdate(); + } +} + +QVector<QRgb> QCustom3DVolume::colorTable() const +{ + return dptrc()->m_colorTable; +} + +/*! \property QCustom3DVolume::textureData + * + * The array containing the texture data in the format specified by textureFormat. + * The size of this array must be at least + * (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}). + * + * A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size + * (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined by textureDepth + * property. Each 2D texture data is identical to a QImage data with the same format, so + * QImage::bits() can be used to supply the data for each subtexture. + * + * Ownership of the new array transfers to QCustom3DVolume instance. + * If another array is set, the previous array is deleted. + * If the same array is set again, it is assumed that the array contents have been changed and the + * graph rendering is triggered. + * + * \note Each X-line of the data needs to be 32bit aligned. If the textureFormat is + * QImage::Format_Indexed8 and textureWidth is not divisible by four, padding bytes need + * to be added to each X-line of the \a data. You can get the padded byte count with + * textureDataWidth() function. The padding bytes should indicate an fully transparent color + * to avoid rendering artifacts. + * + * Defaults to \c{0}. + * + * \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth() + */ +void QCustom3DVolume::setTextureData(QVector<uchar> *data) +{ + if (dptr()->m_textureData != data) + delete dptr()->m_textureData; + + // Even if the pointer is same as previously, consider this property changed, as the values + // can be changed unbeknownst to us via the array pointer. + dptr()->m_textureData = data; + dptr()->m_dirtyBitsVolume.textureDataDirty = true; + emit textureDataChanged(data); + emit dptr()->needUpdate(); +} + +/*! + * This function creates a new texture data array from an array of \a images and sets it as + * textureData for this volume object. The texture dimensions are also set according to image + * and array dimensions. All of the images in the array must be the same size. If the images are not + * all in QImage::Format_Indexed8 format, all texture data will be converted into + * QImage::Format_ARGB32 format. If the images are in QImage::Format_Indexed8 format, the colorTable + * for the entire volume will be taken from the first image. + * + * \return pointer to the newly created array. + * + * \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat() + */ +QVector<uchar> *QCustom3DVolume::createTextureData(const QVector<QImage *> &images) +{ + int imageCount = images.size(); + if (imageCount) { + QImage *currentImage = images.at(0); + int imageWidth = currentImage->width(); + int imageHeight = currentImage->height(); + QImage::Format imageFormat = currentImage->format(); + bool convert = false; + if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) { + convert = true; + imageFormat = QImage::Format_ARGB32; + } else { + for (int i = 0; i < imageCount; i++) { + currentImage = images.at(i); + if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) { + qWarning() << __FUNCTION__ << "Not all images were of the same size."; + setTextureData(0); + setTextureWidth(0); + setTextureHeight(0); + setTextureDepth(0); + return 0; + + } + if (currentImage->format() != imageFormat) { + convert = true; + imageFormat = QImage::Format_ARGB32; + break; + } + } + } + int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4; + int imageByteWidth = (imageFormat == QImage::Format_Indexed8) + ? currentImage->bytesPerLine() : imageWidth; + int frameSize = imageByteWidth * imageHeight * colorBytes; + QVector<uchar> *newTextureData = new QVector<uchar>; + newTextureData->resize(frameSize * imageCount); + uchar *texturePtr = newTextureData->data(); + QImage convertedImage; + + for (int i = 0; i < imageCount; i++) { + currentImage = images.at(i); + if (convert) { + convertedImage = currentImage->convertToFormat(imageFormat); + currentImage = &convertedImage; + } + memcpy(texturePtr, static_cast<void *>(currentImage->bits()), frameSize); + texturePtr += frameSize; + } + + if (imageFormat == QImage::Format_Indexed8) + setColorTable(images.at(0)->colorTable()); + setTextureData(newTextureData); + setTextureFormat(imageFormat); + setTextureWidth(imageWidth); + setTextureHeight(imageHeight); + setTextureDepth(imageCount); + } else { + setTextureData(0); + setTextureWidth(0); + setTextureHeight(0); + setTextureDepth(0); + } + return dptr()->m_textureData; +} + +QVector<uchar> *QCustom3DVolume::textureData() const +{ + return dptrc()->m_textureData; +} + +/*! + * This function allows setting a single 2D subtexture of the 3D texture along the specified + * \a axis of the volume. + * The \a index parameter specifies the subtexture to set. + * The texture \a data must be in the format specified by textureFormat property and have size of + * the cross-section of the volume texture along the specified axis multiplied by + * the texture format color depth in bytes. + * The \a data is expected to be ordered similarly to the data in images produced by renderSlice() + * method along the same axis. + * + * \note Each X-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a data in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. + * + * \sa textureData, renderSlice() + */ +void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data) +{ + if (data) { + int lineSize = textureDataWidth(); + int frameSize = lineSize * dptr()->m_textureHeight; + int dataSize = dptr()->m_textureData->size(); + int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4; + int targetIndex; + uchar *dataPtr = dptr()->m_textureData->data(); + bool invalid = (index < 0); + if (axis == Qt::XAxis) { + targetIndex = index * pixelWidth; + if (index >= dptr()->m_textureWidth + || (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) { + invalid = true; + } + } else if (axis == Qt::YAxis) { + targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1)); + if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize)) + invalid = true; + } else { + targetIndex = index * frameSize; + if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize)) + invalid = true; + } + + if (invalid) { + qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture."; + } else { + const uchar *sourcePtr = data; + uchar *targetPtr = dataPtr + targetIndex; + if (axis == Qt::XAxis) { + int targetWidth = dptr()->m_textureDepth; + int targetHeight = dptr()->m_textureHeight; + for (int i = 0; i < targetHeight; i++) { + targetPtr = dataPtr + targetIndex + (lineSize * i); + for (int j = 0; j < targetWidth; j++) { + for (int k = 0; k < pixelWidth; k++) + *targetPtr++ = *sourcePtr++; + targetPtr += (frameSize - pixelWidth); + } + } + } else if (axis == Qt::YAxis) { + int targetHeight = dptr()->m_textureDepth; + for (int i = 0; i < targetHeight; i++){ + for (int j = 0; j < lineSize; j++) + *targetPtr++ = *sourcePtr++; + targetPtr -= (frameSize + lineSize); + } + } else { + void *subTexPtr = dataPtr + targetIndex; + memcpy(subTexPtr, static_cast<const void *>(data), frameSize); + } + dptr()->m_dirtyBitsVolume.textureDataDirty = true; + emit textureDataChanged(dptr()->m_textureData); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Tried to set null data."; + } +} + +/*! + * This function allows setting a single 2D subtexture of the 3D texture along the specified + * \a axis of the volume. + * The \a index parameter specifies the subtexture to set. + * The source \a image must be in the format specified by the textureFormat property if the + * textureFormat is indexed. If the textureFormat is QImage::Format_ARGB32, the image is converted + * to that format. The image must have the size of the cross-section of the volume texture along + * the specified axis. The orientation of the image should correspond to the orientation of + * the slice image produced by renderSlice() method along the same axis. + * + * \note Each X-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a image in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. It is not guaranteed QImage will do this automatically. + * + * \sa textureData, renderSlice() + */ +void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image) +{ + int sourceWidth = image.width(); + int sourceHeight = image.height(); + int targetWidth; + int targetHeight; + if (axis == Qt::XAxis) { + targetWidth = dptr()->m_textureDepth; + targetHeight = dptr()->m_textureHeight; + } else if (axis == Qt::YAxis) { + targetWidth = dptr()->m_textureWidth; + targetHeight = dptr()->m_textureDepth; + } else { + targetWidth = dptr()->m_textureWidth; + targetHeight = dptr()->m_textureHeight; + } + + if (sourceWidth == targetWidth + && sourceHeight == targetHeight + && (image.format() == dptr()->m_textureFormat + || dptr()->m_textureFormat == QImage::Format_ARGB32)) { + QImage convertedImage; + if (dptr()->m_textureFormat == QImage::Format_ARGB32 + && image.format() != QImage::Format_ARGB32) { + convertedImage = image.convertToFormat(QImage::Format_ARGB32); + } else { + convertedImage = image; + } + setSubTextureData(axis, index, convertedImage.bits()); + } else { + qWarning() << __FUNCTION__ << "Invalid image size or format."; + } +} + +// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that +// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem. + +/*! + * Sets the format of the textureData to \a format. Only two formats are supported currently: + * QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable + * must also be set. + * Defaults to QImage::Format_ARGB32. + * + * \sa colorTable, textureData + */ +void QCustom3DVolume::setTextureFormat(QImage::Format format) +{ + if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) { + if (dptr()->m_textureFormat != format) { + dptr()->m_textureFormat = format; + dptr()->m_dirtyBitsVolume.textureFormatDirty = true; + emit textureFormatChanged(format); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Attempted to set invalid texture format."; + } +} + +/*! + * \return the format of the textureData. + * + * \sa setTextureFormat() + */ +QImage::Format QCustom3DVolume::textureFormat() const +{ + return dptrc()->m_textureFormat; +} + +/*! + * \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format) + * + * This signal is emitted when the textureData \a format changes. + * + * \sa setTextureFormat() + */ + +/*! + * \property QCustom3DVolume::alphaMultiplier + * + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0f}. + * + * \sa preserveOpacity, textureData + */ +void QCustom3DVolume::setAlphaMultiplier(float mult) +{ + if (mult >= 0.0f) { + if (dptr()->m_alphaMultiplier != mult) { + dptr()->m_alphaMultiplier = mult; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit alphaMultiplierChanged(mult); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Attempted to set negative multiplier."; + } +} + +float QCustom3DVolume::alphaMultiplier() const +{ + return dptrc()->m_alphaMultiplier; +} + +/*! + * \property QCustom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ +void QCustom3DVolume::setPreserveOpacity(bool enable) +{ + if (dptr()->m_preserveOpacity != enable) { + dptr()->m_preserveOpacity = enable; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit preserveOpacityChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::preserveOpacity() const +{ + return dptrc()->m_preserveOpacity; +} + +/*! + * \property QCustom3DVolume::useHighDefShader + * + * If this property value is \c{true}, a high definition shader is used to render the volume. + * If it is \c{false}, a low definition shader is used. + * + * The high definition shader guarantees that every visible texel of the volume texture is sampled + * when the volume is rendered. + * The low definition shader renders only a rough approximation of the volume contents, + * but at much higher frame rate. The low definition shader doesn't guarantee every texel of the + * volume texture is sampled, so there may be flickering if the volume contains distinct thin + * features. + * + * \note This value doesn't affect the level of detail when rendering the slices of the volume. + * + * Defaults to \c{true}. + * + * \sa renderSlice() + */ +void QCustom3DVolume::setUseHighDefShader(bool enable) +{ + if (dptr()->m_useHighDefShader != enable) { + dptr()->m_useHighDefShader = enable; + dptr()->m_dirtyBitsVolume.shaderDirty = true; + emit useHighDefShaderChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::useHighDefShader() const +{ + return dptrc()->m_useHighDefShader; +} + +/*! + * \property QCustom3DVolume::drawSlices + * + * If this property value is \c{true}, the slices indicated by slice index properties + * will be drawn instead of the full volume. + * If it is \c{false}, the full volume will always be drawn. + * Defaults to \c{false}. + * + * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are + * rotated as well. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ + */ +void QCustom3DVolume::setDrawSlices(bool enable) +{ + if (dptr()->m_drawSlices != enable) { + dptr()->m_drawSlices = enable; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit drawSlicesChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::drawSlices() const +{ + return dptrc()->m_drawSlices; +} + +/*! + * \property QCustom3DVolume::drawSliceFrames + * + * If this property value is \c{true}, the frames of slices indicated by slice index properties + * will be drawn around the volume. + * If it is \c{false}, no slice frames will be drawn. + * Drawing slice frames is independent of drawing slices, so you can show the full volume and + * still draw the slice frames around it. This is useful when using renderSlice() to display the + * slices outside the graph itself. + * Defaults to \c{false}. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice() + */ +void QCustom3DVolume::setDrawSliceFrames(bool enable) +{ + if (dptr()->m_drawSliceFrames != enable) { + dptr()->m_drawSliceFrames = enable; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit drawSliceFramesChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::drawSliceFrames() const +{ + return dptrc()->m_drawSliceFrames; +} + +/*! + * \property QCustom3DVolume::sliceFrameColor + * + * Indicates the color of the slice frame. Transparent slice frame color is not supported. + * + * Defaults to black. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameColor(const QColor &color) +{ + if (dptr()->m_sliceFrameColor != color) { + dptr()->m_sliceFrameColor = color; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameColorChanged(color); + emit dptr()->needUpdate(); + } +} + +QColor QCustom3DVolume::sliceFrameColor() const +{ + return dptrc()->m_sliceFrameColor; +} + +/*! + * \property QCustom3DVolume::sliceFrameWidths + * + * Indicates the widths of the slice frame. The width can be different on different dimensions, + * so you can for example omit drawing the frames on certain sides of the volume by setting the + * value for that dimension to zero. The values are fractions of the volume thickness in the same + * dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameWidths != values) { + dptr()->m_sliceFrameWidths = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameWidthsChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameWidths() const +{ + return dptrc()->m_sliceFrameWidths; +} + +/*! + * \property QCustom3DVolume::sliceFrameGaps + * + * Indicates the amount of air gap left between the volume itself and the frame in each dimension. + * The gap can be different on different dimensions. The values are fractions of the volume + * thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameGaps != values) { + dptr()->m_sliceFrameGaps = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameGapsChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameGaps() const +{ + return dptrc()->m_sliceFrameGaps; +} + +/*! + * \property QCustom3DVolume::sliceFrameThicknesses + * + * Indicates the thickness of the slice frames for each dimension. The values are fractions of + * the volume thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameThicknesses != values) { + dptr()->m_sliceFrameThicknesses = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameThicknessesChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameThicknesses() const +{ + return dptrc()->m_sliceFrameThicknesses; +} + +/*! + * Renders the slice specified by \a index along \a axis into an image. + * The texture format of this object is used. + * + * \return the rendered image of the slice, or a null image if invalid index is specified. + * + * \sa setTextureFormat() + */ +QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index) +{ + return dptr()->renderSlice(axis, index); +} + +/*! + * \internal + */ +QCustom3DVolumePrivate *QCustom3DVolume::dptr() +{ + return static_cast<QCustom3DVolumePrivate *>(d_ptr.data()); +} + +/*! + * \internal + */ +const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const +{ + return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data()); +} + +QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) : + QCustom3DItemPrivate(q), + m_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureFormat(QImage::Format_ARGB32), + m_textureData(0), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false), + m_sliceFrameColor(Qt::black), + m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f)) +{ + m_isVolumeItem = true; + m_meshFile = QStringLiteral(":/defaultMeshes/barFull"); +} + +QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position, + const QVector3D &scaling, + const QQuaternion &rotation, + int textureWidth, int textureHeight, + int textureDepth, QVector<uchar> *textureData, + QImage::Format textureFormat, + const QVector<QRgb> &colorTable) : + QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling, rotation), + m_textureWidth(textureWidth), + m_textureHeight(textureHeight), + m_textureDepth(textureDepth), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureFormat(textureFormat), + m_colorTable(colorTable), + m_textureData(textureData), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false), + m_sliceFrameColor(Qt::black), + m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f)) +{ + m_isVolumeItem = true; + m_shadowCasting = false; + + if (m_textureWidth < 0) + m_textureWidth = 0; + if (m_textureHeight < 0) + m_textureHeight = 0; + if (m_textureDepth < 0) + m_textureDepth = 0; + + if (m_textureFormat != QImage::Format_Indexed8) + m_textureFormat = QImage::Format_ARGB32; + +} + +QCustom3DVolumePrivate::~QCustom3DVolumePrivate() +{ + delete m_textureData; +} + +void QCustom3DVolumePrivate::resetDirtyBits() +{ + QCustom3DItemPrivate::resetDirtyBits(); + + m_dirtyBitsVolume.textureDimensionsDirty = false; + m_dirtyBitsVolume.slicesDirty = false; + m_dirtyBitsVolume.colorTableDirty = false; + m_dirtyBitsVolume.textureDataDirty = false; + m_dirtyBitsVolume.textureFormatDirty = false; + m_dirtyBitsVolume.alphaDirty = false; + m_dirtyBitsVolume.shaderDirty = false; +} + +QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index) +{ + if (index < 0) + return QImage(); + + int x; + int y; + if (axis == Qt::XAxis) { + if (index >= m_textureWidth) + return QImage(); + x = m_textureDepth; + y = m_textureHeight; + } else if (axis == Qt::YAxis) { + if (index >= m_textureHeight) + return QImage(); + x = m_textureWidth; + y = m_textureDepth; + } else { + if (index >= m_textureDepth) + return QImage(); + x = m_textureWidth; + y = m_textureHeight; + } + + int padding = 0; + int pixelWidth = 4; + int dataWidth = qptr()->textureDataWidth(); + if (m_textureFormat == QImage::Format_Indexed8) { + padding = x % 4; + pixelWidth = 1; + } + QVector<uchar> data((x + padding) * y * pixelWidth); + int frameSize = qptr()->textureDataWidth() * m_textureHeight; + + int dataIndex = 0; + if (axis == Qt::XAxis) { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + + (index * pixelWidth) + (dataWidth * i); + for (int j = 0; j < x; j++) { + for (int k = 0; k < pixelWidth; k++) + data[dataIndex++] = *(p + k); + p += frameSize; + } + } + } else if (axis == Qt::YAxis) { + for (int i = y - 1; i >= 0; i--) { + const uchar *p = m_textureData->constData() + (index * dataWidth) + + (frameSize * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } else { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } + + if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) { + for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth) + data[i] = static_cast<uchar>(multipliedAlphaValue(data.at(i))); + } + + QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat); + image.bits(); // Call bits() to detach the new image from local data + if (m_textureFormat == QImage::Format_Indexed8) { + QVector<QRgb> colorTable = m_colorTable; + if (m_alphaMultiplier != 1.0f) { + for (int i = 0; i < colorTable.size(); i++) { + QRgb curCol = colorTable.at(i); + int alpha = multipliedAlphaValue(qAlpha(curCol)); + if (alpha != qAlpha(curCol)) + colorTable[i] = qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), alpha); + } + } + image.setColorTable(colorTable); + } + + return image; +} + +int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha) +{ + int modifiedAlpha = alpha; + if (!m_preserveOpacity || alpha != 255) { + modifiedAlpha = int(m_alphaMultiplier * float(alpha)); + modifiedAlpha = qMin(modifiedAlpha, 255); + } + return modifiedAlpha; +} + +QCustom3DVolume *QCustom3DVolumePrivate::qptr() +{ + return static_cast<QCustom3DVolume *>(q_ptr); +} + +QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/qcustom3dvolume.h b/src/datavisualization/data/qcustom3dvolume.h new file mode 100644 index 00000000..a9dfda86 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +#ifndef QCUSTOM3DVOLUME_H +#define QCUSTOM3DVOLUME_H + +#include <QtDataVisualization/qdatavisualizationglobal.h> +#include <QtDataVisualization/QCustom3DItem> +#include <QtGui/QColor> +#include <QtGui/QImage> + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +class QCustom3DVolumePrivate; + +class QT_DATAVISUALIZATION_EXPORT QCustom3DVolume : public QCustom3DItem +{ + Q_OBJECT + Q_PROPERTY(int textureWidth READ textureWidth WRITE setTextureWidth NOTIFY textureWidthChanged) + Q_PROPERTY(int textureHeight READ textureHeight WRITE setTextureHeight NOTIFY textureHeightChanged) + Q_PROPERTY(int textureDepth READ textureDepth WRITE setTextureDepth NOTIFY textureDepthChanged) + Q_PROPERTY(int sliceIndexX READ sliceIndexX WRITE setSliceIndexX NOTIFY sliceIndexXChanged) + Q_PROPERTY(int sliceIndexY READ sliceIndexY WRITE setSliceIndexY NOTIFY sliceIndexYChanged) + Q_PROPERTY(int sliceIndexZ READ sliceIndexZ WRITE setSliceIndexZ NOTIFY sliceIndexZChanged) + Q_PROPERTY(QVector<QRgb> colorTable READ colorTable WRITE setColorTable NOTIFY colorTableChanged) + Q_PROPERTY(QVector<uchar> *textureData READ textureData WRITE setTextureData NOTIFY textureDataChanged) + Q_PROPERTY(float alphaMultiplier READ alphaMultiplier WRITE setAlphaMultiplier NOTIFY alphaMultiplierChanged) + Q_PROPERTY(bool preserveOpacity READ preserveOpacity WRITE setPreserveOpacity NOTIFY preserveOpacityChanged) + Q_PROPERTY(bool useHighDefShader READ useHighDefShader WRITE setUseHighDefShader NOTIFY useHighDefShaderChanged) + Q_PROPERTY(bool drawSlices READ drawSlices WRITE setDrawSlices NOTIFY drawSlicesChanged) + Q_PROPERTY(bool drawSliceFrames READ drawSliceFrames WRITE setDrawSliceFrames NOTIFY drawSliceFramesChanged) + Q_PROPERTY(QColor sliceFrameColor READ sliceFrameColor WRITE setSliceFrameColor NOTIFY sliceFrameColorChanged) + Q_PROPERTY(QVector3D sliceFrameWidths READ sliceFrameWidths WRITE setSliceFrameWidths NOTIFY sliceFrameWidthsChanged) + Q_PROPERTY(QVector3D sliceFrameGaps READ sliceFrameGaps WRITE setSliceFrameGaps NOTIFY sliceFrameGapsChanged) + Q_PROPERTY(QVector3D sliceFrameThicknesses READ sliceFrameThicknesses WRITE setSliceFrameThicknesses NOTIFY sliceFrameThicknessesChanged) + +public: + + explicit QCustom3DVolume(QObject *parent = 0); + explicit QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector<uchar> *textureData, QImage::Format textureFormat, + const QVector<QRgb> &colorTable, QObject *parent = 0); + virtual ~QCustom3DVolume(); + + void setTextureWidth(int value); + int textureWidth() const; + void setTextureHeight(int value); + int textureHeight() const; + void setTextureDepth(int value); + int textureDepth() const; + void setTextureDimensions(int width, int height, int depth); + int textureDataWidth() const; + + void setSliceIndexX(int value); + int sliceIndexX() const; + void setSliceIndexY(int value); + int sliceIndexY() const; + void setSliceIndexZ(int value); + int sliceIndexZ() const; + void setSliceIndices(int x, int y, int z); + + void setColorTable(const QVector<QRgb> &colors); + QVector<QRgb> colorTable() const; + + void setTextureData(QVector<uchar> *data); + QVector<uchar> *createTextureData(const QVector<QImage *> &images); + QVector<uchar> *textureData() const; + void setSubTextureData(Qt::Axis axis, int index, const uchar *data); + void setSubTextureData(Qt::Axis axis, int index, const QImage &image); + + void setTextureFormat(QImage::Format format); + QImage::Format textureFormat() const; + + void setAlphaMultiplier(float mult); + float alphaMultiplier() const; + void setPreserveOpacity(bool enable); + bool preserveOpacity() const; + + void setUseHighDefShader(bool enable); + bool useHighDefShader() const; + + void setDrawSlices(bool enable); + bool drawSlices() const; + void setDrawSliceFrames(bool enable); + bool drawSliceFrames() const; + + void setSliceFrameColor(const QColor &color); + QColor sliceFrameColor() const; + void setSliceFrameWidths(const QVector3D &values); + QVector3D sliceFrameWidths() const; + void setSliceFrameGaps(const QVector3D &values); + QVector3D sliceFrameGaps() const; + void setSliceFrameThicknesses(const QVector3D &values); + QVector3D sliceFrameThicknesses() const; + + QImage renderSlice(Qt::Axis axis, int index); + +signals: + void textureWidthChanged(int value); + void textureHeightChanged(int value); + void textureDepthChanged(int value); + void sliceIndexXChanged(int value); + void sliceIndexYChanged(int value); + void sliceIndexZChanged(int value); + void colorTableChanged(); + void textureDataChanged(QVector<uchar> *data); + void textureFormatChanged(QImage::Format format); + void alphaMultiplierChanged(float mult); + void preserveOpacityChanged(bool enabled); + void useHighDefShaderChanged(bool enabled); + void drawSlicesChanged(bool enabled); + void drawSliceFramesChanged(bool enabled); + void sliceFrameColorChanged(const QColor &color); + void sliceFrameWidthsChanged(const QVector3D &values); + void sliceFrameGapsChanged(const QVector3D &values); + void sliceFrameThicknessesChanged(const QVector3D &values); + +protected: + QCustom3DVolumePrivate *dptr(); + const QCustom3DVolumePrivate *dptrc() const; + +private: + Q_DISABLE_COPY(QCustom3DVolume) + + friend class Abstract3DRenderer; +}; + +QT_END_NAMESPACE_DATAVISUALIZATION + +#endif diff --git a/src/datavisualization/data/qcustom3dvolume_p.h b/src/datavisualization/data/qcustom3dvolume_p.h new file mode 100644 index 00000000..642d2af5 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtDataVisualization API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCUSTOM3DVOLUME_P_H +#define QCUSTOM3DVOLUME_P_H + +#include "qcustom3dvolume.h" +#include "qcustom3ditem_p.h" + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +struct QCustomVolumeDirtyBitField { + bool textureDimensionsDirty : 1; + bool slicesDirty : 1; + bool colorTableDirty : 1; + bool textureDataDirty : 1; + bool textureFormatDirty : 1; + bool alphaDirty : 1; + bool shaderDirty : 1; + + QCustomVolumeDirtyBitField() + : textureDimensionsDirty(false), + slicesDirty(false), + colorTableDirty(false), + textureDataDirty(false), + textureFormatDirty(false), + alphaDirty(false), + shaderDirty(false) + { + } +}; + +class QCustom3DVolumePrivate : public QCustom3DItemPrivate +{ + Q_OBJECT + +public: + QCustom3DVolumePrivate(QCustom3DVolume *q); + QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, QVector<uchar> *textureData, + QImage::Format textureFormat, const QVector<QRgb> &colorTable); + virtual ~QCustom3DVolumePrivate(); + + void resetDirtyBits(); + QImage renderSlice(Qt::Axis axis, int index); + + QCustom3DVolume *qptr(); + +public: + int m_textureWidth; + int m_textureHeight; + int m_textureDepth; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + + QImage::Format m_textureFormat; + QVector<QRgb> m_colorTable; + QVector<uchar> *m_textureData; + + float m_alphaMultiplier; + bool m_preserveOpacity; + bool m_useHighDefShader; + + bool m_drawSlices; + bool m_drawSliceFrames; + QColor m_sliceFrameColor; + QVector3D m_sliceFrameWidths; + QVector3D m_sliceFrameGaps; + QVector3D m_sliceFrameThicknesses; + + QCustomVolumeDirtyBitField m_dirtyBitsVolume; + +private: + int multipliedAlphaValue(int alpha); + + friend class QCustom3DVolume; +}; + +QT_END_NAMESPACE_DATAVISUALIZATION + +#endif diff --git a/src/datavisualization/data/qheightmapsurfacedataproxy.cpp b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp index d64046be..85aed432 100644 --- a/src/datavisualization/data/qheightmapsurfacedataproxy.cpp +++ b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp @@ -84,7 +84,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::minXValue * - * The minimum X value for the generated surface points. + * The minimum X value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -92,7 +92,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue * - * The maximum X value for the generated surface points. + * The maximum X value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -100,7 +100,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::minZValue * - * The minimum Z value for the generated surface points. + * The minimum Z value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -108,7 +108,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue * - * The maximum Z value for the generated surface points. + * The maximum Z value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -228,7 +228,7 @@ void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float mi /*! * \property QHeightMapSurfaceDataProxy::minXValue * - * The minimum X value for the generated surface points. + * The minimum X value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -245,7 +245,7 @@ float QHeightMapSurfaceDataProxy::minXValue() const /*! * \property QHeightMapSurfaceDataProxy::maxXValue * - * The maximum X value for the generated surface points. + * The maximum X value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -262,7 +262,7 @@ float QHeightMapSurfaceDataProxy::maxXValue() const /*! * \property QHeightMapSurfaceDataProxy::minZValue * - * The minimum Z value for the generated surface points. + * The minimum Z value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -279,7 +279,7 @@ float QHeightMapSurfaceDataProxy::minZValue() const /*! * \property QHeightMapSurfaceDataProxy::maxZValue * - * The maximum Z value for the generated surface points. + * The maximum Z value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ diff --git a/src/datavisualization/data/qsurface3dseries.cpp b/src/datavisualization/data/qsurface3dseries.cpp index 1107d721..7d4dacfe 100644 --- a/src/datavisualization/data/qsurface3dseries.cpp +++ b/src/datavisualization/data/qsurface3dseries.cpp @@ -141,6 +141,14 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty string Surface3DSeries::textureFile + * + * Holds the texture file name for the surface texture. To clear the texture, set empty + * file name. + */ + + +/*! * \enum QSurface3DSeries::DrawFlag * * Drawing mode of the surface. Values of this enumeration can be combined with OR operator. @@ -300,6 +308,57 @@ QSurface3DSeries::DrawFlags QSurface3DSeries::drawMode() const } /*! + * \property QSurface3DSeries::texture + * + * Set the \a texture as a QImage for the surface. To clear the texture, set empty + * QImage as texture. + */ +void QSurface3DSeries::setTexture(const QImage &texture) +{ + if (dptr()->m_texture != texture) { + dptr()->setTexture(texture); + + emit textureChanged(texture); + dptr()->m_textureFile.clear(); + } +} + +QImage QSurface3DSeries::texture() const +{ + return dptrc()->m_texture; +} + +/*! + * \property QSurface3DSeries::textureFile + * + * Holds the texture file name for the surface texture. To clear the texture, set empty + * file name. + */ +void QSurface3DSeries::setTextureFile(const QString &filename) +{ + if (dptr()->m_textureFile != filename) { + if (filename.isEmpty()) { + setTexture(QImage()); + } else { + QImage image(filename); + if (image.isNull()) { + qWarning() << "Warning: Tried to set invalid image file as surface texture."; + return; + } + setTexture(image); + } + + dptr()->m_textureFile = filename; + emit textureFileChanged(filename); + } +} + +QString QSurface3DSeries::textureFile() const +{ + return dptrc()->m_textureFile; +} + +/*! * \internal */ QSurface3DSeriesPrivate *QSurface3DSeries::dptr() @@ -445,4 +504,11 @@ void QSurface3DSeriesPrivate::setDrawMode(QSurface3DSeries::DrawFlags mode) } } +void QSurface3DSeriesPrivate::setTexture(const QImage &texture) +{ + m_texture = texture; + if (static_cast<Surface3DController *>(m_controller)) + static_cast<Surface3DController *>(m_controller)->updateSurfaceTexture(qptr()); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/qsurface3dseries.h b/src/datavisualization/data/qsurface3dseries.h index 64df7202..7051e583 100644 --- a/src/datavisualization/data/qsurface3dseries.h +++ b/src/datavisualization/data/qsurface3dseries.h @@ -35,6 +35,8 @@ class QT_DATAVISUALIZATION_EXPORT QSurface3DSeries : public QAbstract3DSeries Q_PROPERTY(bool flatShadingEnabled READ isFlatShadingEnabled WRITE setFlatShadingEnabled NOTIFY flatShadingEnabledChanged) Q_PROPERTY(bool flatShadingSupported READ isFlatShadingSupported NOTIFY flatShadingSupportedChanged) Q_PROPERTY(DrawFlags drawMode READ drawMode WRITE setDrawMode NOTIFY drawModeChanged) + Q_PROPERTY(QImage texture READ texture WRITE setTexture NOTIFY textureChanged) + Q_PROPERTY(QString textureFile READ textureFile WRITE setTextureFile NOTIFY textureFileChanged) public: enum DrawFlag { @@ -63,12 +65,19 @@ public: bool isFlatShadingSupported() const; + void setTexture(const QImage &texture); + QImage texture() const; + void setTextureFile(const QString &filename); + QString textureFile() const; + signals: void dataProxyChanged(QSurfaceDataProxy *proxy); void selectedPointChanged(const QPoint &position); void flatShadingEnabledChanged(bool enable); void flatShadingSupportedChanged(bool enable); void drawModeChanged(QSurface3DSeries::DrawFlags mode); + void textureChanged(const QImage &image); + void textureFileChanged(const QString &filename); protected: explicit QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent = 0); diff --git a/src/datavisualization/data/qsurface3dseries_p.h b/src/datavisualization/data/qsurface3dseries_p.h index d4cc2820..270a3ad1 100644 --- a/src/datavisualization/data/qsurface3dseries_p.h +++ b/src/datavisualization/data/qsurface3dseries_p.h @@ -48,6 +48,7 @@ public: void setSelectedPoint(const QPoint &position); void setFlatShadingEnabled(bool enabled); void setDrawMode(QSurface3DSeries::DrawFlags mode); + void setTexture(const QImage &texture); private: QSurface3DSeries *qptr(); @@ -55,6 +56,8 @@ private: QPoint m_selectedPoint; bool m_flatShadingEnabled; QSurface3DSeries::DrawFlags m_drawMode; + QImage m_texture; + QString m_textureFile; private: friend class QSurface3DSeries; diff --git a/src/datavisualization/data/qsurfacedataproxy.cpp b/src/datavisualization/data/qsurfacedataproxy.cpp index dbe7cc49..12b24465 100644 --- a/src/datavisualization/data/qsurfacedataproxy.cpp +++ b/src/datavisualization/data/qsurfacedataproxy.cpp @@ -53,6 +53,10 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \note Surfaces with less than two rows or columns are not considered valid surfaces and will * not be rendered. * + * \note On some environments, surfaces with a lot of visible vertices may not render, because + * they exceed the per-draw vertex count supported by the graphics driver. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * * \sa {Qt Data Visualization Data Handling} */ diff --git a/src/datavisualization/datavisualization.pro b/src/datavisualization/datavisualization.pro index 44f0f60c..645a1733 100644 --- a/src/datavisualization/datavisualization.pro +++ b/src/datavisualization/datavisualization.pro @@ -1,5 +1,5 @@ # Target can't start with 'Qt' as it gets major version number inserted into it in that case, -# which we don't want. Exception is mac bundles, where the target name must match the module name +# which we don't want. Exception is OS X bundles, where the target name must match the module name mac:CONFIG(shared, static|shared):contains(QT_CONFIG, qt_framework) { TARGET = QtDataVisualization } else { @@ -7,7 +7,8 @@ mac:CONFIG(shared, static|shared):contains(QT_CONFIG, qt_framework) { } message($$QT_CONFIG) -QT = core gui +QT += core gui +osx: QT += gui-private DEFINES += QT_DATAVISUALIZATION_LIBRARY # Fix exports in static builds for applications linking datavisualization module @@ -19,9 +20,8 @@ QMAKE_DOCS = $$PWD/doc/qtdatavisualization.qdocconf load(qt_module) -include($$PWD/common.pri) -include($$PWD/engine/engine.pri) include($$PWD/global/global.pri) +include($$PWD/engine/engine.pri) include($$PWD/utils/utils.pri) include($$PWD/theme/theme.pri) include($$PWD/axis/axis.pri) diff --git a/src/datavisualization/doc/qtdatavisualization.qdocconf b/src/datavisualization/doc/qtdatavisualization.qdocconf index 6e9f09b4..4d63b526 100644 --- a/src/datavisualization/doc/qtdatavisualization.qdocconf +++ b/src/datavisualization/doc/qtdatavisualization.qdocconf @@ -6,7 +6,7 @@ include($QT_INSTALL_DOCS/global/qt-html-templates-offline.qdocconf) project = QtDataVisualization description = Qt Data Visualization Reference Documentation -version = 1.1.0 +version = 1.2.0 exampledirs += ../../../examples/datavisualization \ snippets @@ -27,14 +27,14 @@ indexes += $QT_INSTALL_DOCS/qtcore/qtcore.index \ qhp.projects = QtDataVisualization qhp.QtDataVisualization.file = qtdatavisualization.qhp -qhp.QtDataVisualization.namespace = com.digia.qtdatavisualization.110 +qhp.QtDataVisualization.namespace = com.digia.qtdatavisualization.120 qhp.QtDataVisualization.virtualFolder = qtdatavisualization qhp.QtDataVisualization.indexTitle = Qt Data Visualization qhp.QtDataVisualization.indexRoot = -qhp.QtDataVisualization.filterAttributes = qtdatavisualization 1.1.0 qtrefdoc -qhp.QtDataVisualization.customFilters.Qt.name = QtDataVisualization 1.1.0 -qhp.QtDataVisualization.customFilters.Qt.filterAttributes = qtdatavisualization 1.1.0 +qhp.QtDataVisualization.filterAttributes = qtdatavisualization 1.2.0 qtrefdoc +qhp.QtDataVisualization.customFilters.Qt.name = QtDataVisualization 1.2.0 +qhp.QtDataVisualization.customFilters.Qt.filterAttributes = qtdatavisualization 1.2.0 qhp.QtDataVisualization.subprojects = gettingstarted examples classes types qhp.QtDataVisualization.subprojects.gettingstarted.title = Getting Started qhp.QtDataVisualization.subprojects.gettingstarted.indexTitle = Qt Data Visualization Getting Started @@ -50,7 +50,7 @@ qhp.QtDataVisualization.subprojects.classes.selectors = class qhp.QtDataVisualization.subprojects.classes.sortPages = true qhp.QtDataVisualization.subprojects.types.title = QML Types qhp.QtDataVisualization.subprojects.types.indexTitle = Qt Data Visualization QML Types -qhp.QtDataVisualization.subprojects.types.selectors = fake:qmlclass +qhp.QtDataVisualization.subprojects.types.selectors = qmlclass qhp.QtDataVisualization.subprojects.types.sortPages = true navigation.landingpage = Qt Data Visualization diff --git a/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp b/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp index 56bfc5ee..1ca3f597 100644 --- a/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp +++ b/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp @@ -17,12 +17,12 @@ ****************************************************************************/ //! [0] -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 //! [0] //! [1] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 @@ -61,7 +61,7 @@ Item { //! [2] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 @@ -94,7 +94,7 @@ Item { //! [3] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc index 2cc3eece..b5a678e5 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc @@ -153,6 +153,32 @@ */ /*! + * \qmlproperty bool AbstractGraph3D::polar + * \since QtDataVisualization 1.2 + * + * If \c {true}, the horizontal axes are changed into polar axes. The X axis becomes the + * angular axis and the Z axis becomes the radial axis. + * Polar mode is not available for bar graphs. + * + * Defaults to \c{false}. + * + * \sa orthoProjection, radialLabelOffset + */ + +/*! + * \qmlproperty real AbstractGraph3D::radialLabelOffset + * \since QtDataVisualization 1.2 + * + * This property specifies the normalized horizontal offset for the axis labels of the radial + * polar axis. The value 0.0 indicates the labels should be drawn next to the 0-angle angular + * axis grid line. The value 1.0 indicates the labels are drawn on their normal place at the edge + * of the graph background. + * This property is ignored if polar property value is \c{false}. Defaults to 1.0. + * + * \sa polar + */ + +/*! * \qmlmethod void AbstractGraph3D::clearSelection() * Clears selection from all attached series. */ @@ -289,22 +315,123 @@ * \qmlproperty real AbstractGraph3D::aspectRatio * \since QtDataVisualization 1.1 * - * Aspect ratio of the graph data. This is the ratio of data scaling between horizontal and - * vertical axes. Defaults to \c{2.0}. + * The aspect ratio is the ratio of the graph scaling between the longest axis on the horizontal + * plane and the Y-axis. Defaults to \c{2.0}. * * \note Has no effect on Bars3D. + * + * \sa horizontalAspectRatio + */ + +/*! + * \qmlproperty real AbstractGraph3D::horizontalAspectRatio + * \since QtDataVisualization 1.2 + * + * The horizontal aspect ratio is the ratio of the graph scaling between the X and Z axes. + * Value of 0.0 indicates automatic scaling according to axis ranges. + * Defaults to \c{0.0}. + * + * \note Has no effect on Bars3D, which handles scaling on the horizontal plane via + * \l{Bars3D::barThickness}{barThickness} and \l{Bars3D::barSpacing}{barSpacing} properties. + * Polar graphs also ignore this property. + * + * \sa aspectRatio, polar, Bars3D::barThickness, Bars3D::barSpacing */ /*! * \qmlproperty AbstractGraph3D.OptimizationHints AbstractGraph3D::optimizationHints - * \since Qt Data Visualization 1.1 - * - * Defines if the rendering optimization is default or static. Default mode provides the full feature set at - * reasonable performance. Static is a beta level feature and currently supports only a subset of the - * features on the Scatter graph. Missing features are object gradient for mesh objects, both gradients - * for points, and diffuse and specular color on rotations. At this point static is intended just for - * introducing a new feature. It optimizes graph rendering and is ideal for large non-changing data - * sets. It is slower with dynamic data changes and item rotations. Selection is not optimized, so using it - * with massive data sets is not advisable. - * Defaults to \c{OptimizationDefault} + * \since QtDataVisualization 1.1 + * + * Defines if the rendering optimization is default or static. Default mode provides the full + * feature set at reasonable performance. Static mode optimizes graph rendering and is ideal for + * large non-changing data sets. It is slower with dynamic data changes and item rotations. + * Selection is not optimized, so using it with massive data sets is not advisable. + * Static works only on the Scatter graph. + * Defaults to \c{OptimizationDefault}. + * + * \note On some environments, large graphs using static optimization may not render, because + * all of the items are rendered using a single draw call, and different graphics drivers have + * different maximum vertice counts per call that they support. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * To work around this issue, choose an item mesh with low vertex count or use the point mesh. + * + * \sa Abstract3DSeries::mesh + */ + +/*! + * \qmlproperty bool AbstractGraph3D::reflection + * \since QtDataVisualization 1.2 + * + * Sets floor reflections on or off. Defaults to \c{false}. + * + * \note Affects only Bars3D. + * + * \note In Bars3D graphs holding both positive and negative values, reflections are not supported + * for custom items that intersect the floor plane. In that case, reflections should be turned off + * to avoid incorrect rendering. + * + * \sa reflectivity + */ + +/*! + * \qmlproperty real AbstractGraph3D::reflectivity + * \since QtDataVisualization 1.2 + * + * Adjusts floor reflectivity, larger number being more reflective. Valid range is \c{[0...1]}. + * Defaults to \c{0.5}. + * + * \note Affects only Bars3D. + * + * \sa reflection + */ + +/*! + * \qmlproperty locale AbstractGraph3D::locale + * \since QtDataVisualization 1.2 + * + * Sets the locale used for formatting various numeric labels. + * Defaults to \c{"C"} locale. + * + * \sa ValueAxis3D::labelFormat + */ + +/*! + * \qmlproperty vector3d AbstractGraph3D::queriedGraphPosition + * \since QtDataVisualization 1.2 + * + * This read-only property contains the latest graph position values along each axis queried using + * Scene3D::graphPositionQuery. The values are normalized to range \c{[-1, 1]}. + * If the queried position was outside the graph bounds, the values + * will not reflect the real position, but will instead be some undefined position outside + * the range \c{[-1, 1]}. The value will be undefined before any queries are made. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs only allow querying graph position at the graph floor level, + * so the Y-value is always zero for bar graphs and the valid queries can be only made at + * screen positions that contain the floor of the graph. + * + * \sa Scene3D::graphPositionQuery + */ + +/*! + * \qmlproperty real AbstractGraph3D::margin + * \since QtDataVisualization 1.2 + * + * This property contains the absolute value used for graph margin. The graph margin is the space + * left between the edge of the plottable graph area and the edge of the graph background. + * If the margin value is negative, the margins are determined automatically and can vary according + * to size of the items in the series and the type of the graph. + * The value is interpreted as a fraction of Y-axis range, provided the graph aspect ratios have + * not beed changed from the defaults. + * Defaults to \c{-1.0}. + * + * \note Having smaller than the automatically determined margin on scatter graph can cause + * the scatter items at the edges of the graph to overlap with the graph background. + * + * \note On scatter and surface graphs, if the margin is comparatively small to the axis label + * size, the positions of the edge labels of the axes are adjusted to avoid overlap with + * the edge labels of the neighboring axes. */ diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc index 6ee51742..0348652a 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc @@ -115,6 +115,14 @@ */ /*! + * \qmlproperty real Bars3D::floorLevel + * + * The desired floor level for the bar graph in Y-axis data coordinates. + * The actual floor level cannot go below Y-axis minimum or above Y-axis maximum. + * Defaults to zero. + */ + +/*! * \qmlmethod void Bars3D::addSeries(Bar3DSeries series) * Adds the \a series to the graph. A graph can contain multiple series, but only one set of axes, * so the rows and columns of all series must match for the visualized data to be meaningful. diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc index 23a9a004..2b83b807 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc @@ -73,11 +73,6 @@ */ /*! - \qmlproperty ColorGradient Surface3D::gradient - The current surface gradient. Setting this property replaces the previous gradient. - */ - -/*! * \qmlproperty list<Surface3DSeries> Surface3D::seriesList * \default * This property holds the series of the graph. @@ -86,6 +81,22 @@ */ /*! + * \qmlproperty bool Surface3D::flipHorizontalGrid + * \since QtDataVisualization 1.2 + * + * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more + * useful to display the horizontal axis grid on top of the graph rather than on the bottom. + * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with + * a top-down viewpoint. + * + * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background + * of the graph. + * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph + * from the horizontal background. + * Defaults to \c{false}. + */ + +/*! * \qmlmethod void Surface3D::addSeries(Surface3DSeries series) * Adds the \a series to the graph. */ diff --git a/src/datavisualization/doc/src/qtdatavisualization.qdoc b/src/datavisualization/doc/src/qtdatavisualization.qdoc index af419814..b07074b1 100644 --- a/src/datavisualization/doc/src/qtdatavisualization.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization.qdoc @@ -37,7 +37,7 @@ */ /*! - \qmlmodule QtDataVisualization 1.1 + \qmlmodule QtDataVisualization 1.2 \title Qt Data Visualization QML Types \ingroup qmlmodules @@ -98,7 +98,7 @@ \row \li Windows (MSVC) \li nmake \row - \li OSX \li make + \li OS X \li make \endtable The above generates the default makefiles for your configuration, which is typically @@ -127,7 +127,7 @@ make release \endcode - For both builds (Windows/Mac only): + For both builds (Windows/OS X only): \code qmake CONFIG+="debug_and_release build_all" make @@ -323,20 +323,21 @@ \title Qt Data Visualization Known Issues \list - \li Android doesn't support both widgets and OpenGL simultaneously, so only - the Qt Quick 2 API is usable in practice in Android. + \li Some platforms like Android and WinRT cannot handle multiple native windows properly, + so only the Qt Quick 2 graphs are available in practice for those platforms. \li Shadows are not supported with OpenGL ES2 (including Angle builds in Windows). \li Anti-aliasing doesn't work with OpenGL ES2 (including Angle builds in Windows). + \li QCustom3DVolume items are not supported with OpenGL ES2 (including Angle builds in + Windows). \li Surfaces with non-straight rows and columns do not always render properly. \li Q3DLight class (and Light3D QML item) are currently not usable for anything. - \li Changing any of Q3DScene properties affecting subviewports currently has no effect. - \li The color style Q3DTheme::ColorStyleObjectGradient doesn't work for surface graphs. + \li Changing most of Q3DScene properties affecting subviewports currently has no effect. \li Widget based examples layout incorrectly in iOS. \li Reparenting a graph to an item in another QQuickWindow is not supported. - \li There is a low-impact binary break between 1.0 and 1.1. The break is due to a QML type - registration conflict with QAbstractItemModel between QtDataVisualization and - QtCommercial.Charts. Introducing the binary break makes it possible to use both - Charts and Data Visualization in the same QML application. + \li Android builds of QML applications importing QtDataVisualization also require + "QT += datavisualization" in the pro file. This is because Qt Data Visualization + QML plugin has a dependency to Qt Data Visualization C++ library, which Qt Creator + doesn't automatically add to the deployment package. \endlist */ diff --git a/src/datavisualization/engine/abstract3dcontroller.cpp b/src/datavisualization/engine/abstract3dcontroller.cpp index 30434dca..275d0fe2 100644 --- a/src/datavisualization/engine/abstract3dcontroller.cpp +++ b/src/datavisualization/engine/abstract3dcontroller.cpp @@ -25,6 +25,7 @@ #include "thememanager_p.h" #include "q3dtheme_p.h" #include "qcustom3ditem_p.h" +#include "utils_p.h" #include <QtCore/QThread> #include <QtGui/QOpenGLFramebufferObject> @@ -37,8 +38,12 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen m_selectionMode(QAbstract3DGraph::SelectionItem), m_shadowQuality(QAbstract3DGraph::ShadowQualityMedium), m_useOrthoProjection(false), - m_aspectRatio(2.0f), + m_aspectRatio(2.0), + m_horizontalAspectRatio(0.0), m_optimizationHints(QAbstract3DGraph::OptimizationDefault), + m_reflectionEnabled(false), + m_reflectivity(0.5), + m_locale(QLocale::c()), m_scene(scene), m_activeInputHandler(0), m_axisX(0), @@ -50,12 +55,19 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen m_isCustomItemDirty(true), m_isSeriesVisualsDirty(true), m_renderPending(false), + m_isPolar(false), + m_radialLabelOffset(1.0f), m_measureFps(false), m_numFrames(0), - m_currentFps(0.0) + m_currentFps(0.0), + m_clickedType(QAbstract3DGraph::ElementNone), + m_selectedLabelIndex(-1), + m_selectedCustomItemIndex(-1), + m_margin(-1.0) { if (!m_scene) m_scene = new Q3DScene; + m_scene->setParent(this); // Set initial theme Q3DTheme *defaultTheme = new Q3DTheme(Q3DTheme::ThemeQt); @@ -72,10 +84,6 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen inputHandler = new QTouch3DInputHandler(); inputHandler->d_ptr->m_isDefaultHandler = true; setActiveInputHandler(inputHandler); - connect(inputHandler, &QAbstract3DInputHandler::inputViewChanged, this, - &Abstract3DController::handleInputViewChanged); - connect(inputHandler, &QAbstract3DInputHandler::positionChanged, this, - &Abstract3DController::handleInputPositionChanged); connect(m_scene->d_ptr.data(), &Q3DScenePrivate::needRender, this, &Abstract3DController::emitNeedRender); } @@ -93,7 +101,7 @@ Abstract3DController::~Abstract3DController() void Abstract3DController::destroyRenderer() { // Renderer can be in another thread, don't delete it directly in that case - if (m_renderer && m_renderer->thread() != QThread::currentThread()) + if (m_renderer && m_renderer->thread() && m_renderer->thread() != this->thread()) m_renderer->deleteLater(); else delete m_renderer; @@ -107,6 +115,13 @@ void Abstract3DController::destroyRenderer() void Abstract3DController::setRenderer(Abstract3DRenderer *renderer) { m_renderer = renderer; + + // If renderer is created in different thread than controller, make sure renderer gets + // destroyed before the render thread finishes. + if (renderer->thread() != this->thread()) { + QObject::connect(renderer->thread(), &QThread::finished, this, + &Abstract3DController::destroyRenderer, Qt::DirectConnection); + } } void Abstract3DController::addSeries(QAbstract3DSeries *series) @@ -163,11 +178,14 @@ void Abstract3DController::synchDataToRenderer() { // Subclass implementations check for renderer validity already, so no need to check here. - // If there is a pending click from renderer, handle that first. - if (m_renderer->isClickPending()) { + m_renderPending = false; + + // If there are pending queries, handle those first + if (m_renderer->isGraphPositionQueryResolved()) + handlePendingGraphPositionQuery(); + + if (m_renderer->isClickQueryResolved()) handlePendingClick(); - m_renderer->clearClickPending(); - } startRecordingRemovesAndInserts(); @@ -176,6 +194,16 @@ void Abstract3DController::synchDataToRenderer() m_renderer->updateTheme(m_themeManager->activeTheme()); + if (m_changeTracker.polarChanged) { + m_renderer->updatePolar(m_isPolar); + m_changeTracker.polarChanged = false; + } + + if (m_changeTracker.radialLabelOffsetChanged) { + m_renderer->updateRadialLabelOffset(m_radialLabelOffset); + m_changeTracker.radialLabelOffsetChanged = false; + } + if (m_changeTracker.shadowQualityChanged) { m_renderer->updateShadowQuality(m_shadowQuality); m_changeTracker.shadowQualityChanged = false; @@ -192,15 +220,31 @@ void Abstract3DController::synchDataToRenderer() } if (m_changeTracker.aspectRatioChanged) { - m_renderer->updateAspectRatio(m_aspectRatio); + m_renderer->updateAspectRatio(float(m_aspectRatio)); m_changeTracker.aspectRatioChanged = false; } + if (m_changeTracker.horizontalAspectRatioChanged) { + m_renderer->updateHorizontalAspectRatio(float(m_horizontalAspectRatio)); + m_changeTracker.horizontalAspectRatioChanged = false; + } + if (m_changeTracker.optimizationHintChanged) { m_renderer->updateOptimizationHint(m_optimizationHints); m_changeTracker.optimizationHintChanged = false; } + if (m_changeTracker.reflectionChanged) { + m_renderer->m_reflectionEnabled = m_reflectionEnabled; + m_changeTracker.reflectionChanged = false; + } + + if (m_changeTracker.reflectivityChanged) { + // Invert value to match functionality to the property description + m_renderer->m_reflectivity = -(m_reflectivity - 1.0); + m_changeTracker.reflectivityChanged = false; + } + if (m_changeTracker.axisXFormatterChanged) { m_changeTracker.axisXFormatterChanged = false; if (m_axisX->type() & QAbstract3DAxis::AxisTypeValue) { @@ -445,6 +489,11 @@ void Abstract3DController::synchDataToRenderer() m_changeTracker.axisZTitleFixedChanged = false; } + if (m_changeTracker.marginChanged) { + m_renderer->updateMargin(float(m_margin)); + m_changeTracker.marginChanged = false; + } + if (m_changedSeriesList.size()) { m_renderer->modifiedSeriesList(m_changedSeriesList); m_changedSeriesList.clear(); @@ -475,8 +524,6 @@ void Abstract3DController::synchDataToRenderer() void Abstract3DController::render(const GLuint defaultFboHandle) { - m_renderPending = false; - // If not initialized, do nothing. if (!m_renderer) return; @@ -765,8 +812,9 @@ void Abstract3DController::setActiveInputHandler(QAbstract3DInputHandler *inputH m_inputHandlers.removeAll(m_activeInputHandler); delete m_activeInputHandler; } else { - // Disconnect the old input handler from the scene + // Disconnect the old input handler m_activeInputHandler->setScene(0); + QObject::disconnect(m_activeInputHandler, 0, this, 0); } } @@ -775,9 +823,16 @@ void Abstract3DController::setActiveInputHandler(QAbstract3DInputHandler *inputH addInputHandler(inputHandler); m_activeInputHandler = inputHandler; - if (m_activeInputHandler) + if (m_activeInputHandler) { m_activeInputHandler->setScene(m_scene); + // Connect the input handler + QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::inputViewChanged, this, + &Abstract3DController::handleInputViewChanged); + QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::positionChanged, this, + &Abstract3DController::handleInputPositionChanged); + } + // Notify change of input handler emit activeInputHandlerChanged(m_activeInputHandler); } @@ -886,11 +941,7 @@ QAbstract3DGraph::OptimizationHints Abstract3DController::optimizationHints() co bool Abstract3DController::shadowsSupported() const { -#if defined(QT_OPENGL_ES_2) - return false; -#else - return true; -#endif + return !isOpenGLES(); } bool Abstract3DController::isSlicingActive() const @@ -989,6 +1040,11 @@ void Abstract3DController::releaseCustomItem(QCustom3DItem *item) } } +QList<QCustom3DItem *> Abstract3DController::customItems() const +{ + return m_customItems; +} + void Abstract3DController::updateCustomItem() { m_isCustomItemDirty = true; @@ -1303,6 +1359,11 @@ void Abstract3DController::markSeriesItemLabelsDirty() m_seriesList.at(i)->d_ptr->markItemLabelDirty(); } +bool Abstract3DController::isOpenGLES() const +{ + return Utils::isOpenGLES(); +} + void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis *axis, QAbstract3DAxis **axisPtr) { @@ -1381,6 +1442,8 @@ void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orient handleAxisLabelFormatChangedBySender(valueAxis); handleAxisReversedChangedBySender(valueAxis); handleAxisFormatterDirtyBySender(valueAxis->dptr()); + + valueAxis->formatter()->setLocale(m_locale); } } @@ -1427,13 +1490,37 @@ void Abstract3DController::emitNeedRender() void Abstract3DController::handlePendingClick() { - QAbstract3DGraph::ElementType type = m_renderer->clickedType(); - emit elementSelected(type); + m_clickedType = m_renderer->clickedType(); + m_selectedLabelIndex = m_renderer->m_selectedLabelIndex; + m_selectedCustomItemIndex = m_renderer->m_selectedCustomItemIndex; + + // Invalidate query position to indicate the query has been handled, unless another + // point has been queried. + if (m_renderer->cachedClickQuery() == m_scene->selectionQueryPosition()) + m_scene->setSelectionQueryPosition(Q3DScene::invalidSelectionPoint()); + + m_renderer->clearClickQueryResolved(); + + emit elementSelected(m_clickedType); +} + +void Abstract3DController::handlePendingGraphPositionQuery() +{ + m_queriedGraphPosition = m_renderer->queriedGraphPosition(); + + // Invalidate query position to indicate the query has been handled, unless another + // point has been queried. + if (m_renderer->cachedGraphPositionQuery() == m_scene->graphPositionQuery()) + m_scene->setGraphPositionQuery(Q3DScene::invalidSelectionPoint()); + + m_renderer->clearGraphPositionQueryResolved(); + + emit queriedGraphPositionChanged(m_queriedGraphPosition); } int Abstract3DController::selectedLabelIndex() const { - int index = m_renderer->m_selectedLabelIndex; + int index = m_selectedLabelIndex; QAbstract3DAxis *axis = selectedAxis(); if (axis && axis->labels().count() <= index) index = -1; @@ -1443,7 +1530,7 @@ int Abstract3DController::selectedLabelIndex() const QAbstract3DAxis *Abstract3DController::selectedAxis() const { QAbstract3DAxis *axis = 0; - QAbstract3DGraph::ElementType type = m_renderer->clickedType(); + QAbstract3DGraph::ElementType type = m_clickedType; switch (type) { case QAbstract3DGraph::ElementAxisXLabel: axis = axisX(); @@ -1464,7 +1551,7 @@ QAbstract3DAxis *Abstract3DController::selectedAxis() const int Abstract3DController::selectedCustomItemIndex() const { - int index = m_renderer->m_selectedCustomItemIndex; + int index = m_selectedCustomItemIndex; if (m_customItems.count() <= index) index = -1; return index; @@ -1481,10 +1568,7 @@ QCustom3DItem *Abstract3DController::selectedCustomItem() const QAbstract3DGraph::ElementType Abstract3DController::selectedElement() const { - if (m_renderer) - return m_renderer->clickedType(); - else - return QAbstract3DGraph::ElementNone; + return m_clickedType; } void Abstract3DController::setOrthoProjection(bool enable) @@ -1505,7 +1589,7 @@ bool Abstract3DController::isOrthoProjection() const return m_useOrthoProjection; } -void Abstract3DController::setAspectRatio(float ratio) +void Abstract3DController::setAspectRatio(qreal ratio) { if (m_aspectRatio != ratio) { m_aspectRatio = ratio; @@ -1516,9 +1600,131 @@ void Abstract3DController::setAspectRatio(float ratio) } } -float Abstract3DController::aspectRatio() +qreal Abstract3DController::aspectRatio() { return m_aspectRatio; } +void Abstract3DController::setHorizontalAspectRatio(qreal ratio) +{ + if (m_horizontalAspectRatio != ratio) { + m_horizontalAspectRatio = ratio; + m_changeTracker.horizontalAspectRatioChanged = true; + emit horizontalAspectRatioChanged(m_horizontalAspectRatio); + m_isDataDirty = true; + emitNeedRender(); + } +} + +qreal Abstract3DController::horizontalAspectRatio() const +{ + return m_horizontalAspectRatio; +} + +void Abstract3DController::setReflection(bool enable) +{ + if (m_reflectionEnabled != enable) { + m_reflectionEnabled = enable; + m_changeTracker.reflectionChanged = true; + emit reflectionChanged(m_reflectionEnabled); + emitNeedRender(); + } +} + +bool Abstract3DController::reflection() const +{ + return m_reflectionEnabled; +} + +void Abstract3DController::setReflectivity(qreal reflectivity) +{ + if (m_reflectivity != reflectivity) { + m_reflectivity = reflectivity; + m_changeTracker.reflectivityChanged = true; + emit reflectivityChanged(m_reflectivity); + emitNeedRender(); + } +} + +qreal Abstract3DController::reflectivity() const +{ + return m_reflectivity; +} + +void Abstract3DController::setPolar(bool enable) +{ + if (enable != m_isPolar) { + m_isPolar = enable; + m_changeTracker.polarChanged = true; + m_isDataDirty = true; + emit polarChanged(m_isPolar); + emitNeedRender(); + } +} + +bool Abstract3DController::isPolar() const +{ + return m_isPolar; +} + +void Abstract3DController::setRadialLabelOffset(float offset) +{ + if (m_radialLabelOffset != offset) { + m_radialLabelOffset = offset; + m_changeTracker.radialLabelOffsetChanged = true; + emit radialLabelOffsetChanged(m_radialLabelOffset); + emitNeedRender(); + } +} + +float Abstract3DController::radialLabelOffset() const +{ + return m_radialLabelOffset; +} + +void Abstract3DController::setLocale(const QLocale &locale) +{ + if (m_locale != locale) { + m_locale = locale; + + // Value axis formatters need to be updated + QValue3DAxis *axis = qobject_cast<QValue3DAxis *>(m_axisX); + if (axis) + axis->formatter()->setLocale(m_locale); + axis = qobject_cast<QValue3DAxis *>(m_axisY); + if (axis) + axis->formatter()->setLocale(m_locale); + axis = qobject_cast<QValue3DAxis *>(m_axisZ); + if (axis) + axis->formatter()->setLocale(m_locale); + emit localeChanged(m_locale); + } +} + +QLocale Abstract3DController::locale() const +{ + return m_locale; +} + +QVector3D Abstract3DController::queriedGraphPosition() const +{ + return m_queriedGraphPosition; +} + +void Abstract3DController::setMargin(qreal margin) +{ + if (m_margin != margin) { + m_margin = margin; + m_changeTracker.marginChanged = true; + emit marginChanged(margin); + emitNeedRender(); + } +} + +qreal Abstract3DController::margin() const +{ + return m_margin; +} + + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3dcontroller_p.h b/src/datavisualization/engine/abstract3dcontroller_p.h index 0e4d1add..d5a1ac8f 100644 --- a/src/datavisualization/engine/abstract3dcontroller_p.h +++ b/src/datavisualization/engine/abstract3dcontroller_p.h @@ -38,6 +38,7 @@ #include "qcustom3ditem.h" #include <QtGui/QLinearGradient> #include <QtCore/QTime> +#include <QtCore/QLocale> class QOpenGLFramebufferObject; @@ -84,12 +85,18 @@ struct Abstract3DChangeBitField { bool axisYLabelAutoRotationChanged : 1; bool axisZLabelAutoRotationChanged : 1; bool aspectRatioChanged : 1; + bool horizontalAspectRatioChanged : 1; bool axisXTitleVisibilityChanged : 1; bool axisYTitleVisibilityChanged : 1; bool axisZTitleVisibilityChanged : 1; bool axisXTitleFixedChanged : 1; bool axisYTitleFixedChanged : 1; bool axisZTitleFixedChanged : 1; + bool polarChanged : 1; + bool radialLabelOffsetChanged : 1; + bool reflectionChanged : 1; + bool reflectivityChanged : 1; + bool marginChanged : 1; Abstract3DChangeBitField() : themeChanged(true), @@ -128,12 +135,18 @@ struct Abstract3DChangeBitField { axisYLabelAutoRotationChanged(true), axisZLabelAutoRotationChanged(true), aspectRatioChanged(true), + horizontalAspectRatioChanged(true), axisXTitleVisibilityChanged(true), axisYTitleVisibilityChanged(true), axisZTitleVisibilityChanged(true), axisXTitleFixedChanged(true), axisYTitleFixedChanged(true), - axisZTitleFixedChanged(true) + axisZTitleFixedChanged(true), + polarChanged(true), + radialLabelOffsetChanged(true), + reflectionChanged(true), + reflectivityChanged(true), + marginChanged(true) { } }; @@ -156,8 +169,13 @@ private: QAbstract3DGraph::SelectionFlags m_selectionMode; QAbstract3DGraph::ShadowQuality m_shadowQuality; bool m_useOrthoProjection; - float m_aspectRatio; + qreal m_aspectRatio; + qreal m_horizontalAspectRatio; QAbstract3DGraph::OptimizationHints m_optimizationHints; + bool m_reflectionEnabled; + qreal m_reflectivity; + QLocale m_locale; + QVector3D m_queriedGraphPosition; protected: Q3DScene *m_scene; @@ -175,6 +193,8 @@ protected: bool m_isCustomItemDirty; bool m_isSeriesVisualsDirty; bool m_renderPending; + bool m_isPolar; + float m_radialLabelOffset; QList<QAbstract3DSeries *> m_seriesList; @@ -187,13 +207,17 @@ protected: QList<QCustom3DItem *> m_customItems; + QAbstract3DGraph::ElementType m_clickedType; + int m_selectedLabelIndex; + int m_selectedCustomItemIndex; + qreal m_margin; + explicit Abstract3DController(QRect initialViewport, Q3DScene *scene, QObject *parent = 0); public: virtual ~Abstract3DController(); inline bool isInitialized() { return (m_renderer != 0); } - virtual void destroyRenderer(); virtual void synchDataToRenderer(); virtual void render(const GLuint defaultFboHandle = 0); virtual void initializeOpenGL() = 0; @@ -252,6 +276,7 @@ public: void deleteCustomItem(QCustom3DItem *item); void deleteCustomItem(const QVector3D &position); void releaseCustomItem(QCustom3DItem *item); + QList<QCustom3DItem *> customItems() const; int selectedLabelIndex() const; QAbstract3DAxis *selectedAxis() const; @@ -261,6 +286,35 @@ public: void setOrthoProjection(bool enable); bool isOrthoProjection() const; + void setMeasureFps(bool enable); + inline bool measureFps() const { return m_measureFps; } + inline qreal currentFps() const { return m_currentFps; } + + QAbstract3DGraph::ElementType selectedElement() const; + + void setAspectRatio(qreal ratio); + qreal aspectRatio(); + void setHorizontalAspectRatio(qreal ratio); + qreal horizontalAspectRatio() const; + + void setReflection(bool enable); + bool reflection() const; + void setReflectivity(qreal reflectivity); + qreal reflectivity() const; + + void setPolar(bool enable); + bool isPolar() const; + void setRadialLabelOffset(float offset); + float radialLabelOffset() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + QVector3D queriedGraphPosition() const; + + void setMargin(qreal margin); + qreal margin() const; + void emitNeedRender(); virtual void clearSelection() = 0; @@ -286,12 +340,16 @@ public: virtual void handleAxisTitleVisibilityChangedBySender(QObject *sender); virtual void handleAxisTitleFixedChangedBySender(QObject *sender); virtual void handleSeriesVisibilityChangedBySender(QObject *sender); - virtual void handlePendingClick() = 0; + virtual void handlePendingClick(); + virtual void handlePendingGraphPositionQuery(); virtual void adjustAxisRanges() = 0; void markSeriesItemLabelsDirty(); + bool isOpenGLES() const; public slots: + void destroyRenderer(); + void handleAxisTitleChanged(const QString &title); void handleAxisLabelsChanged(); void handleAxisRangeChanged(float min, float max); @@ -320,17 +378,8 @@ public slots: // Renderer callback handlers void handleRequestShadowQuality(QAbstract3DGraph::ShadowQuality quality); - void setMeasureFps(bool enable); - inline bool measureFps() const { return m_measureFps; } - inline qreal currentFps() const { return m_currentFps; } - - QAbstract3DGraph::ElementType selectedElement() const; - void updateCustomItem(); - void setAspectRatio(float ratio); - float aspectRatio(); - signals: void shadowQualityChanged(QAbstract3DGraph::ShadowQuality quality); void activeInputHandlerChanged(QAbstract3DInputHandler *inputHandler); @@ -344,8 +393,16 @@ signals: void measureFpsChanged(bool enabled); void currentFpsChanged(qreal fps); void orthoProjectionChanged(bool enabled); - void aspectRatioChanged(float ratio); + void aspectRatioChanged(qreal ratio); + void horizontalAspectRatioChanged(qreal ratio); void optimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints); + void polarChanged(bool enabled); + void radialLabelOffsetChanged(float offset); + void reflectionChanged(bool enabled); + void reflectivityChanged(qreal reflectivity); + void localeChanged(const QLocale &locale); + void queriedGraphPositionChanged(const QVector3D &data); + void marginChanged(qreal margin); protected: virtual QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation); diff --git a/src/datavisualization/engine/abstract3drenderer.cpp b/src/datavisualization/engine/abstract3drenderer.cpp index 04ede782..cfc691af 100644 --- a/src/datavisualization/engine/abstract3drenderer.cpp +++ b/src/datavisualization/engine/abstract3drenderer.cpp @@ -24,9 +24,24 @@ #include "shaderhelper_p.h" #include "qcustom3ditem_p.h" #include "qcustom3dlabel_p.h" +#include "qcustom3dvolume_p.h" +#include "scatter3drenderer_p.h" + +#include <QtCore/qmath.h> +#include <QtGui/QWindow> +#include <QtCore/QThread> QT_BEGIN_NAMESPACE_DATAVISUALIZATION +// Defined in shaderhelper.cpp +extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg); + +const qreal doublePi(M_PI * 2.0); +const int polarGridRoundness(64); +const qreal polarGridAngle(doublePi / qreal(polarGridRoundness)); +const float polarGridAngleDegrees(float(360.0 / qreal(polarGridRoundness))); +const qreal polarGridHalfAngle(polarGridAngle / 2.0); + Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller) : QObject(0), m_hasNegativeValues(false), @@ -37,26 +52,83 @@ Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller) m_cachedSelectionMode(QAbstract3DGraph::SelectionNone), m_cachedOptimizationHint(QAbstract3DGraph::OptimizationDefault), m_textureHelper(0), + m_depthTexture(0), m_cachedScene(new Q3DScene()), m_selectionDirty(true), m_selectionState(SelectNone), m_devicePixelRatio(1.0f), m_selectionLabelDirty(true), - m_clickPending(false), + m_clickResolved(false), + m_graphPositionQueryPending(false), + m_graphPositionQueryResolved(false), m_clickedSeries(0), m_clickedType(QAbstract3DGraph::ElementNone), + m_selectedLabelIndex(-1), + m_selectedCustomItemIndex(-1), m_selectionLabelItem(0), m_visibleSeriesCount(0), m_customItemShader(0), + m_volumeTextureShader(0), + m_volumeTextureLowDefShader(0), + m_volumeTextureSliceShader(0), + m_volumeSliceFrameShader(0), + m_labelShader(0), + m_cursorPositionShader(0), + m_cursorPositionFrameBuffer(0), + m_cursorPositionTexture(0), m_useOrthoProjection(false), m_xFlipped(false), m_yFlipped(false), m_zFlipped(false), + m_yFlippedForGrid(false), m_backgroundObj(0), m_gridLineObj(0), m_labelObj(0), - m_graphAspectRatio(2.0f) + m_positionMapperObj(0), + m_graphAspectRatio(2.0f), + m_graphHorizontalAspectRatio(0.0f), + m_polarGraph(false), + m_radialLabelOffset(1.0f), + m_polarRadius(2.0f), + m_xRightAngleRotation(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f)), + m_yRightAngleRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f)), + m_zRightAngleRotation(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f)), + m_xRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f)), + m_yRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f)), + m_zRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -90.0f)), + m_xFlipRotation(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -180.0f)), + m_zFlipRotation(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f)), + m_requestedMargin(-1.0f), + m_vBackgroundMargin(0.1f), + m_hBackgroundMargin(0.1f), + m_scaleXWithBackground(0.0f), + m_scaleYWithBackground(0.0f), + m_scaleZWithBackground(0.0f), + m_oldCameraTarget(QVector3D(2000.0f, 2000.0f, 2000.0f)), // Just random invalid target + m_reflectionEnabled(false), + m_reflectivity(0.5), +#if !defined(QT_OPENGL_ES_2) + m_funcs_2_1(0), +#endif + m_context(0), + m_dummySurfaceAtDelete(0), + m_isOpenGLES(true) + { + initializeOpenGLFunctions(); + m_isOpenGLES = Utils::isOpenGLES(); +#if !defined(QT_OPENGL_ES_2) + if (!m_isOpenGLES) { + // Discard warnings about deprecated functions + QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs); + + m_funcs_2_1 = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>(); + m_funcs_2_1->initializeOpenGLFunctions(); + + // Restore original message handler + qInstallMessageHandler(handler); + } +#endif QObject::connect(m_drawer, &Drawer::drawerChanged, this, &Abstract3DRenderer::updateTextures); QObject::connect(this, &Abstract3DRenderer::needRender, controller, &Abstract3DController::needRender, Qt::QueuedConnection); @@ -71,6 +143,12 @@ Abstract3DRenderer::~Abstract3DRenderer() delete m_cachedTheme; delete m_selectionLabelItem; delete m_customItemShader; + delete m_volumeTextureShader; + delete m_volumeTextureLowDefShader; + delete m_volumeSliceFrameShader; + delete m_volumeTextureSliceShader; + delete m_labelShader; + delete m_cursorPositionShader; foreach (SeriesRenderCache *cache, m_renderCacheList) { cache->cleanup(m_textureHelper); @@ -88,12 +166,29 @@ Abstract3DRenderer::~Abstract3DRenderer() ObjectHelper::releaseObjectHelper(this, m_backgroundObj); ObjectHelper::releaseObjectHelper(this, m_gridLineObj); ObjectHelper::releaseObjectHelper(this, m_labelObj); + ObjectHelper::releaseObjectHelper(this, m_positionMapperObj); + + if (m_textureHelper) { + m_textureHelper->deleteTexture(&m_depthTexture); + m_textureHelper->deleteTexture(&m_cursorPositionTexture); + + if (QOpenGLContext::currentContext()) + m_textureHelper->glDeleteFramebuffers(1, &m_cursorPositionFrameBuffer); + + delete m_textureHelper; + } + + m_axisCacheX.clearLabels(); + m_axisCacheY.clearLabels(); + m_axisCacheZ.clearLabels(); - delete m_textureHelper; + restoreContextAfterDelete(); } void Abstract3DRenderer::initializeOpenGL() { + m_context = QOpenGLContext::currentContext(); + // Set OpenGL features glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); @@ -101,9 +196,11 @@ void Abstract3DRenderer::initializeOpenGL() glCullFace(GL_BACK); #if !defined(QT_OPENGL_ES_2) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + if (!m_isOpenGLES) { + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + } #endif m_textureHelper = new TextureHelper(); @@ -112,6 +209,15 @@ void Abstract3DRenderer::initializeOpenGL() axisCacheForOrientation(QAbstract3DAxis::AxisOrientationX).setDrawer(m_drawer); axisCacheForOrientation(QAbstract3DAxis::AxisOrientationY).setDrawer(m_drawer); axisCacheForOrientation(QAbstract3DAxis::AxisOrientationZ).setDrawer(m_drawer); + + initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), + QStringLiteral(":/shaders/fragmentLabel")); + + initCursorPositionShaders(QStringLiteral(":/shaders/vertexPosition"), + QStringLiteral(":/shaders/fragmentPositionMap")); + + loadLabelMesh(); + loadPositionMapperMesh(); } void Abstract3DRenderer::render(const GLuint defaultFboHandle) @@ -137,7 +243,7 @@ void Abstract3DRenderer::render(const GLuint defaultFboHandle) glEnable(GL_SCISSOR_TEST); QVector4D clearColor = Utils::vectorFromColor(m_cachedTheme->windowColor()); glClearColor(clearColor.x(), clearColor.y(), clearColor.z(), 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } @@ -146,28 +252,87 @@ void Abstract3DRenderer::updateSelectionState(SelectionState state) m_selectionState = state; } -void Abstract3DRenderer::updateInputPosition(const QPoint &position) +void Abstract3DRenderer::initGradientShaders(const QString &vertexShader, + const QString &fragmentShader) { - m_inputPosition = position; + // Do nothing by default + Q_UNUSED(vertexShader) + Q_UNUSED(fragmentShader) } -void Abstract3DRenderer::initGradientShaders(const QString &vertexShader, - const QString &fragmentShader) +void Abstract3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader) { // Do nothing by default Q_UNUSED(vertexShader) Q_UNUSED(fragmentShader) + Q_UNUSED(gradientVertexShader) + Q_UNUSED(gradientFragmentShader) } void Abstract3DRenderer::initCustomItemShaders(const QString &vertexShader, const QString &fragmentShader) { - if (m_customItemShader) - delete m_customItemShader; + delete m_customItemShader; m_customItemShader = new ShaderHelper(this, vertexShader, fragmentShader); m_customItemShader->initialize(); } +void Abstract3DRenderer::initVolumeTextureShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &fragmentLowDefShader, + const QString &sliceShader, + const QString &sliceFrameVertexShader, + const QString &sliceFrameShader) +{ + + delete m_volumeTextureShader; + m_volumeTextureShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_volumeTextureShader->initialize(); + + delete m_volumeTextureLowDefShader; + m_volumeTextureLowDefShader = new ShaderHelper(this, vertexShader, fragmentLowDefShader); + m_volumeTextureLowDefShader->initialize(); + + delete m_volumeTextureSliceShader; + m_volumeTextureSliceShader = new ShaderHelper(this, vertexShader, sliceShader); + m_volumeTextureSliceShader->initialize(); + + delete m_volumeSliceFrameShader; + m_volumeSliceFrameShader = new ShaderHelper(this, sliceFrameVertexShader, sliceFrameShader); + m_volumeSliceFrameShader->initialize(); +} + +void Abstract3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) +{ + delete m_labelShader; + m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_labelShader->initialize(); +} + +void Abstract3DRenderer::initCursorPositionShaders(const QString &vertexShader, + const QString &fragmentShader) +{ + // Init the shader + delete m_cursorPositionShader; + m_cursorPositionShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_cursorPositionShader->initialize(); +} + +void Abstract3DRenderer::initCursorPositionBuffer() +{ + m_textureHelper->deleteTexture(&m_cursorPositionTexture); + + if (m_primarySubViewport.size().isEmpty()) + return; + + m_cursorPositionTexture = + m_textureHelper->createCursorPositionTexture(m_primarySubViewport.size(), + m_cursorPositionFrameBuffer); +} + void Abstract3DRenderer::updateTheme(Q3DTheme *theme) { // Synchronize the controller theme with renderer @@ -193,24 +358,22 @@ void Abstract3DRenderer::updateScene(Q3DScene *scene) handleResize(); } - scene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment); - // Set light position (rotate light with activeCamera, a bit above it (as set in defaultLightPos)) - scene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos); - QPoint logicalPixelPosition = scene->selectionQueryPosition(); - updateInputPosition(QPoint(logicalPixelPosition.x() * m_devicePixelRatio, - logicalPixelPosition.y() * m_devicePixelRatio)); + m_inputPosition = QPoint(logicalPixelPosition.x() * m_devicePixelRatio, + logicalPixelPosition.y() * m_devicePixelRatio); + + QPoint logicalGraphPosition = scene->graphPositionQuery(); + m_graphPositionQuery = QPoint(logicalGraphPosition.x() * m_devicePixelRatio, + logicalGraphPosition.y() * m_devicePixelRatio); // Synchronize the renderer scene to controller scene scene->d_ptr->sync(*m_cachedScene->d_ptr); + updateCameraViewport(); + if (Q3DScene::invalidSelectionPoint() == logicalPixelPosition) { updateSelectionState(SelectNone); } else { - // Selections are one-shot, reset selection active to false before processing - scene->setSelectionQueryPosition(Q3DScene::invalidSelectionPoint()); - m_clickPending = true; - if (scene->isSlicingActive()) { if (scene->isPointInPrimarySubView(logicalPixelPosition)) updateSelectionState(SelectOnOverview); @@ -222,53 +385,109 @@ void Abstract3DRenderer::updateScene(Q3DScene *scene) updateSelectionState(SelectOnScene); } } + + if (Q3DScene::invalidSelectionPoint() != logicalGraphPosition) + m_graphPositionQueryPending = true; + + // Queue up another render when we have a query that needs resolving. + // This is needed because QtQuick scene graph can sometimes do a sync without following it up + // with a render. + if (m_graphPositionQueryPending || m_selectionState != SelectNone) + emit needRender(); +} + +void Abstract3DRenderer::updateTextures() +{ + m_axisCacheX.updateTextures(); + m_axisCacheY.updateTextures(); + m_axisCacheZ.updateTextures(); } void Abstract3DRenderer::reInitShaders() { -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - initGradientShaders(QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY")); - initShaders(QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentShadowNoTex")); - initBackgroundShaders(QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentShadowNoTex")); - initCustomItemShaders(QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentShadow")); - } else { - initGradientShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentColorOnY")); - initShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragment")); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic) + && qobject_cast<Scatter3DRenderer *>(this)) { + initGradientShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadow")); + initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadowNoTex"), + QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY")); + initShaders(QStringLiteral(":/shaders/vertexShadowNoMatrices"), + QStringLiteral(":/shaders/fragmentShadowNoTex")); + } else { + initGradientShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY")); + initShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadowNoTex")); + } + initBackgroundShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadowNoTex")); + initCustomItemShaders(QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentShadow")); + } else { + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic) + && qobject_cast<Scatter3DRenderer *>(this)) { + initGradientShaders(QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTexture")); + initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragment"), + QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentColorOnY")); + initShaders(QStringLiteral(":/shaders/vertexNoMatrices"), + QStringLiteral(":/shaders/fragment")); + } else { + initGradientShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentColorOnY")); + initShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragment")); + } + initBackgroundShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragment")); + initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTexture")); + } + initVolumeTextureShaders(QStringLiteral(":/shaders/vertexTexture3D"), + QStringLiteral(":/shaders/fragmentTexture3D"), + QStringLiteral(":/shaders/fragmentTexture3DLowDef"), + QStringLiteral(":/shaders/fragmentTexture3DSlice"), + QStringLiteral(":/shaders/vertexPosition"), + QStringLiteral(":/shaders/fragment3DSliceFrames")); + } else { + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic) + && qobject_cast<Scatter3DRenderer *>(this)) { + initGradientShaders(QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTextureES2")); + initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentES2"), + QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentColorOnYES2")); + initBackgroundShaders(QStringLiteral(":/shaders/vertexNoMatrices"), + QStringLiteral(":/shaders/fragmentES2")); + } else { + initGradientShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentColorOnYES2")); + initShaders(QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentES2")); + } initBackgroundShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragment")); + QStringLiteral(":/shaders/fragmentES2")); initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"), - QStringLiteral(":/shaders/fragmentTexture")); + QStringLiteral(":/shaders/fragmentTextureES2")); } -#else - initGradientShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentColorOnYES2")); - initShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentES2")); - initBackgroundShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentES2")); - initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"), - QStringLiteral(":/shaders/fragmentTextureES2")); -#endif } void Abstract3DRenderer::handleShadowQualityChange() { reInitShaders(); -#if defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) { + if (m_isOpenGLES && m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) { emit requestShadowQuality(QAbstract3DGraph::ShadowQualityNone); qWarning("Shadows are not yet supported for OpenGL ES2"); m_cachedShadowQuality = QAbstract3DGraph::ShadowQualityNone; } -#endif } void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode) @@ -280,16 +499,39 @@ void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mo void Abstract3DRenderer::updateAspectRatio(float ratio) { m_graphAspectRatio = ratio; - calculateZoomLevel(); - m_cachedScene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment); foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - updateCustomItemPositions(); +} + +void Abstract3DRenderer::updateHorizontalAspectRatio(float ratio) +{ + m_graphHorizontalAspectRatio = ratio; + foreach (SeriesRenderCache *cache, m_renderCacheList) + cache->setDataDirty(true); +} + +void Abstract3DRenderer::updatePolar(bool enable) +{ + m_polarGraph = enable; + foreach (SeriesRenderCache *cache, m_renderCacheList) + cache->setDataDirty(true); +} + +void Abstract3DRenderer::updateRadialLabelOffset(float offset) +{ + m_radialLabelOffset = offset; +} + +void Abstract3DRenderer::updateMargin(float margin) +{ + m_requestedMargin = margin; } void Abstract3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint) { m_cachedOptimizationHint = hint; + foreach (SeriesRenderCache *cache, m_renderCacheList) + cache->setDataDirty(true); } void Abstract3DRenderer::handleResize() @@ -303,10 +545,10 @@ void Abstract3DRenderer::handleResize() // Re-init selection buffer initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif + + initCursorPositionBuffer(); } void Abstract3DRenderer::calculateZoomLevel() @@ -315,9 +557,9 @@ void Abstract3DRenderer::calculateZoomLevel() GLfloat div; GLfloat zoomAdjustment; div = qMin(m_primarySubViewport.width(), m_primarySubViewport.height()); - zoomAdjustment = 2.0f * defaultRatio + zoomAdjustment = defaultRatio * ((m_primarySubViewport.width() / div) - / (m_primarySubViewport.height() / div)) / m_graphAspectRatio; + / (m_primarySubViewport.height() / div)); m_autoScaleAdjustment = qMin(zoomAdjustment, 1.0f); // clamp to 1.0f } @@ -348,8 +590,6 @@ void Abstract3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orient foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisSegmentCount(QAbstract3DAxis::AxisOrientation orientation, @@ -378,8 +618,6 @@ void Abstract3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation ori axisCacheForOrientation(orientation).setReversed(enable); foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation orientation, @@ -396,8 +634,6 @@ void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation or foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation, @@ -607,10 +843,9 @@ void Abstract3DRenderer::drawAxisTitleY(const QVector3D &sideLabelRotation, QQuaternion titleRotation; if (m_axisCacheY.isTitleFixed()) { titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation) - * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f); + * m_zRightAngleRotation; } else { - titleRotation = totalRotation - * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f); + titleRotation = totalRotation * m_zRightAngleRotation; } dummyItem.setTranslation(titleTrans + titleOffsetVector); @@ -628,17 +863,22 @@ void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation, float labelsMaxWidth, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - ShaderHelper *shader) + ShaderHelper *shader, + bool radial) { float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheX.titleItem().size().height(); - float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor)); + float titleOffset; + if (radial) + titleOffset = -2.0f * (labelMargin + m_drawer->scaledFontSize()); + else + titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor)); float zRotation = 0.0f; float yRotation = 0.0f; float xRotation = -90.0f + labelRotation.z(); float offsetRotation = labelRotation.z(); float extraRotation = -90.0f; Qt::AlignmentFlag alignment = Qt::AlignTop; - if (m_yFlipped) { + if (m_yFlippedForGrid) { alignment = Qt::AlignBottom; zRotation = 180.0f; if (m_zFlipped) { @@ -678,6 +918,17 @@ void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation, } } + if (radial) { + if (m_zFlipped) { + titleOffset = -titleOffset; + } else { + if (m_yFlippedForGrid) + alignment = Qt::AlignTop; + else + alignment = Qt::AlignBottom; + } + } + if (offsetRotation == 180.0f || offsetRotation == -180.0f) offsetRotation = 0.0f; QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, offsetRotation); @@ -718,7 +969,7 @@ void Abstract3DRenderer::drawAxisTitleZ(const QVector3D &labelRotation, float xRotation = -90.0f; float extraRotation = 90.0f; Qt::AlignmentFlag alignment = Qt::AlignTop; - if (m_yFlipped) { + if (m_yFlippedForGrid) { alignment = Qt::AlignBottom; xRotation = -xRotation; if (m_zFlipped) { @@ -793,6 +1044,11 @@ void Abstract3DRenderer::loadLabelMesh() QStringLiteral(":/defaultMeshes/plane")); } +void Abstract3DRenderer::loadPositionMapperMesh() +{ + ObjectHelper::resetObjectHelper(this, m_positionMapperObj, + QStringLiteral(":/defaultMeshes/barFull")); +} void Abstract3DRenderer::generateBaseColorTexture(const QColor &color, GLuint *texture) { @@ -846,11 +1102,16 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) newItem->setRenderer(this); newItem->setItemPointer(item); // Store pointer for render item updates newItem->setMesh(item->meshFile()); - QVector3D scaling = item->scaling(); + newItem->setOrigPosition(item->position()); + newItem->setOrigScaling(item->scaling()); + newItem->setScalingAbsolute(item->isScalingAbsolute()); + newItem->setPositionAbsolute(item->isPositionAbsolute()); QImage textureImage = item->d_ptr->textureImage(); bool facingCamera = false; + GLuint texture = 0; if (item->d_ptr->m_isLabelItem) { QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item); + newItem->setLabelItem(true); float pointSize = labelItem->font().pointSizeF(); // Check do we have custom visuals or need to use theme if (!labelItem->dptr()->m_customVisuals) { @@ -864,22 +1125,52 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) } // Calculate scaling based on text (texture size), font size and asked scaling float scaledFontSize = (0.05f + pointSize / 500.0f) / float(textureImage.height()); + QVector3D scaling = newItem->origScaling(); scaling.setX(scaling.x() * textureImage.width() * scaledFontSize); scaling.setY(scaling.y() * textureImage.height() * scaledFontSize); + newItem->setOrigScaling(scaling); // Check if facing camera facingCamera = labelItem->isFacingCamera(); + } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) { + QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item); + newItem->setTextureWidth(volumeItem->textureWidth()); + newItem->setTextureHeight(volumeItem->textureHeight()); + newItem->setTextureDepth(volumeItem->textureDepth()); + if (volumeItem->textureFormat() == QImage::Format_Indexed8) + newItem->setColorTable(volumeItem->colorTable()); + newItem->setTextureFormat(volumeItem->textureFormat()); + newItem->setVolume(true); + newItem->setBlendNeeded(true); + texture = m_textureHelper->create3DTexture(volumeItem->textureData(), + volumeItem->textureWidth(), + volumeItem->textureHeight(), + volumeItem->textureDepth(), + volumeItem->textureFormat()); + newItem->setSliceIndexX(volumeItem->sliceIndexX()); + newItem->setSliceIndexY(volumeItem->sliceIndexY()); + newItem->setSliceIndexZ(volumeItem->sliceIndexZ()); + newItem->setAlphaMultiplier(volumeItem->alphaMultiplier()); + newItem->setPreserveOpacity(volumeItem->preserveOpacity()); + newItem->setUseHighDefShader(volumeItem->useHighDefShader()); + + newItem->setDrawSlices(volumeItem->drawSlices()); + newItem->setDrawSliceFrames(volumeItem->drawSliceFrames()); + newItem->setSliceFrameColor(volumeItem->sliceFrameColor()); + newItem->setSliceFrameWidths(volumeItem->sliceFrameWidths()); + newItem->setSliceFrameGaps(volumeItem->sliceFrameGaps()); + newItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses()); } - newItem->setScaling(scaling); + recalculateCustomItemScalingAndPos(newItem); newItem->setRotation(item->rotation()); - newItem->setPosition(item->position()); - newItem->setPositionAbsolute(item->isPositionAbsolute()); - newItem->setBlendNeeded(textureImage.hasAlphaChannel()); - GLuint texture = m_textureHelper->create2DTexture(textureImage, true, true, true); + + // In OpenGL ES we simply draw volumes as regular custom item placeholders. + if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES) + { + newItem->setBlendNeeded(textureImage.hasAlphaChannel()); + texture = m_textureHelper->create2DTexture(textureImage, true, true, true); + } newItem->setTexture(texture); item->d_ptr->clearTextureImage(); - QVector3D translation = convertPositionToTranslation(item->position(), - item->isPositionAbsolute()); - newItem->setTranslation(translation); newItem->setVisible(item->isVisible()); newItem->setShadowCasting(item->isShadowCasting()); newItem->setFacingCamera(facingCamera); @@ -887,6 +1178,74 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) return newItem; } +void Abstract3DRenderer::recalculateCustomItemScalingAndPos(CustomRenderItem *item) +{ + if (!m_polarGraph && !item->isLabel() && !item->isScalingAbsolute() + && !item->isPositionAbsolute()) { + QVector3D scale = item->origScaling() / 2.0f; + QVector3D pos = item->origPosition(); + QVector3D minBounds(pos.x() - scale.x(), + pos.y() - scale.y(), + pos.z() + scale.z()); + QVector3D maxBounds(pos.x() + scale.x(), + pos.y() + scale.y(), + pos.z() - scale.z()); + QVector3D minCorner = convertPositionToTranslation(minBounds, false); + QVector3D maxCorner = convertPositionToTranslation(maxBounds, false); + scale = QVector3D(qAbs(maxCorner.x() - minCorner.x()), + qAbs(maxCorner.y() - minCorner.y()), + qAbs(maxCorner.z() - minCorner.z())) / 2.0f; + if (item->isVolume()) { + // Only volume items need to scale and reposition according to bounds + QVector3D minBoundsNormal = minCorner; + QVector3D maxBoundsNormal = maxCorner; + // getVisibleItemBounds returns bounds normalized for fragment shader [-1,1] + // Y and Z are also flipped. + getVisibleItemBounds(minBoundsNormal, maxBoundsNormal); + item->setMinBounds(minBoundsNormal); + item->setMaxBounds(maxBoundsNormal); + // For scaling calculations, we want [0,1] normalized values + minBoundsNormal = item->minBoundsNormal(); + maxBoundsNormal = item->maxBoundsNormal(); + + // Rescale and reposition the item so that it doesn't go over the edges + QVector3D adjScaling = + QVector3D(scale.x() * (maxBoundsNormal.x() - minBoundsNormal.x()), + scale.y() * (maxBoundsNormal.y() - minBoundsNormal.y()), + scale.z() * (maxBoundsNormal.z() - minBoundsNormal.z())); + + item->setScaling(adjScaling); + + QVector3D adjPos = item->origPosition(); + QVector3D dataExtents = QVector3D(maxBounds.x() - minBounds.x(), + maxBounds.y() - minBounds.y(), + maxBounds.z() - minBounds.z()) / 2.0f; + adjPos.setX(adjPos.x() + (dataExtents.x() * minBoundsNormal.x()) + - (dataExtents.x() * (1.0f - maxBoundsNormal.x()))); + adjPos.setY(adjPos.y() + (dataExtents.y() * minBoundsNormal.y()) + - (dataExtents.y() * (1.0f - maxBoundsNormal.y()))); + adjPos.setZ(adjPos.z() + (dataExtents.z() * minBoundsNormal.z()) + - (dataExtents.z() * (1.0f - maxBoundsNormal.z()))); + item->setPosition(adjPos); + } else { + // Only scale for non-volume items, and do not readjust position + item->setScaling(scale); + item->setPosition(item->origPosition()); + } + } else { + item->setScaling(item->origScaling()); + item->setPosition(item->origPosition()); + if (item->isVolume()) { + // Y and Z need to be flipped as shader flips those axes + item->setMinBounds(QVector3D(-1.0f, 1.0f, 1.0f)); + item->setMaxBounds(QVector3D(1.0f, -1.0f, -1.0f)); + } + } + QVector3D translation = convertPositionToTranslation(item->position(), + item->isPositionAbsolute()); + item->setTranslation(translation); +} + void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) { QCustom3DItem *item = renderItem->itemPointer(); @@ -894,8 +1253,17 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) renderItem->setMesh(item->meshFile()); item->d_ptr->m_dirtyBits.meshDirty = false; } + if (item->d_ptr->m_dirtyBits.positionDirty) { + renderItem->setOrigPosition(item->position()); + renderItem->setPositionAbsolute(item->isPositionAbsolute()); + if (!item->d_ptr->m_dirtyBits.scalingDirty) + recalculateCustomItemScalingAndPos(renderItem); + item->d_ptr->m_dirtyBits.positionDirty = false; + } if (item->d_ptr->m_dirtyBits.scalingDirty) { QVector3D scaling = item->scaling(); + renderItem->setOrigScaling(scaling); + renderItem->setScalingAbsolute(item->isScalingAbsolute()); // In case we have label item, we need to recreate texture for scaling adjustment if (item->d_ptr->m_isLabelItem) { QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item); @@ -918,8 +1286,9 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) scaling.setX(scaling.x() * textureImage.width() * scaledFontSize); scaling.setY(scaling.y() * textureImage.height() * scaledFontSize); item->d_ptr->clearTextureImage(); + renderItem->setOrigScaling(scaling); } - renderItem->setScaling(scaling); + recalculateCustomItemScalingAndPos(renderItem); item->d_ptr->m_dirtyBits.scalingDirty = false; } if (item->d_ptr->m_dirtyBits.rotationDirty) { @@ -939,24 +1308,16 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) m_cachedTheme->isLabelBorderEnabled()); textureImage = item->d_ptr->textureImage(); } + } else if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES) { + renderItem->setBlendNeeded(textureImage.hasAlphaChannel()); + GLuint oldTexture = renderItem->texture(); + m_textureHelper->deleteTexture(&oldTexture); + GLuint texture = m_textureHelper->create2DTexture(textureImage, true, true, true); + renderItem->setTexture(texture); } - renderItem->setBlendNeeded(textureImage.hasAlphaChannel()); - GLuint oldTexture = renderItem->texture(); - m_textureHelper->deleteTexture(&oldTexture); - GLuint texture = m_textureHelper->create2DTexture(textureImage, true, true, true); - renderItem->setTexture(texture); item->d_ptr->clearTextureImage(); item->d_ptr->m_dirtyBits.textureDirty = false; } - if (item->d_ptr->m_dirtyBits.positionDirty || item->d_ptr->m_dirtyBits.positionAbsoluteDirty) { - renderItem->setPosition(item->position()); - renderItem->setPositionAbsolute(item->isPositionAbsolute()); - QVector3D translation = convertPositionToTranslation(item->position(), - item->isPositionAbsolute()); - renderItem->setTranslation(translation); - item->d_ptr->m_dirtyBits.positionDirty = false; - item->d_ptr->m_dirtyBits.positionAbsoluteDirty = false; - } if (item->d_ptr->m_dirtyBits.visibleDirty) { renderItem->setVisible(item->isVisible()); item->d_ptr->m_dirtyBits.visibleDirty = false; @@ -971,130 +1332,649 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) renderItem->setFacingCamera(labelItem->isFacingCamera()); labelItem->dptr()->m_facingCameraDirty = false; } + } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) { + QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item); + if (volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty) { + renderItem->setColorTable(volumeItem->colorTable()); + volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty = false; + } + if (volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty + || volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty + || volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty) { + GLuint oldTexture = renderItem->texture(); + m_textureHelper->deleteTexture(&oldTexture); + GLuint texture = m_textureHelper->create3DTexture(volumeItem->textureData(), + volumeItem->textureWidth(), + volumeItem->textureHeight(), + volumeItem->textureDepth(), + volumeItem->textureFormat()); + renderItem->setTexture(texture); + renderItem->setTextureWidth(volumeItem->textureWidth()); + renderItem->setTextureHeight(volumeItem->textureHeight()); + renderItem->setTextureDepth(volumeItem->textureDepth()); + renderItem->setTextureFormat(volumeItem->textureFormat()); + volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty = false; + volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty = false; + volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty = false; + } + if (volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty) { + renderItem->setDrawSlices(volumeItem->drawSlices()); + renderItem->setDrawSliceFrames(volumeItem->drawSliceFrames()); + renderItem->setSliceFrameColor(volumeItem->sliceFrameColor()); + renderItem->setSliceFrameWidths(volumeItem->sliceFrameWidths()); + renderItem->setSliceFrameGaps(volumeItem->sliceFrameGaps()); + renderItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses()); + renderItem->setSliceIndexX(volumeItem->sliceIndexX()); + renderItem->setSliceIndexY(volumeItem->sliceIndexY()); + renderItem->setSliceIndexZ(volumeItem->sliceIndexZ()); + volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty = false; + } + if (volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty) { + renderItem->setAlphaMultiplier(volumeItem->alphaMultiplier()); + renderItem->setPreserveOpacity(volumeItem->preserveOpacity()); + volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty = false; + } + if (volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty) { + renderItem->setUseHighDefShader(volumeItem->useHighDefShader()); + volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty = false; + } } } void Abstract3DRenderer::updateCustomItemPositions() { - foreach (CustomRenderItem *renderItem, m_customRenderCache) { - QVector3D translation = convertPositionToTranslation(renderItem->position(), - renderItem->isPositionAbsolute()); - renderItem->setTranslation(translation); - } + foreach (CustomRenderItem *renderItem, m_customRenderCache) + recalculateCustomItemScalingAndPos(renderItem); } void Abstract3DRenderer::drawCustomItems(RenderingState state, - ShaderHelper *shader, + ShaderHelper *regularShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, GLuint depthTexture, - GLfloat shadowQuality) + GLfloat shadowQuality, + GLfloat reflection) { if (m_customRenderCache.isEmpty()) return; + ShaderHelper *shader = regularShader; + shader->bind(); + if (RenderingNormal == state) { - shader->bind(); shader->setUniformValue(shader->lightP(), m_cachedScene->activeLight()->position()); shader->setUniformValue(shader->ambientS(), m_cachedTheme->ambientLightStrength()); shader->setUniformValue(shader->lightColor(), Utils::vectorFromColor(m_cachedTheme->lightColor())); shader->setUniformValue(shader->view(), viewMatrix); - - glEnable(GL_TEXTURE_2D); } - // Draw custom items - foreach (CustomRenderItem *item, m_customRenderCache) { - // Check that the render item is visible, and skip drawing if not - if (!item->isVisible()) - continue; - - // Check if the render item is in data coordinates and not within axis ranges, and skip drawing if it is - if (!item->isPositionAbsolute() - && (item->position().x() < m_axisCacheX.min() - || item->position().x() > m_axisCacheX.max() - || item->position().z() < m_axisCacheZ.min() - || item->position().z() > m_axisCacheZ.max() - || item->position().y() < m_axisCacheY.min() - || item->position().y() > m_axisCacheY.max())) { - continue; - } + // Draw custom items - first regular and then volumes + bool volumeDetected = false; + int loopCount = 0; + while (loopCount < 2) { + foreach (CustomRenderItem *item, m_customRenderCache) { + // Check that the render item is visible, and skip drawing if not + // Also check if reflected item is on the "wrong" side, and skip drawing if it is + if (!item->isVisible() || ((m_reflectionEnabled && reflection < 0.0f) + && (m_yFlipped == (item->translation().y() >= 0.0)))) { + continue; + } + if (loopCount == 0) { + if (item->isVolume()) { + volumeDetected = true; + continue; + } + } else { + if (!item->isVolume()) + continue; + } - QMatrix4x4 modelMatrix; - QMatrix4x4 itModelMatrix; - QMatrix4x4 MVPMatrix; - - QQuaternion rotation = item->rotation(); - // Check if the (label) item should be facing camera, and adjust rotation accordingly - if (item->isFacingCamera()) { - float camRotationX = m_cachedScene->activeCamera()->xRotation(); - float camRotationY = m_cachedScene->activeCamera()->yRotation(); - rotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -camRotationX) - * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -camRotationY); + // If the render item is in data coordinates and not within axis ranges, skip it + if (!item->isPositionAbsolute() + && (item->position().x() < m_axisCacheX.min() + || item->position().x() > m_axisCacheX.max() + || item->position().z() < m_axisCacheZ.min() + || item->position().z() > m_axisCacheZ.max() + || item->position().y() < m_axisCacheY.min() + || item->position().y() > m_axisCacheY.max())) { + continue; + } + + QMatrix4x4 modelMatrix; + QMatrix4x4 itModelMatrix; + QMatrix4x4 MVPMatrix; + + QQuaternion rotation = item->rotation(); + // Check if the (label) item should be facing camera, and adjust rotation accordingly + if (item->isFacingCamera()) { + float camRotationX = m_cachedScene->activeCamera()->xRotation(); + float camRotationY = m_cachedScene->activeCamera()->yRotation(); + rotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -camRotationX) + * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -camRotationY); + } + + if (m_reflectionEnabled) { + if (reflection < 0.0f) { + if (item->itemPointer()->d_ptr->m_isLabelItem) + continue; + else + glCullFace(GL_FRONT); + } else { + glCullFace(GL_BACK); + } + QVector3D trans = item->translation(); + trans.setY(reflection * trans.y()); + modelMatrix.translate(trans); + if (reflection < 0.0f) { + QQuaternion mirror = QQuaternion(rotation.scalar(), + -rotation.x(), rotation.y(), -rotation.z()); + modelMatrix.rotate(mirror); + itModelMatrix.rotate(mirror); + } else { + modelMatrix.rotate(rotation); + itModelMatrix.rotate(rotation); + } + QVector3D scale = item->scaling(); + scale.setY(reflection * scale.y()); + modelMatrix.scale(scale); + } else { + modelMatrix.translate(item->translation()); + modelMatrix.rotate(rotation); + modelMatrix.scale(item->scaling()); + itModelMatrix.rotate(rotation); + } + if (!item->isFacingCamera()) + itModelMatrix.scale(item->scaling()); + MVPMatrix = projectionViewMatrix * modelMatrix; + + if (RenderingNormal == state) { + // Normal render + ShaderHelper *prevShader = shader; + if (item->isVolume() && !m_isOpenGLES) { + if (item->drawSlices() && + (item->sliceIndexX() >= 0 + || item->sliceIndexY() >= 0 + || item->sliceIndexZ() >= 0)) { + shader = m_volumeTextureSliceShader; + } else if (item->useHighDefShader()) { + shader = m_volumeTextureShader; + } else { + shader = m_volumeTextureLowDefShader; + } + } else if (item->isLabel()) { + shader = m_labelShader; + } else { + shader = regularShader; + } + if (shader != prevShader) + shader->bind(); + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->MVP(), MVPMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + + if (item->isBlendNeeded()) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (!item->isVolume() && !m_isOpenGLES) + glDisable(GL_CULL_FACE); + } else { + glDisable(GL_BLEND); + glEnable(GL_CULL_FACE); + } + + if (!m_isOpenGLES && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone + && !item->isVolume()) { + // Set shadow shader bindings + shader->setUniformValue(shader->shadowQ(), shadowQuality); + shader->setUniformValue(shader->depth(), depthProjectionViewMatrix * modelMatrix); + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength() / 10.0f); + m_drawer->drawObject(shader, item->mesh(), item->texture(), depthTexture); + } else { + // Set shadowless shader bindings + if (item->isVolume() && !m_isOpenGLES) { + QVector3D cameraPos = m_cachedScene->activeCamera()->position(); + cameraPos = MVPMatrix.inverted().map(cameraPos); + // Adjust camera position according to min/max bounds + cameraPos = -(cameraPos + + ((oneVector - cameraPos) * item->minBoundsNormal()) + - ((oneVector + cameraPos) * (oneVector - item->maxBoundsNormal()))); + shader->setUniformValue(shader->cameraPositionRelativeToModel(), cameraPos); + GLint color8Bit = (item->textureFormat() == QImage::Format_Indexed8) ? 1 : 0; + if (color8Bit) { + shader->setUniformValueArray(shader->colorIndex(), + item->colorTable().constData(), 256); + } + shader->setUniformValue(shader->color8Bit(), color8Bit); + shader->setUniformValue(shader->alphaMultiplier(), item->alphaMultiplier()); + shader->setUniformValue(shader->preserveOpacity(), + item->preserveOpacity() ? 1 : 0); + + shader->setUniformValue(shader->minBounds(), item->minBounds()); + shader->setUniformValue(shader->maxBounds(), item->maxBounds()); + + if (shader == m_volumeTextureSliceShader) { + shader->setUniformValue(shader->volumeSliceIndices(), + item->sliceFractions()); + } else { + // Precalculate texture dimensions so we can optimize + // ray stepping to hit every texture layer. + QVector3D textureDimensions(1.0f / float(item->textureWidth()), + 1.0f / float(item->textureHeight()), + 1.0f / float(item->textureDepth())); + + // Worst case scenario sample count + int sampleCount; + if (shader == m_volumeTextureLowDefShader) { + sampleCount = qMax(item->textureWidth(), + qMax(item->textureDepth(), item->textureHeight())); + // Further improve speed with big textures by simply dropping every + // other sample: + if (sampleCount > 256) + sampleCount /= 2; + } else { + sampleCount = item->textureWidth() + item->textureHeight() + + item->textureDepth(); + } + shader->setUniformValue(shader->textureDimensions(), textureDimensions); + shader->setUniformValue(shader->sampleCount(), sampleCount); + } + if (item->drawSliceFrames()) { + // Set up the slice frame shader + glDisable(GL_CULL_FACE); + m_volumeSliceFrameShader->bind(); + m_volumeSliceFrameShader->setUniformValue( + m_volumeSliceFrameShader->color(), item->sliceFrameColor()); + + // Draw individual slice frames. + if (item->sliceIndexX() >= 0) + drawVolumeSliceFrame(item, Qt::XAxis, projectionViewMatrix); + if (item->sliceIndexY() >= 0) + drawVolumeSliceFrame(item, Qt::YAxis, projectionViewMatrix); + if (item->sliceIndexZ() >= 0) + drawVolumeSliceFrame(item, Qt::ZAxis, projectionViewMatrix); + + glEnable(GL_CULL_FACE); + shader->bind(); + } + m_drawer->drawObject(shader, item->mesh(), 0, 0, item->texture()); + } else { + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); + m_drawer->drawObject(shader, item->mesh(), item->texture()); + } + } + } else if (RenderingSelection == state) { + // Selection render + shader->setUniformValue(shader->MVP(), MVPMatrix); + QVector4D itemColor = indexToSelectionColor(item->index()); + itemColor.setW(customItemAlpha); + itemColor /= 255.0f; + shader->setUniformValue(shader->color(), itemColor); + m_drawer->drawObject(shader, item->mesh()); + } else if (item->isShadowCasting()) { + // Depth render + shader->setUniformValue(shader->MVP(), depthProjectionViewMatrix * modelMatrix); + m_drawer->drawObject(shader, item->mesh()); + } } + loopCount++; + if (!volumeDetected) + loopCount++; // Skip second run if no volumes detected + } + + if (RenderingNormal == state) { + glDisable(GL_BLEND); + glEnable(GL_CULL_FACE); + } +} + +void Abstract3DRenderer::drawVolumeSliceFrame(const CustomRenderItem *item, Qt::Axis axis, + const QMatrix4x4 &projectionViewMatrix) +{ + QVector2D frameWidth; + QVector3D frameScaling; + QVector3D translation = item->translation(); + QQuaternion rotation = item->rotation(); + float fracTrans; + bool needRotate = !rotation.isIdentity(); + QMatrix4x4 rotationMatrix; + if (needRotate) + rotationMatrix.rotate(rotation); + + if (axis == Qt::XAxis) { + fracTrans = item->sliceFractions().x(); + float range = item->maxBoundsNormal().x() - item->minBoundsNormal().x(); + float minMult = item->minBoundsNormal().x() / range; + float maxMult = (1.0f - item->maxBoundsNormal().x()) / range; + fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult); + if (needRotate) + translation += rotationMatrix.map(QVector3D(fracTrans * item->scaling().x(), 0.0f, 0.0f)); + else + translation.setX(translation.x() + fracTrans * item->scaling().x()); + frameScaling = QVector3D(item->scaling().z() + + (item->scaling().z() * item->sliceFrameGaps().z()) + + (item->scaling().z() * item->sliceFrameWidths().z()), + item->scaling().y() + + (item->scaling().y() * item->sliceFrameGaps().y()) + + (item->scaling().y() * item->sliceFrameWidths().y()), + item->scaling().x() * item->sliceFrameThicknesses().x()); + frameWidth = QVector2D(item->scaling().z() * item->sliceFrameWidths().z(), + item->scaling().y() * item->sliceFrameWidths().y()); + rotation *= m_yRightAngleRotation; + } else if (axis == Qt::YAxis) { + fracTrans = item->sliceFractions().y(); + float range = item->maxBoundsNormal().y() - item->minBoundsNormal().y(); + // Y axis is logically flipped, so we need to swam min and max bounds + float minMult = (1.0f - item->maxBoundsNormal().y()) / range; + float maxMult = item->minBoundsNormal().y() / range; + fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult); + if (needRotate) + translation -= rotationMatrix.map(QVector3D(0.0f, fracTrans * item->scaling().y(), 0.0f)); + else + translation.setY(translation.y() - fracTrans * item->scaling().y()); + frameScaling = QVector3D(item->scaling().x() + + (item->scaling().x() * item->sliceFrameGaps().x()) + + (item->scaling().x() * item->sliceFrameWidths().x()), + item->scaling().z() + + (item->scaling().z() * item->sliceFrameGaps().z()) + + (item->scaling().z() * item->sliceFrameWidths().z()), + item->scaling().y() * item->sliceFrameThicknesses().y()); + frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(), + item->scaling().z() * item->sliceFrameWidths().z()); + rotation *= m_xRightAngleRotation; + } else { // Z axis + fracTrans = item->sliceFractions().z(); + float range = item->maxBoundsNormal().z() - item->minBoundsNormal().z(); + // Z axis is logically flipped, so we need to swam min and max bounds + float minMult = (1.0f - item->maxBoundsNormal().z()) / range; + float maxMult = item->minBoundsNormal().z() / range; + fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult); + if (needRotate) + translation -= rotationMatrix.map(QVector3D(0.0f, 0.0f, fracTrans * item->scaling().z())); + else + translation.setZ(translation.z() - fracTrans * item->scaling().z()); + frameScaling = QVector3D(item->scaling().x() + + (item->scaling().x() * item->sliceFrameGaps().x()) + + (item->scaling().x() * item->sliceFrameWidths().x()), + item->scaling().y() + + (item->scaling().y() * item->sliceFrameGaps().y()) + + (item->scaling().y() * item->sliceFrameWidths().y()), + item->scaling().z() * item->sliceFrameThicknesses().z()); + frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(), + item->scaling().y() * item->sliceFrameWidths().y()); + } + + // If the slice is outside the shown area, don't show the frame + if (fracTrans < -1.0 || fracTrans > 1.0) + return; + + // Shader needs the width of clear space in the middle. + frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x())); + frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y())); - modelMatrix.translate(item->translation()); - modelMatrix.rotate(rotation); - modelMatrix.scale(item->scaling()); - itModelMatrix.rotate(rotation); - if (!item->isFacingCamera()) - itModelMatrix.scale(item->scaling()); - MVPMatrix = projectionViewMatrix * modelMatrix; + QMatrix4x4 modelMatrix; + QMatrix4x4 mvpMatrix; + + modelMatrix.translate(translation); + modelMatrix.rotate(rotation); + modelMatrix.scale(frameScaling); + mvpMatrix = projectionViewMatrix * modelMatrix; + m_volumeSliceFrameShader->setUniformValue(m_volumeSliceFrameShader->MVP(), mvpMatrix); + m_volumeSliceFrameShader->setUniformValue(m_volumeSliceFrameShader->sliceFrameWidth(), + frameWidth); + + m_drawer->drawObject(m_volumeSliceFrameShader, item->mesh()); + +} + +void Abstract3DRenderer::queriedGraphPosition(const QMatrix4x4 &projectionViewMatrix, + const QVector3D &scaling, + GLuint defaultFboHandle) +{ + m_cursorPositionShader->bind(); + + // Set up mapper framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, m_cursorPositionFrameBuffer); + glViewport(0, 0, + m_primarySubViewport.width(), + m_primarySubViewport.height()); + glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DITHER); // Dither may affect colors if enabled + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + + // Draw a cube scaled to the graph dimensions + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + + modelMatrix.scale(scaling); + + MVPMatrix = projectionViewMatrix * modelMatrix; + m_cursorPositionShader->setUniformValue(m_cursorPositionShader->MVP(), MVPMatrix); + m_drawer->drawObject(m_cursorPositionShader, m_positionMapperObj); + + QVector4D dataColor = Utils::getSelection(m_graphPositionQuery, + m_primarySubViewport.height()); + if (dataColor.w() > 0.0f) { + // If position is outside the graph, set the position well outside the graph boundaries + dataColor = QVector4D(-10000.0f, -10000.0f, -10000.0f, 0.0f); + } else { + // Normalize to range [0.0, 1.0] + dataColor /= 255.0f; + } + + // Restore state + glEnable(GL_DITHER); + glCullFace(GL_BACK); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); + glViewport(m_primarySubViewport.x(), + m_primarySubViewport.y(), + m_primarySubViewport.width(), + m_primarySubViewport.height()); + + QVector3D normalizedValues = dataColor.toVector3D() * 2.0f; + normalizedValues -= oneVector; + m_queriedGraphPosition = QVector3D(normalizedValues.x(), + normalizedValues.y(), + normalizedValues.z()); + m_graphPositionQueryResolved = true; + m_graphPositionQueryPending = false; +} + +void Abstract3DRenderer::fixContextBeforeDelete() +{ + // Only need to fix context if the current context is null. + // Otherwise we expect it to be our shared context, so we can use it for cleanup. + if (!QOpenGLContext::currentContext() && !m_context.isNull() + && QThread::currentThread() == this->thread()) { + m_dummySurfaceAtDelete = new QWindow(); + m_dummySurfaceAtDelete->setSurfaceType(QWindow::OpenGLSurface); + m_dummySurfaceAtDelete->setFormat(m_context->format()); + m_dummySurfaceAtDelete->create(); + + m_context->makeCurrent(m_dummySurfaceAtDelete); + } +} + +void Abstract3DRenderer::restoreContextAfterDelete() +{ + if (m_dummySurfaceAtDelete) + m_context->doneCurrent(); + + delete m_dummySurfaceAtDelete; + m_dummySurfaceAtDelete = 0; +} + +void Abstract3DRenderer::calculatePolarXZ(const QVector3D &dataPos, float &x, float &z) const +{ + // x is angular, z is radial + qreal angle = m_axisCacheX.formatter()->positionAt(dataPos.x()) * doublePi; + qreal radius = m_axisCacheZ.formatter()->positionAt(dataPos.z()); + + // Convert angle & radius to X and Z coords + x = float(radius * qSin(angle)) * m_polarRadius; + z = -float(radius * qCos(angle)) * m_polarRadius; +} + +void Abstract3DRenderer::drawRadialGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &depthMatrix) +{ + static QVector<QQuaternion> lineRotations; + if (!lineRotations.size()) { + lineRotations.resize(polarGridRoundness); + for (int j = 0; j < polarGridRoundness; j++) { + lineRotations[j] = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, + polarGridAngleDegrees * float(j)); + } + } + int gridLineCount = m_axisCacheZ.gridLineCount(); + const QVector<float> &gridPositions = m_axisCacheZ.formatter()->gridPositions(); + const QVector<float> &subGridPositions = m_axisCacheZ.formatter()->subGridPositions(); + int mainSize = gridPositions.size(); + QVector3D translateVector(0.0f, yFloorLinePos, 0.0f); + QQuaternion finalRotation = m_xRightAngleRotationNeg; + if (m_yFlippedForGrid) + finalRotation *= m_xFlipRotation; + + for (int i = 0; i < gridLineCount; i++) { + float gridPosition = (i >= mainSize) + ? subGridPositions.at(i - mainSize) : gridPositions.at(i); + float radiusFraction = m_polarRadius * gridPosition; + QVector3D gridLineScaler(radiusFraction * float(qSin(polarGridHalfAngle)), + gridLineWidth, gridLineWidth); + translateVector.setZ(gridPosition * m_polarRadius); + for (int j = 0; j < polarGridRoundness; j++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 itModelMatrix; + modelMatrix.rotate(lineRotations.at(j)); + itModelMatrix.rotate(lineRotations.at(j)); + modelMatrix.translate(translateVector); + modelMatrix.scale(gridLineScaler); + itModelMatrix.scale(gridLineScaler); + modelMatrix.rotate(finalRotation); + itModelMatrix.rotate(finalRotation); + QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix; - if (RenderingNormal == state) { - // Normal render shader->setUniformValue(shader->model(), modelMatrix); - shader->setUniformValue(shader->MVP(), MVPMatrix); shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); - - if (item->isBlendNeeded()) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); + shader->setUniformValue(shader->MVP(), MVPMatrix); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix; + shader->setUniformValue(shader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(shader, m_gridLineObj); + } } else { - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); + m_drawer->drawLine(shader); } + } + } +} -#if !defined(QT_OPENGL_ES_2) +void Abstract3DRenderer::drawAngularGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &depthMatrix) +{ + float halfRatio((m_polarRadius + (labelMargin / 2.0f)) / 2.0f); + QVector3D gridLineScaler(gridLineWidth, gridLineWidth, halfRatio); + int gridLineCount = m_axisCacheX.gridLineCount(); + const QVector<float> &gridPositions = m_axisCacheX.formatter()->gridPositions(); + const QVector<float> &subGridPositions = m_axisCacheX.formatter()->subGridPositions(); + int mainSize = gridPositions.size(); + QVector3D translateVector(0.0f, yFloorLinePos, -halfRatio); + QQuaternion finalRotation; + if (m_isOpenGLES) + finalRotation = m_yRightAngleRotationNeg; + else + finalRotation = m_xRightAngleRotationNeg; + if (m_yFlippedForGrid) + finalRotation *= m_xFlipRotation; + for (int i = 0; i < gridLineCount; i++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 itModelMatrix; + float gridPosition = (i >= mainSize) + ? subGridPositions.at(i - mainSize) : gridPositions.at(i); + QQuaternion lineRotation = QQuaternion::fromAxisAndAngle(upVector, gridPosition * 360.0f); + modelMatrix.rotate(lineRotation); + itModelMatrix.rotate(lineRotation); + modelMatrix.translate(translateVector); + modelMatrix.scale(gridLineScaler); + itModelMatrix.scale(gridLineScaler); + modelMatrix.rotate(finalRotation); + itModelMatrix.rotate(finalRotation); + QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix; + + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + shader->setUniformValue(shader->MVP(), MVPMatrix); + if (m_isOpenGLES) { + m_drawer->drawLine(shader); + } else { if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { // Set shadow shader bindings - shader->setUniformValue(shader->shadowQ(), shadowQuality); - shader->setUniformValue(shader->depth(), depthProjectionViewMatrix * modelMatrix); - shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength() / 10.0f); - m_drawer->drawObject(shader, item->mesh(), item->texture(), depthTexture); - } else -#else - Q_UNUSED(depthTexture) - Q_UNUSED(shadowQuality) -#endif - { - // Set shadowless shader bindings - shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); - m_drawer->drawObject(shader, item->mesh(), item->texture()); + QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix; + shader->setUniformValue(shader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(shader, m_gridLineObj); } - } else if (RenderingSelection == state) { - // Selection render - shader->setUniformValue(shader->MVP(), MVPMatrix); - QVector4D itemColor = indexToSelectionColor(item->index()); - itemColor.setW(customItemAlpha); - itemColor /= 255.0f; - shader->setUniformValue(shader->color(), itemColor); - m_drawer->drawObject(shader, item->mesh()); - } else if (item->isShadowCasting()) { - // Depth render - shader->setUniformValue(shader->MVP(), depthProjectionViewMatrix * modelMatrix); - m_drawer->drawObject(shader, item->mesh()); } } +} - if (RenderingNormal == state) { - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); +float Abstract3DRenderer::calculatePolarBackgroundMargin() +{ + // Check each extents of each angular label + // Calculate angular position + QVector<float> &labelPositions = m_axisCacheX.formatter()->labelPositions(); + float actualLabelHeight = m_drawer->scaledFontSize() * 2.0f; // All labels are same height + float maxNeededMargin = 0.0f; + + // Axis title needs to be accounted for + if (m_axisCacheX.isTitleVisible()) + maxNeededMargin = 2.0f * actualLabelHeight + 3.0f * labelMargin; + + for (int label = 0; label < labelPositions.size(); label++) { + QSize labelSize = m_axisCacheX.labelItems().at(label)->size(); + float actualLabelWidth = actualLabelHeight / labelSize.height() * labelSize.width(); + float labelPosition = labelPositions.at(label); + qreal angle = labelPosition * M_PI * 2.0; + float x = qAbs((m_polarRadius + labelMargin) * float(qSin(angle))) + + actualLabelWidth - m_polarRadius + labelMargin; + float z = qAbs(-(m_polarRadius + labelMargin) * float(qCos(angle))) + + actualLabelHeight - m_polarRadius + labelMargin; + float neededMargin = qMax(x, z); + maxNeededMargin = qMax(maxNeededMargin, neededMargin); } + + return maxNeededMargin; +} + +void Abstract3DRenderer::updateCameraViewport() +{ + QVector3D adjustedTarget = m_cachedScene->activeCamera()->target(); + fixCameraTarget(adjustedTarget); + if (m_oldCameraTarget != adjustedTarget) { + QVector3D cameraBase = cameraDistanceVector + adjustedTarget; + + m_cachedScene->activeCamera()->d_ptr->setBaseOrientation(cameraBase, + adjustedTarget, + upVector); + m_oldCameraTarget = adjustedTarget; + } + m_cachedScene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment); + // Set light position (i.e rotate light with activeCamera, a bit above it) + m_cachedScene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3drenderer_p.h b/src/datavisualization/engine/abstract3drenderer_p.h index 0dfc7367..1e38023d 100644 --- a/src/datavisualization/engine/abstract3drenderer_p.h +++ b/src/datavisualization/engine/abstract3drenderer_p.h @@ -30,13 +30,17 @@ #define ABSTRACT3DRENDERER_P_H #include <QtGui/QOpenGLFunctions> - +#if !defined(QT_OPENGL_ES_2) +# include <QtGui/QOpenGLFunctions_2_1> +#endif #include "datavisualizationglobal_p.h" #include "abstract3dcontroller_p.h" #include "axisrendercache_p.h" #include "seriesrendercache_p.h" #include "customrenderitem_p.h" +class QSurface; + QT_BEGIN_NAMESPACE_DATAVISUALIZATION class TextureHelper; @@ -77,21 +81,33 @@ public: virtual void updateSelectionMode(QAbstract3DGraph::SelectionFlags newMode); virtual void updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint); virtual void updateScene(Q3DScene *scene); - virtual void updateTextures() = 0; + virtual void updateTextures(); virtual void initSelectionBuffer() = 0; virtual void updateSelectionState(SelectionState state); - virtual void updateInputPosition(const QPoint &position); -#if !defined(QT_OPENGL_ES_2) virtual void updateDepthBuffer() = 0; -#endif virtual void updateShadowQuality(QAbstract3DGraph::ShadowQuality quality) = 0; virtual void initShaders(const QString &vertexShader, const QString &fragmentShader) = 0; virtual void initGradientShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader); virtual void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) = 0; virtual void initCustomItemShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initVolumeTextureShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &fragmentLowDefShader, + const QString &sliceShader, + const QString &sliceFrameVertexShader, + const QString &sliceFrameShader); + virtual void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initCursorPositionShaders(const QString &vertexShader, + const QString &fragmentShader); + virtual void initCursorPositionBuffer(); + virtual void updateAxisType(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis::AxisType type); virtual void updateAxisTitle(QAbstract3DAxis::AxisOrientation orientation, @@ -112,7 +128,7 @@ public: virtual void updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation, float angle); virtual void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, - bool visible); + bool visible); virtual void updateAxisTitleFixed(QAbstract3DAxis::AxisOrientation orientation, bool fixed); virtual void modifiedSeriesList(const QVector<QAbstract3DSeries *> &seriesList); @@ -123,6 +139,10 @@ public: virtual void updateCustomItem(CustomRenderItem *renderItem); virtual void updateAspectRatio(float ratio); + virtual void updateHorizontalAspectRatio(float ratio); + virtual void updatePolar(bool enable); + virtual void updateRadialLabelOffset(float offset); + virtual void updateMargin(float margin); virtual QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute) = 0; @@ -130,21 +150,28 @@ public: void generateBaseColorTexture(const QColor &color, GLuint *texture); void fixGradientAndGenerateTexture(QLinearGradient *gradient, GLuint *gradientTexture); - inline bool isClickPending() { return m_clickPending; } - inline void clearClickPending() { m_clickPending = false; } + inline bool isClickQueryResolved() const { return m_clickResolved; } + inline void clearClickQueryResolved() { m_clickResolved = false; } + inline QPoint cachedClickQuery() const { return m_cachedScene->selectionQueryPosition(); } inline QAbstract3DSeries *clickedSeries() const { return m_clickedSeries; } inline QAbstract3DGraph::ElementType clickedType() { return m_clickedType; } + inline bool isGraphPositionQueryResolved() const { return m_graphPositionQueryResolved; } + inline void clearGraphPositionQueryResolved() { m_graphPositionQueryResolved = false; } + inline QVector3D queriedGraphPosition() const { return m_queriedGraphPosition; } + inline QPoint cachedGraphPositionQuery() const { return m_cachedScene->graphPositionQuery(); } LabelItem &selectionLabelItem(); void setSelectionLabel(const QString &label); QString &selectionLabel(); - void drawCustomItems(RenderingState state, ShaderHelper *shader, + void drawCustomItems(RenderingState state, ShaderHelper *regularShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, - GLuint depthTexture, GLfloat shadowQuality); + GLuint depthTexture, GLfloat shadowQuality, GLfloat reflection = 1.0f); + QVector4D indexToSelectionColor(GLint index); + void calculatePolarXZ(const QVector3D &dataPos, float &x, float &z) const; signals: void needRender(); // Emit this if something in renderer causes need for another render pass. @@ -177,7 +204,7 @@ protected: const QQuaternion &totalRotation, AbstractRenderItem &dummyItem, const Q3DCamera *activeCamera, float labelsMaxWidth, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - ShaderHelper *shader); + ShaderHelper *shader, bool radial = false); void drawAxisTitleZ(const QVector3D &labelRotation, const QVector3D &labelTrans, const QQuaternion &totalRotation, AbstractRenderItem &dummyItem, const Q3DCamera *activeCamera, float labelsMaxWidth, @@ -186,6 +213,26 @@ protected: void loadGridLineMesh(); void loadLabelMesh(); + void loadPositionMapperMesh(); + + void drawRadialGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix); + void drawAngularGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix); + + float calculatePolarBackgroundMargin(); + virtual void fixCameraTarget(QVector3D &target) = 0; + void updateCameraViewport(); + + void recalculateCustomItemScalingAndPos(CustomRenderItem *item); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) = 0; + void drawVolumeSliceFrame(const CustomRenderItem *item, Qt::Axis axis, + const QMatrix4x4 &projectionViewMatrix); + void queriedGraphPosition(const QMatrix4x4 &projectionViewMatrix, const QVector3D &scaling, + GLuint defaultFboHandle); + + void fixContextBeforeDelete(); + void restoreContextAfterDelete(); bool m_hasNegativeValues; Q3DTheme *m_cachedTheme; @@ -201,6 +248,7 @@ protected: AxisRenderCache m_axisCacheY; AxisRenderCache m_axisCacheZ; TextureHelper *m_textureHelper; + GLuint m_depthTexture; Q3DScene *m_cachedScene; bool m_selectionDirty; @@ -212,28 +260,75 @@ protected: QRect m_secondarySubViewport; float m_devicePixelRatio; bool m_selectionLabelDirty; - bool m_clickPending; + bool m_clickResolved; + bool m_graphPositionQueryPending; + bool m_graphPositionQueryResolved; QAbstract3DSeries *m_clickedSeries; QAbstract3DGraph::ElementType m_clickedType; int m_selectedLabelIndex; int m_selectedCustomItemIndex; + QVector3D m_queriedGraphPosition; + QPoint m_graphPositionQuery; QString m_selectionLabel; LabelItem *m_selectionLabelItem; int m_visibleSeriesCount; ShaderHelper *m_customItemShader; + ShaderHelper *m_volumeTextureShader; + ShaderHelper *m_volumeTextureLowDefShader; + ShaderHelper *m_volumeTextureSliceShader; + ShaderHelper *m_volumeSliceFrameShader; + ShaderHelper *m_labelShader; + ShaderHelper *m_cursorPositionShader; + GLuint m_cursorPositionFrameBuffer; + GLuint m_cursorPositionTexture; bool m_useOrthoProjection; bool m_xFlipped; bool m_yFlipped; bool m_zFlipped; + bool m_yFlippedForGrid; ObjectHelper *m_backgroundObj; // Shared reference ObjectHelper *m_gridLineObj; // Shared reference ObjectHelper *m_labelObj; // Shared reference + ObjectHelper *m_positionMapperObj; // Shared reference float m_graphAspectRatio; + float m_graphHorizontalAspectRatio; + bool m_polarGraph; + float m_radialLabelOffset; + float m_polarRadius; + + QQuaternion m_xRightAngleRotation; + QQuaternion m_yRightAngleRotation; + QQuaternion m_zRightAngleRotation; + QQuaternion m_xRightAngleRotationNeg; + QQuaternion m_yRightAngleRotationNeg; + QQuaternion m_zRightAngleRotationNeg; + QQuaternion m_xFlipRotation; + QQuaternion m_zFlipRotation; + + float m_requestedMargin; + float m_vBackgroundMargin; + float m_hBackgroundMargin; + float m_scaleXWithBackground; + float m_scaleYWithBackground; + float m_scaleZWithBackground; + + QVector3D m_oldCameraTarget; + + bool m_reflectionEnabled; + qreal m_reflectivity; + + QLocale m_locale; +#if !defined(QT_OPENGL_ES_2) + QOpenGLFunctions_2_1 *m_funcs_2_1; // Not owned +#endif + QPointer<QOpenGLContext> m_context; // Not owned + QWindow *m_dummySurfaceAtDelete; + bool m_isOpenGLES; private: friend class Abstract3DController; diff --git a/src/datavisualization/engine/axisrendercache.cpp b/src/datavisualization/engine/axisrendercache.cpp index 0b7a5c7a..02e3b7f6 100644 --- a/src/datavisualization/engine/axisrendercache.cpp +++ b/src/datavisualization/engine/axisrendercache.cpp @@ -46,6 +46,7 @@ AxisRenderCache::~AxisRenderCache() { foreach (LabelItem *label, m_labelItems) delete label; + m_titleItem.clear(); delete m_formatter; } @@ -54,10 +55,8 @@ void AxisRenderCache::setDrawer(Drawer *drawer) { m_drawer = drawer; m_font = m_drawer->font(); - if (m_drawer) { - QObject::connect(m_drawer, &Drawer::drawerChanged, this, &AxisRenderCache::updateTextures); + if (m_drawer) updateTextures(); - } } void AxisRenderCache::setType(QAbstract3DAxis::AxisType type) @@ -106,10 +105,12 @@ void AxisRenderCache::setLabels(const QStringList &labels) if (i >= oldSize) m_labelItems.append(new LabelItem); if (m_drawer) { - if (labels.at(i).isEmpty()) + if (labels.at(i).isEmpty()) { m_labelItems[i]->clear(); - else if (i >= oldSize || labels.at(i) != m_labels.at(i)) + } else if (i >= oldSize || labels.at(i) != m_labels.at(i) + || m_labelItems[i]->size().width() != widest) { m_drawer->generateLabelItem(*m_labelItems[i], labels.at(i), widest); + } } } m_labels = labels; @@ -175,6 +176,13 @@ void AxisRenderCache::updateTextures() } } +void AxisRenderCache::clearLabels() +{ + m_titleItem.clear(); + for (int i = 0; i < m_labels.size(); i++) + m_labelItems[i]->clear(); +} + int AxisRenderCache::maxLabelWidth(const QStringList &labels) const { int labelWidth = 0; diff --git a/src/datavisualization/engine/axisrendercache_p.h b/src/datavisualization/engine/axisrendercache_p.h index 90321740..e32c590e 100644 --- a/src/datavisualization/engine/axisrendercache_p.h +++ b/src/datavisualization/engine/axisrendercache_p.h @@ -107,8 +107,8 @@ public: inline bool isTitleFixed() const { return m_titleFixed; } inline void setTitleFixed(bool fixed) { m_titleFixed = fixed; } -public slots: void updateTextures(); + void clearLabels(); private: int maxLabelWidth(const QStringList &labels) const; diff --git a/src/datavisualization/engine/bars3dcontroller.cpp b/src/datavisualization/engine/bars3dcontroller.cpp index 3a240c24..2092dd60 100644 --- a/src/datavisualization/engine/bars3dcontroller.cpp +++ b/src/datavisualization/engine/bars3dcontroller.cpp @@ -36,6 +36,7 @@ Bars3DController::Bars3DController(QRect boundRect, Q3DScene *scene) m_isBarSpecRelative(true), m_barThicknessRatio(1.0f), m_barSpacing(QSizeF(1.0, 1.0)), + m_floorLevel(0.0f), m_renderer(0) { // Setting a null axis creates a new default axis according to orientation and graph type. @@ -83,6 +84,12 @@ void Bars3DController::synchDataToRenderer() needSceneUpdate = true; } + // Floor level update requires data update, so do before abstract sync + if (m_changeTracker.floorLevelChanged) { + m_renderer->updateFloorLevel(m_floorLevel); + m_changeTracker.floorLevelChanged = false; + } + Abstract3DController::synchDataToRenderer(); // Notify changes to renderer @@ -114,12 +121,10 @@ void Bars3DController::synchDataToRenderer() m_changeTracker.selectedBarChanged = false; } - if (needSceneUpdate) { - // Since scene is updated before axis updates are handled, - // do another render pass for scene update - m_scene->d_ptr->m_sceneDirty = true; - emitNeedRender(); - } + // Since scene is updated before axis updates are handled, do another render pass to + // properly update controller side camera limits. + if (needSceneUpdate) + m_scene->d_ptr->markDirty(); } void Bars3DController::handleArrayReset() @@ -487,6 +492,19 @@ bool Bars3DController::isBarSpecRelative() return m_isBarSpecRelative; } +void Bars3DController::setFloorLevel(float level) +{ + m_floorLevel = level; + m_isDataDirty = true; + m_changeTracker.floorLevelChanged = true; + emitNeedRender(); +} + +float Bars3DController::floorLevel() const +{ + return m_floorLevel; +} + void Bars3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode) { if (mode.testFlag(QAbstract3DGraph::SelectionSlice) diff --git a/src/datavisualization/engine/bars3dcontroller_p.h b/src/datavisualization/engine/bars3dcontroller_p.h index 4f0e1f20..ac62f37d 100644 --- a/src/datavisualization/engine/bars3dcontroller_p.h +++ b/src/datavisualization/engine/bars3dcontroller_p.h @@ -43,13 +43,15 @@ struct Bars3DChangeBitField { bool selectedBarChanged : 1; bool rowsChanged : 1; bool itemChanged : 1; + bool floorLevelChanged : 1; Bars3DChangeBitField() : multiSeriesScalingChanged(true), barSpecsChanged(true), selectedBarChanged(true), rowsChanged(false), - itemChanged(false) + itemChanged(false), + floorLevelChanged(false) { } }; @@ -84,6 +86,7 @@ private: bool m_isBarSpecRelative; GLfloat m_barThicknessRatio; QSizeF m_barSpacing; + float m_floorLevel; // Rendering Bars3DRenderer *m_renderer; @@ -107,6 +110,8 @@ public: GLfloat barThickness(); QSizeF barSpacing(); bool isBarSpecRelative(); + void setFloorLevel(float level); + float floorLevel() const; inline QBar3DSeries *selectedSeries() const { return m_selectedBarSeries; } diff --git a/src/datavisualization/engine/bars3drenderer.cpp b/src/datavisualization/engine/bars3drenderer.cpp index 689f3f5d..b6a191a9 100644 --- a/src/datavisualization/engine/bars3drenderer.cpp +++ b/src/datavisualization/engine/bars3drenderer.cpp @@ -31,7 +31,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const GLfloat gridLineWidth = 0.005f; const bool sliceGridLabels = true; Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) @@ -48,9 +47,7 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_depthShader(0), m_selectionShader(0), m_backgroundShader(0), - m_labelShader(0), m_bgrTexture(0), - m_depthTexture(0), m_selectionTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), @@ -67,7 +64,6 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_scaleFactor(0), m_maxSceneSize(40.0f), m_visualSelectedBarPos(Bars3DController::invalidSelectionPosition()), - m_resetCameraBaseOrientation(true), m_selectedBarPos(Bars3DController::invalidSelectionPosition()), m_selectedSeriesCache(0), m_noZeroInRange(false), @@ -79,17 +75,22 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_keepSeriesUniform(false), m_haveUniformColorSeries(false), m_haveGradientSeries(false), - m_zeroPosition(0.0f) + m_zeroPosition(0.0f), + m_xScaleFactor(1.0f), + m_zScaleFactor(1.0f), + m_floorLevel(0.0f), + m_actualFloorLevel(0.0f) { m_axisCacheY.setScale(2.0f); m_axisCacheY.setTranslate(-1.0f); - initializeOpenGLFunctions(); initializeOpenGL(); } Bars3DRenderer::~Bars3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -103,7 +104,6 @@ Bars3DRenderer::~Bars3DRenderer() delete m_depthShader; delete m_selectionShader; delete m_backgroundShader; - delete m_labelShader; } void Bars3DRenderer::initializeOpenGL() @@ -111,13 +111,9 @@ void Bars3DRenderer::initializeOpenGL() Abstract3DRenderer::initializeOpenGL(); // Initialize shaders - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. initDepthShader(); -#endif // Init selection shader initSelectionShader(); @@ -125,13 +121,57 @@ void Bars3DRenderer::initializeOpenGL() // Load grid line mesh loadGridLineMesh(); - // Load label mesh - loadLabelMesh(); - // Load background mesh (we need to be initialized first) loadBackgroundMesh(); } +void Bars3DRenderer::fixCameraTarget(QVector3D &target) +{ + target.setX(target.x() * m_xScaleFactor); + target.setY(0.0f); + target.setZ(target.z() * -m_zScaleFactor); +} + +void Bars3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) +{ + // The inputs are the item bounds in OpenGL coordinates. + // The outputs limit these bounds to visible ranges, normalized to range [-1, 1] + // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those + float itemRangeX = (maxBounds.x() - minBounds.x()); + float itemRangeY = (maxBounds.y() - minBounds.y()); + float itemRangeZ = (maxBounds.z() - minBounds.z()); + + if (minBounds.x() < -m_xScaleFactor) + minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_xScaleFactor) / itemRangeX)); + else + minBounds.setX(-1.0f); + + if (minBounds.y() < -1.0f + m_backgroundAdjustment) + minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + 1.0f - m_backgroundAdjustment) / itemRangeY))); + else + minBounds.setY(1.0f); + + if (minBounds.z() < -m_zScaleFactor) + minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_zScaleFactor) / itemRangeZ))); + else + minBounds.setZ(1.0f); + + if (maxBounds.x() > m_xScaleFactor) + maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_xScaleFactor) / itemRangeX)); + else + maxBounds.setX(1.0f); + + if (maxBounds.y() > 1.0f + m_backgroundAdjustment) + maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - 1.0f - m_backgroundAdjustment) / itemRangeY))); + else + maxBounds.setY(-1.0f); + + if (maxBounds.z() > m_zScaleFactor) + maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_zScaleFactor) / itemRangeZ))); + else + maxBounds.setZ(-1.0f); +} + void Bars3DRenderer::updateData() { int minRow = m_axisCacheZ.min(); @@ -163,11 +203,11 @@ void Bars3DRenderer::updateData() GLfloat sceneRatio = qMin(GLfloat(newColumns) / GLfloat(newRows), GLfloat(newRows) / GLfloat(newColumns)); m_maxSceneSize = 2.0f * qSqrt(sceneRatio * newColumns * newRows); - // Calculate here and at setting bar specs - calculateSceneScalingFactors(); } - m_zeroPosition = m_axisCacheY.formatter()->positionAt(0.0f); + calculateSceneScalingFactors(); + + m_zeroPosition = m_axisCacheY.formatter()->positionAt(m_actualFloorLevel); foreach (SeriesRenderCache *baseCache, m_renderCacheList) { BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache); @@ -390,14 +430,6 @@ void Bars3DRenderer::updateScene(Q3DScene *scene) } } - if (m_resetCameraBaseOrientation) { - // Set initial camera position. Also update if height adjustment has changed. - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, - zeroVector, - upVector); - m_resetCameraBaseOrientation = false; - } - Abstract3DRenderer::updateScene(scene); updateSlicingActive(scene->isSlicingActive()); @@ -418,10 +450,17 @@ void Bars3DRenderer::render(GLuint defaultFboHandle) void Bars3DRenderer::drawSlicedScene() { + if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow) + == m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) { + qWarning("Invalid selection mode. Either QAbstract3DGraph::SelectionRow or" + " QAbstract3DGraph::SelectionColumn must be set before calling" + " setSlicingActive(true)."); + return; + } + GLfloat barPosX = 0; QVector3D lightPos; QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); - static QQuaternion ninetyDegreeRotation = QQuaternion::fromAxisAndAngle(upVector, 90.0f); // Specify viewport glViewport(m_secondarySubViewport.x(), @@ -482,11 +521,12 @@ void Bars3DRenderer::drawSlicedScene() // Draw grid lines if (m_cachedTheme->isGridEnabled()) { glDisable(GL_DEPTH_TEST); -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; + // Bind line shader lineShader->bind(); @@ -496,7 +536,8 @@ void Bars3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->view(), viewMatrix); lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); lineShader->setUniformValue(lineShader->lightS(), 0.0f); lineShader->setUniformValue(lineShader->lightColor(), lightColor); @@ -524,11 +565,10 @@ void Bars3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); // Check if we have a line at zero position already if (gridPos == (barPosYAdjustment + zeroPosAdjustment)) @@ -553,18 +593,16 @@ void Bars3DRenderer::drawSlicedScene() m_cachedTheme->labelTextColor())); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } if (sliceGridLabels) { // Bind label shader m_labelShader->bind(); - glEnable(GL_TEXTURE_2D); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -587,7 +625,6 @@ void Bars3DRenderer::drawSlicedScene() } labelNbr++; } - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } @@ -606,14 +643,16 @@ void Bars3DRenderer::drawSlicedScene() m_barShader->setUniformValue(m_barShader->view(), viewMatrix); m_barShader->setUniformValue(m_barShader->lightS(), 0.15f); m_barShader->setUniformValue(m_barShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); m_barShader->setUniformValue(m_barShader->lightColor(), lightColor); m_barGradientShader->bind(); m_barGradientShader->setUniformValue(m_barGradientShader->lightP(), lightPos); m_barGradientShader->setUniformValue(m_barGradientShader->view(), viewMatrix); m_barGradientShader->setUniformValue(m_barGradientShader->lightS(), 0.15f); m_barGradientShader->setUniformValue(m_barGradientShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); m_barGradientShader->setUniformValue(m_barGradientShader->gradientMin(), 0.0f); m_barGradientShader->setUniformValue(m_barGradientShader->lightColor(), lightColor); @@ -700,7 +739,7 @@ void Bars3DRenderer::drawSlicedScene() barPosX = item.translation().x(); } else { barPosX = -(item.translation().z()); // flip z; frontmost bar to the left - barRotation *= ninetyDegreeRotation; + barRotation *= m_yRightAngleRotation; } modelMatrix.translate(barPosX, barPosY, 0.0f); @@ -760,7 +799,6 @@ void Bars3DRenderer::drawSlicedScene() // Draw labels m_labelShader->bind(); glDisable(GL_DEPTH_TEST); - glEnable(GL_TEXTURE_2D); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -778,6 +816,12 @@ void Bars3DRenderer::drawSlicedScene() int labelCount = m_sliceCache->labelItems().size(); for (int labelNo = 0; labelNo < labelCount; labelNo++) { + // Check for invalid usage (no selection when setting slicing active) + if (!firstVisualSliceArray) { + qWarning("No slice data found. Make sure there is a valid selection."); + continue; + } + // Get labels from first series only const BarRenderSliceItem &item = firstVisualSliceArray->at(labelNo); m_dummyBarRenderItem.setTranslation(QVector3D(item.translation().x(), @@ -889,7 +933,6 @@ void Bars3DRenderer::drawSlicedScene() m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera, false, false, Drawer::LabelMid, Qt::AlignBottom); - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); @@ -912,8 +955,6 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) GLfloat colPos = 0; GLfloat rowPos = 0; - QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); - const Q3DCamera *activeCamera = m_cachedScene->activeCamera(); glViewport(m_primarySubViewport.x(), @@ -990,15 +1031,9 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix; - bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow); - - GLfloat rowScaleFactor = m_rowWidth / m_scaleFactor; - GLfloat columnScaleFactor = m_columnDepth / m_scaleFactor; - BarRenderItem *selectedBar(0); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Render scene into a depth texture for using with shadow mapping // Enable drawing to depth framebuffer glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); @@ -1052,6 +1087,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) shadowOffset = -0.015f; } + if (m_cachedTheme->isBackgroundEnabled() && m_reflectionEnabled + && ((m_yFlipped && item.height() > 0.0) + || (!m_yFlipped && item.height() < 0.0))) { + continue; + } + QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1097,8 +1138,9 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1112,12 +1154,22 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_primarySubViewport.width(), m_primarySubViewport.height()); } -#endif + + // Do position mapping when necessary + if (m_graphPositionQueryPending) { + QVector3D graphDimensions(m_xScaleFactor, 0.0f, m_zScaleFactor); + queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle); + + // Y is always at floor level + m_queriedGraphPosition.setY(0.0f); + emit needRender(); + } // Skip selection mode drawing if we're slicing or have no selection mode if (!m_cachedIsSlicingActivated && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone && m_selectionState == SelectOnScene - && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())) { + && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty()) + && m_selectionTexture) { // Bind selection shader m_selectionShader->bind(); @@ -1181,18 +1233,20 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } glCullFace(GL_BACK); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); - drawLabels(true, activeCamera, viewMatrix, projectionMatrix, rowScaleFactor, - columnScaleFactor); + drawLabels(true, activeCamera, viewMatrix, projectionMatrix); + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix, false, true); glEnable(GL_DITHER); // Read color under cursor - QVector4D clickedColor = Utils::getSelection(m_inputPosition, - m_viewport.height()); + QVector4D clickedColor = Utils::getSelection(m_inputPosition, m_viewport.height()); m_clickedPosition = selectionColorToArrayPosition(clickedColor); m_clickedSeries = selectionColorToSeries(clickedColor); + m_clickResolved = true; emit needRender(); @@ -1204,8 +1258,133 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_primarySubViewport.height()); } - // Enable texturing - glEnable(GL_TEXTURE_2D); + if (m_reflectionEnabled) { + // + // Draw reflections + // + glDisable(GL_DEPTH_TEST); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_STENCIL_TEST); + glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); + glStencilFunc(GL_ALWAYS, 1, 0xffffffff); + + // Draw background stencil + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glEnable(GL_DEPTH_TEST); + + glStencilFunc(GL_EQUAL, 1, 0xffffffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + // Set light + QVector3D reflectionLightPos = lightPos; + reflectionLightPos.setY(-(lightPos.y())); + m_cachedScene->activeLight()->setPosition(reflectionLightPos); + + // Draw bar reflections + (void)drawBars(&selectedBar, depthProjectionViewMatrix, + projectionViewMatrix, viewMatrix, + startRow, stopRow, stepRow, + startBar, stopBar, stepBar, -1.0f); + + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, + viewMatrix, projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader, -1.0f); + + // Reset light + m_cachedScene->activeLight()->setPosition(lightPos); + + glDisable(GL_STENCIL_TEST); + + glCullFace(GL_BACK); + } + + // + // Draw the real scene + // + // Draw background + if (m_reflectionEnabled) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix, true); + glDisable(GL_BLEND); + } else { + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix); + } + + // Draw bars + bool barSelectionFound = drawBars(&selectedBar, depthProjectionViewMatrix, + projectionViewMatrix, viewMatrix, + startRow, stopRow, stepRow, + startBar, stopBar, stepBar); + + // Draw grid lines + drawGridLines(depthProjectionViewMatrix, projectionViewMatrix, viewMatrix); + + // Draw custom items + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, + projectionViewMatrix, depthProjectionViewMatrix, + m_depthTexture, m_shadowQualityToShader); + + // Draw labels + drawLabels(false, activeCamera, viewMatrix, projectionMatrix); + + // Handle selected bar label generation + if (barSelectionFound) { + // Print value of selected bar + glDisable(GL_DEPTH_TEST); + // Draw the selection label + LabelItem &labelItem = selectionLabelItem(); + if (m_selectedBar != selectedBar || m_updateLabels || !labelItem.textureId() + || m_selectionLabelDirty) { + QString labelText = selectionLabel(); + if (labelText.isNull() || m_selectionLabelDirty) { + labelText = m_selectedSeriesCache->itemLabel(); + setSelectionLabel(labelText); + m_selectionLabelDirty = false; + } + m_drawer->generateLabelItem(labelItem, labelText); + m_selectedBar = selectedBar; + } + + Drawer::LabelPosition position = + m_selectedBar->height() >= 0 ? Drawer::LabelOver : Drawer::LabelBelow; + + m_drawer->drawLabel(*selectedBar, labelItem, viewMatrix, projectionMatrix, + zeroVector, identityQuaternion, selectedBar->height(), + m_cachedSelectionMode, m_labelShader, + m_labelObj, activeCamera, true, false, position); + + // Reset label update flag; they should have been updated when we get here + m_updateLabels = false; + + glEnable(GL_DEPTH_TEST); + } else { + m_selectedBar = 0; + } + + glDisable(GL_BLEND); + + // Release shader + glUseProgram(0); + m_selectionDirty = false; +} + +bool Bars3DRenderer::drawBars(BarRenderItem **selectedBar, + const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + GLint startRow, GLint stopRow, GLint stepRow, + GLint startBar, GLint stopBar, GLint stepBar, GLfloat reflection) +{ + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + + bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow); ShaderHelper *barShader = 0; GLuint gradientTexture = 0; @@ -1251,7 +1430,6 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_sliceTitleItem = 0; } - // Draw bars glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(0.5f, 1.0f); @@ -1302,12 +1480,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } previousColorStyle = colorStyle; - for (int row = startRow; row != stopRow; row += stepRow) { BarRenderItemRow &renderRow = renderArray[row]; for (int bar = startBar; bar != stopBar; bar += stepBar) { BarRenderItem &item = renderRow[bar]; - if (item.height() < 0) + float adjustedHeight = reflection * item.height(); + if (adjustedHeight < 0) glCullFace(GL_FRONT); else glCullFace(GL_BACK); @@ -1316,13 +1494,13 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 itModelMatrix; QMatrix4x4 MVPMatrix; - colPos = (bar + seriesPos) * (m_cachedBarSpacing.width()); - rowPos = (row + 0.5f) * (m_cachedBarSpacing.height()); + GLfloat colPos = (bar + seriesPos) * (m_cachedBarSpacing.width()); + GLfloat rowPos = (row + 0.5f) * (m_cachedBarSpacing.height()); modelMatrix.translate((colPos - m_rowWidth) / m_scaleFactor, - item.height(), + adjustedHeight, (m_columnDepth - rowPos) / m_scaleFactor); - modelScaler.setY(item.height()); + modelScaler.setY(adjustedHeight); if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { QQuaternion totalRotation = seriesRotation * item.rotation(); modelMatrix.rotate(totalRotation); @@ -1357,8 +1535,8 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) // We have no ownership, don't delete the previous one if (!m_cachedIsSlicingActivated && m_selectedSeriesCache == cache) { - selectedBar = &item; - selectedBar->setPosition(QPoint(row, bar)); + *selectedBar = &item; + (*selectedBar)->setPosition(QPoint(row, bar)); item.setTranslation(modelMatrix.column(3).toVector3D()); barSelectionFound = true; } @@ -1438,8 +1616,15 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } - // Skip drawing of 0-height bars - if (item.height() != 0) { + if (item.height() == 0) { + continue; + } else if ((m_reflectionEnabled + && (reflection == 1.0f + || (reflection != 1.0f + && ((m_yFlipped && item.height() < 0.0) + || (!m_yFlipped && item.height() > 0.0))))) + || !m_reflectionEnabled) { + // Skip drawing of 0-height bars and reflections of bars on the "wrong side" // Set shader bindings barShader->setUniformValue(barShader->model(), modelMatrix); barShader->setUniformValue(barShader->nModel(), @@ -1452,8 +1637,10 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) qAbs(item.height()) / m_gradientFraction); } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (((m_reflectionEnabled && reflection == 1.0f + && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) + || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) + && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; barShader->setUniformValue(barShader->shadowQ(), @@ -1465,13 +1652,15 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) // Draw the object m_drawer->drawObject(barShader, barObj, gradientTexture, m_depthTexture); - } else -#else - Q_UNUSED(shadowLightStrength); -#endif - { + } else { // Set shadowless shader bindings - barShader->setUniformValue(barShader->lightS(), lightStrength); + if (m_reflectionEnabled && reflection != 1.0f + && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + barShader->setUniformValue(barShader->lightS(), + adjustedLightStrength); + } else { + barShader->setUniformValue(barShader->lightS(), lightStrength); + } // Draw the object m_drawer->drawObject(barShader, barObj, gradientTexture); @@ -1481,122 +1670,145 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } } - glDisable(GL_POLYGON_OFFSET_FILL); // Reset culling glCullFace(GL_BACK); - // Bind background shader - m_backgroundShader->bind(); + return barSelectionFound; +} +void Bars3DRenderer::drawBackground(GLfloat backgroundRotation, + const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix, bool reflectingDraw, + bool drawingSelectionBuffer) +{ // Draw background if (m_cachedTheme->isBackgroundEnabled() && m_backgroundObj) { + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f; + ShaderHelper *shader = 0; + + // Bind background shader + if (drawingSelectionBuffer) + shader = m_selectionShader; // Use single color shader when drawing to selection buffer + else + shader = m_backgroundShader; + shader->bind(); + QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - QVector3D backgroundScaler(rowScaleFactor, 1.0f, columnScaleFactor); - modelMatrix.translate(0.0f, m_backgroundAdjustment, 0.0f); + QVector3D backgroundScaler(m_scaleXWithBackground, m_scaleYWithBackground, + m_scaleZWithBackground); + QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor()); + if (m_reflectionEnabled) + backgroundColor.setW(backgroundColor.w() * m_reflectivity); + // Set shader bindings + shader->setUniformValue(shader->lightP(), lightPos); + shader->setUniformValue(shader->view(), viewMatrix); + if (drawingSelectionBuffer) { + // Use selectionSkipColor for background when drawing to selection buffer + shader->setUniformValue(shader->color(), selectionSkipColor); + } else { + shader->setUniformValue(shader->color(), backgroundColor); + } + shader->setUniformValue(shader->ambientS(), + m_cachedTheme->ambientLightStrength() * 2.0f); + shader->setUniformValue(shader->lightColor(), lightColor); + + // Draw floor modelMatrix.scale(backgroundScaler); - itModelMatrix.scale(backgroundScaler); - modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); - itModelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); + + if (m_yFlipped) + modelMatrix.rotate(m_xRightAngleRotation); + else + modelMatrix.rotate(m_xRightAngleRotationNeg); + + itModelMatrix = modelMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else MVPMatrix = projectionViewMatrix * modelMatrix; #endif - QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor()); - - // Set shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->lightP(), lightPos); - m_backgroundShader->setUniformValue(m_backgroundShader->view(), viewMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->nModel(), - itModelMatrix.inverted().transposed()); - m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->color(), backgroundColor); - m_backgroundShader->setUniformValue(m_backgroundShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.0f); - m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); + // Set changed shader bindings + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + shader->setUniformValue(shader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(), - m_shadowQualityToShader); - m_backgroundShader->setUniformValue(m_backgroundShader->depth(), depthMVPMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), - adjustedLightStrength); - + shader->setUniformValue(shader->depth(), depthMVPMatrix); // Draw the object - m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#endif - { - // Set shadowless shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), - m_cachedTheme->lightStrength()); - + m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture); + } else { // Draw the object - m_drawer->drawObject(m_backgroundShader, m_backgroundObj); + m_drawer->drawObject(shader, m_gridLineObj); } - // Draw floor + // Draw walls modelMatrix = QMatrix4x4(); itModelMatrix = QMatrix4x4(); + modelMatrix.translate(0.0f, m_backgroundAdjustment, 0.0f); modelMatrix.scale(backgroundScaler); - - if (m_yFlipped) - modelMatrix.rotate(90.0f, 1.0f, 0.0f, 0.0f); - else - modelMatrix.rotate(-90.0f, 1.0f, 0.0f, 0.0f); - - itModelMatrix = modelMatrix; + itModelMatrix.scale(backgroundScaler); + modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); + itModelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); #ifdef SHOW_DEPTH_TEXTURE_SCENE MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else MVPMatrix = projectionViewMatrix * modelMatrix; #endif + // Set changed shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->nModel(), - itModelMatrix.inverted().transposed()); - m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix); + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + shader->setUniformValue(shader->MVP(), MVPMatrix); + if (!m_reflectionEnabled || (m_reflectionEnabled && reflectingDraw)) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + shader->setUniformValue(shader->shadowQ(), m_shadowQualityToShader); + shader->setUniformValue(shader->depth(), depthMVPMatrix); + shader->setUniformValue(shader->lightS(), adjustedLightStrength); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - m_backgroundShader->setUniformValue(m_backgroundShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(m_backgroundShader, m_gridLineObj, 0, m_depthTexture); - } else -#endif - { - // Draw the object - m_drawer->drawObject(m_backgroundShader, m_gridLineObj); + // Draw the object + m_drawer->drawObject(shader, m_backgroundObj, 0, m_depthTexture); + } else { + // Set shadowless shader bindings + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); + + // Draw the object + m_drawer->drawObject(shader, m_backgroundObj); + } } } +} - // Disable textures - glDisable(GL_TEXTURE_2D); - - // Draw grid lines +void Bars3DRenderer::drawGridLines(const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix) +{ if (m_cachedTheme->isGridEnabled()) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; + QQuaternion lineRotation; + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + // Bind bar shader lineShader->bind(); @@ -1607,15 +1819,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), barColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadowed shader bindings lineShader->setUniformValue(lineShader->shadowQ(), m_shadowQualityToShader); lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 20.0f); - } else -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1625,12 +1834,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) if (m_yFlipped) yFloorLinePosition = -yFloorLinePosition; - QVector3D gridLineScaler(rowScaleFactor, gridLineWidth, gridLineWidth); + QVector3D gridLineScaler(m_scaleXWithBackground, gridLineWidth, gridLineWidth); if (m_yFlipped) - lineRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + lineRotation = m_xRightAngleRotation; else - lineRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineRotation = m_xRightAngleRotationNeg; // Floor lines: rows for (GLfloat row = 0.0f; row <= m_cachedRowCount; row++) { @@ -1638,7 +1847,7 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - rowPos = row * m_cachedBarSpacing.height(); + GLfloat rowPos = row * m_cachedBarSpacing.height(); modelMatrix.translate(0.0f, yFloorLinePosition, (m_columnDepth - rowPos) / m_scaleFactor); modelMatrix.scale(gridLineScaler); @@ -1654,33 +1863,32 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } -#else - m_drawer->drawLine(lineShader); -#endif } // Floor lines: columns -#if defined(QT_OPENGL_ES_2) - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, columnScaleFactor); + if (m_isOpenGLES) + lineRotation = m_yRightAngleRotation; + gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground); for (GLfloat bar = 0.0f; bar <= m_cachedColumnCount; bar++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - colPos = bar * m_cachedBarSpacing.width(); + GLfloat colPos = bar * m_cachedBarSpacing.width(); modelMatrix.translate((m_rowWidth - colPos) / m_scaleFactor, yFloorLinePosition, 0.0f); modelMatrix.scale(gridLineScaler); @@ -1696,31 +1904,31 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } -#else - m_drawer->drawLine(lineShader); -#endif } if (m_axisCacheY.segmentCount() > 0) { // Wall lines: back wall int gridLineCount = m_axisCacheY.gridLineCount(); - GLfloat zWallLinePosition = -columnScaleFactor + gridLineOffset; + GLfloat zWallLinePosition = -m_scaleZWithBackground + gridLineOffset; if (m_zFlipped) zWallLinePosition = -zWallLinePosition; - gridLineScaler = QVector3D(rowScaleFactor, gridLineWidth, gridLineWidth); + gridLineScaler = QVector3D(m_scaleXWithBackground, gridLineWidth, gridLineWidth); for (int line = 0; line < gridLineCount; line++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1732,8 +1940,8 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) modelMatrix.scale(gridLineScaler); itModelMatrix.scale(gridLineScaler); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1744,33 +1952,33 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } -#else - m_drawer->drawLine(lineShader); -#endif } // Wall lines: side wall - GLfloat xWallLinePosition = -rowScaleFactor + gridLineOffset; + GLfloat xWallLinePosition = -m_scaleXWithBackground + gridLineOffset; if (m_xFlipped) xWallLinePosition = -xWallLinePosition; if (m_xFlipped) - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineRotation = m_yRightAngleRotationNeg; else - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineRotation = m_yRightAngleRotation; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, columnScaleFactor); + gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground); for (int line = 0; line < gridLineCount; line++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1792,76 +2000,27 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } } - - Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); - - drawLabels(false, activeCamera, viewMatrix, projectionMatrix, rowScaleFactor, - columnScaleFactor); - - // Handle selected bar label generation - if (barSelectionFound) { - // Print value of selected bar - glDisable(GL_DEPTH_TEST); - // Draw the selection label - LabelItem &labelItem = selectionLabelItem(); - if (m_selectedBar != selectedBar || m_updateLabels || !labelItem.textureId() - || m_selectionLabelDirty) { - QString labelText = selectionLabel(); - if (labelText.isNull() || m_selectionLabelDirty) { - labelText = m_selectedSeriesCache->itemLabel(); - setSelectionLabel(labelText); - m_selectionLabelDirty = false; - } - m_drawer->generateLabelItem(labelItem, labelText); - m_selectedBar = selectedBar; - } - - Drawer::LabelPosition position = - m_selectedBar->height() >= 0 ? Drawer::LabelOver : Drawer::LabelBelow; - - m_drawer->drawLabel(*selectedBar, labelItem, viewMatrix, projectionMatrix, - zeroVector, identityQuaternion, selectedBar->height(), - m_cachedSelectionMode, m_labelShader, - m_labelObj, activeCamera, true, false, position); - - // Reset label update flag; they should have been updated when we get here - m_updateLabels = false; - - glEnable(GL_DEPTH_TEST); - } else { - m_selectedBar = 0; - } - - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - - // Release shader - glUseProgram(0); - m_selectionDirty = false; } void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamera, - const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - GLfloat rowScaleFactor, GLfloat columnScaleFactor) { + const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix) { ShaderHelper *shader = 0; GLfloat alphaForValueSelection = labelValueAlpha / 255.0f; GLfloat alphaForRowSelection = labelRowAlpha / 255.0f; @@ -1873,7 +2032,6 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1894,8 +2052,8 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer int labelCount = m_axisCacheY.labelCount(); GLfloat labelMarginXTrans = labelMargin; GLfloat labelMarginZTrans = labelMargin; - GLfloat labelXTrans = rowScaleFactor; - GLfloat labelZTrans = columnScaleFactor; + GLfloat labelXTrans = m_scaleXWithBackground; + GLfloat labelZTrans = m_scaleZWithBackground; QVector3D backLabelRotation(0.0f, -90.0f, 0.0f); QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f); Qt::AlignmentFlag backAlignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; @@ -1999,10 +2157,8 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; GLfloat labelYAdjustment = 0.005f; - GLfloat scaledRowWidth = rowScaleFactor; - GLfloat scaledColumnDepth = columnScaleFactor; - GLfloat colPosValue = scaledRowWidth + labelMargin; - GLfloat rowPosValue = scaledColumnDepth + labelMargin; + GLfloat colPosValue = m_scaleXWithBackground + labelMargin; + GLfloat rowPosValue = m_scaleZWithBackground + labelMargin; GLfloat rowPos = 0.0f; GLfloat colPos = 0.0f; Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; @@ -2296,14 +2452,8 @@ void Bars3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orientatio { Abstract3DRenderer::updateAxisRange(orientation, min, max); - if (orientation == QAbstract3DAxis::AxisOrientationY) { - // Check if we have negative values - if (min < 0) - m_hasNegativeValues = true; - else if (min >= 0) - m_hasNegativeValues = false; + if (orientation == QAbstract3DAxis::AxisOrientationY) calculateHeightAdjustment(); - } } void Bars3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation orientation, bool enable) @@ -2384,10 +2534,12 @@ void Bars3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality quality handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif + + // Redraw to handle both reflections and shadows on background + if (m_reflectionEnabled) + needRender(); } void Bars3DRenderer::loadBackgroundMesh() @@ -2398,6 +2550,8 @@ void Bars3DRenderer::loadBackgroundMesh() void Bars3DRenderer::updateTextures() { + Abstract3DRenderer::updateTextures(); + // Drawer has changed; this flag needs to be checked when checking if we need to update labels m_updateLabels = true; } @@ -2420,30 +2574,60 @@ void Bars3DRenderer::calculateSceneScalingFactors() m_maxDimension = qMax(m_rowWidth, m_columnDepth); m_scaleFactor = qMin((m_cachedColumnCount * (m_maxDimension / m_maxSceneSize)), (m_cachedRowCount * (m_maxDimension / m_maxSceneSize))); + + // Single bar scaling m_scaleX = m_cachedBarThickness.width() / m_scaleFactor; m_scaleZ = m_cachedBarThickness.height() / m_scaleFactor; + + // Whole graph scale factors + m_xScaleFactor = m_rowWidth / m_scaleFactor; + m_zScaleFactor = m_columnDepth / m_scaleFactor; + + if (m_requestedMargin < 0.0f) { + m_hBackgroundMargin = 0.0f; + m_vBackgroundMargin = 0.0f; + } else { + m_hBackgroundMargin = m_requestedMargin; + m_vBackgroundMargin = m_requestedMargin; + } + + m_scaleXWithBackground = m_xScaleFactor + m_hBackgroundMargin; + m_scaleYWithBackground = 1.0f + m_vBackgroundMargin; + m_scaleZWithBackground = m_zScaleFactor + m_hBackgroundMargin; + + updateCameraViewport(); + updateCustomItemPositions(); } void Bars3DRenderer::calculateHeightAdjustment() { + float min = m_axisCacheY.min(); + float max = m_axisCacheY.max(); GLfloat newAdjustment = 1.0f; - GLfloat maxAbs = qFabs(m_axisCacheY.max()); - - if (m_axisCacheY.max() < 0.0f) { - m_heightNormalizer = GLfloat(qFabs(m_axisCacheY.min()) - qFabs(m_axisCacheY.max())); - maxAbs = qFabs(m_axisCacheY.max()) - qFabs(m_axisCacheY.min()); + m_actualFloorLevel = qBound(min, m_floorLevel, max); + GLfloat maxAbs = qFabs(max - m_actualFloorLevel); + + // Check if we have negative values + if (min < m_actualFloorLevel) + m_hasNegativeValues = true; + else if (min >= m_actualFloorLevel) + m_hasNegativeValues = false; + + if (max < m_actualFloorLevel) { + m_heightNormalizer = GLfloat(qFabs(min) - qFabs(max)); + maxAbs = qFabs(max) - qFabs(min); } else { - m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min()); + m_heightNormalizer = GLfloat(max - min); } // Height fractions are used in gradient calculations and are therefore doubled // Note that if max or min is exactly zero, we still consider it outside the range - if (m_axisCacheY.max() <= 0.0f || m_axisCacheY.min() >= 0.0f) { + if (max <= m_actualFloorLevel || min >= m_actualFloorLevel) { m_noZeroInRange = true; m_gradientFraction = 2.0f; } else { m_noZeroInRange = false; - GLfloat minAbs = qFabs(m_axisCacheY.min()); + GLfloat minAbs = qFabs(min - m_actualFloorLevel); m_gradientFraction = qMax(minAbs, maxAbs) / m_heightNormalizer * 2.0f; } @@ -2551,13 +2735,13 @@ void Bars3DRenderer::updateSlicingActive(bool isSlicing) m_cachedIsSlicingActivated = isSlicing; - if (!m_cachedIsSlicingActivated) - initSelectionBuffer(); // We need to re-init selection buffer in case there has been a resize + if (!m_cachedIsSlicingActivated) { + // We need to re-init selection buffer in case there has been a resize + initSelectionBuffer(); + initCursorPositionBuffer(); + } -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); // Re-init depth buffer as well -#endif - m_selectionDirty = true; } @@ -2598,32 +2782,35 @@ void Bars3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -#if !defined(QT_OPENGL_ES_2) void Bars3DRenderer::initDepthShader() { - if (m_depthShader) - delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + if (!m_isOpenGLES) { + if (m_depthShader) + delete m_depthShader; + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Bars3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + if (m_primarySubViewport.size().isEmpty()) + return; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), - m_depthFrameBuffer, - m_shadowQualityMultiplier); - if (!m_depthTexture) - lowerShadowQuality(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = + m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#endif void Bars3DRenderer::initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) @@ -2634,14 +2821,6 @@ void Bars3DRenderer::initBackgroundShaders(const QString &vertexShader, m_backgroundShader->initialize(); } -void Bars3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) -{ - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); -} - QVector3D Bars3DRenderer::convertPositionToTranslation(const QVector3D &position, bool isAbsolute) { float xTrans = 0.0f; @@ -2649,15 +2828,15 @@ QVector3D Bars3DRenderer::convertPositionToTranslation(const QVector3D &position float zTrans = 0.0f; if (!isAbsolute) { // Convert row and column to translation on graph - xTrans = (((position.x() + 0.5f) * m_cachedBarSpacing.width()) - m_rowWidth) - / m_scaleFactor; - zTrans = (m_columnDepth - ((position.z() + 0.5f) * m_cachedBarSpacing.height())) - / m_scaleFactor; + xTrans = (((position.x() - m_axisCacheX.min() + 0.5f) * m_cachedBarSpacing.width()) + - m_rowWidth) / m_scaleFactor; + zTrans = (m_columnDepth - ((position.z() - m_axisCacheZ.min() + 0.5f) + * m_cachedBarSpacing.height())) / m_scaleFactor; yTrans = m_axisCacheY.positionAt(position.y()); } else { - xTrans = position.x() * m_scaleX; + xTrans = position.x() * m_xScaleFactor; yTrans = position.y() + m_backgroundAdjustment; - zTrans = position.z() * m_scaleZ; + zTrans = position.z() * -m_zScaleFactor; } return QVector3D(xTrans, yTrans, zTrans); } @@ -2667,4 +2846,18 @@ void Bars3DRenderer::updateAspectRatio(float ratio) Q_UNUSED(ratio) } +void Bars3DRenderer::updateFloorLevel(float level) +{ + foreach (SeriesRenderCache *cache, m_renderCacheList) + cache->setDataDirty(true); + m_floorLevel = level; + calculateHeightAdjustment(); +} + +void Bars3DRenderer::updateMargin(float margin) +{ + Abstract3DRenderer::updateMargin(margin); + calculateSceneScalingFactors(); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/bars3drenderer_p.h b/src/datavisualization/engine/bars3drenderer_p.h index 3a0ab3b8..094b8fd9 100644 --- a/src/datavisualization/engine/bars3drenderer_p.h +++ b/src/datavisualization/engine/bars3drenderer_p.h @@ -66,9 +66,7 @@ private: ShaderHelper *m_depthShader; ShaderHelper *m_selectionShader; ShaderHelper *m_backgroundShader; - ShaderHelper *m_labelShader; GLuint m_bgrTexture; - GLuint m_depthTexture; GLuint m_selectionTexture; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; @@ -86,7 +84,6 @@ private: GLfloat m_scaleFactor; GLfloat m_maxSceneSize; QPoint m_visualSelectedBarPos; - bool m_resetCameraBaseOrientation; QPoint m_selectedBarPos; BarSeriesRenderCache *m_selectedSeriesCache; BarRenderItem m_dummyBarRenderItem; @@ -100,6 +97,10 @@ private: bool m_haveUniformColorSeries; bool m_haveGradientSeries; float m_zeroPosition; + float m_xScaleFactor; + float m_zScaleFactor; + float m_floorLevel; + float m_actualFloorLevel; public: explicit Bars3DRenderer(Bars3DController *controller); @@ -116,9 +117,13 @@ public: QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); void updateAspectRatio(float ratio); + void updateFloorLevel(float level); + void updateMargin(float margin); protected: virtual void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); public slots: void updateMultiSeriesScaling(bool uniform); @@ -146,18 +151,25 @@ private: void drawSlicedScene(); void drawScene(GLuint defaultFboHandle); void drawLabels(bool drawSelection, const Q3DCamera *activeCamera, - const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - GLfloat rowScaleFactor, GLfloat columnScaleFactor); + const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix); + + bool drawBars(BarRenderItem **selectedBar, const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + GLint startRow, GLint stopRow, GLint stepRow, + GLint startBar, GLint stopBar, GLint stepBar, GLfloat reflection = 1.0f); + void drawBackground(GLfloat backgroundRotation, const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + bool reflectingDraw = false, bool drawingSelectionBuffer = false); + void drawGridLines(const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix); void loadBackgroundMesh(); void initSelectionShader(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) void initDepthShader(); void updateDepthBuffer(); -#endif void calculateSceneScalingFactors(); void calculateHeightAdjustment(); Abstract3DController::SelectionType isSelected(int row, int bar, diff --git a/src/datavisualization/engine/drawer.cpp b/src/datavisualization/engine/drawer.cpp index b8726840..d4352702 100644 --- a/src/datavisualization/engine/drawer.cpp +++ b/src/datavisualization/engine/drawer.cpp @@ -69,10 +69,9 @@ Drawer::~Drawer() void Drawer::initializeOpenGL() { - if (!m_textureHelper) { - initializeOpenGLFunctions(); + initializeOpenGLFunctions(); + if (!m_textureHelper) m_textureHelper = new TextureHelper(); - } } void Drawer::setTheme(Q3DTheme *theme) @@ -93,8 +92,11 @@ QFont Drawer::font() const } void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLuint textureId, - GLuint depthTextureId) + GLuint depthTextureId, GLuint textureId3D) { +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(textureId3D) +#endif if (textureId) { // Activate texture glActiveTexture(GL_TEXTURE0); @@ -108,6 +110,14 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glBindTexture(GL_TEXTURE_2D, depthTextureId); shader->setUniformValue(shader->shadow(), 1); } +#if !defined(QT_OPENGL_ES_2) + if (textureId3D) { + // Activate texture + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_3D, textureId3D); + shader->setUniformValue(shader->texture(), 2); + } +#endif // 1st attribute buffer : vertices glEnableVertexAttribArray(shader->posAtt()); @@ -115,14 +125,18 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glVertexAttribPointer(shader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); // 2nd attribute buffer : normals - glEnableVertexAttribArray(shader->normalAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->normalBuf()); - glVertexAttribPointer(shader->normalAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + if (shader->normalAtt() >= 0) { + glEnableVertexAttribArray(shader->normalAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->normalBuf()); + glVertexAttribPointer(shader->normalAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + } // 3rd attribute buffer : UVs - glEnableVertexAttribArray(shader->uvAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); - glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + if (shader->uvAtt() >= 0) { + glEnableVertexAttribArray(shader->uvAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); + glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + } // Index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); @@ -134,11 +148,19 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDisableVertexAttribArray(shader->uvAtt()); - glDisableVertexAttribArray(shader->normalAtt()); + if (shader->uvAtt() >= 0) + glDisableVertexAttribArray(shader->uvAtt()); + if (shader->normalAtt() >= 0) + glDisableVertexAttribArray(shader->normalAtt()); glDisableVertexAttribArray(shader->posAtt()); // Release textures +#if !defined(QT_OPENGL_ES_2) + if (textureId3D) { + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_3D, 0); + } +#endif if (depthTextureId) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); @@ -206,13 +228,27 @@ void Drawer::drawPoint(ShaderHelper *shader) glDisableVertexAttribArray(shader->posAtt()); } -void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object) +void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object, GLuint textureId) { + if (textureId) { + // Activate texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + shader->setUniformValue(shader->texture(), 0); + } + // 1st attribute buffer : vertices glEnableVertexAttribArray(shader->posAtt()); glBindBuffer(GL_ARRAY_BUFFER, object->pointBuf()); glVertexAttribPointer(shader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + // 2nd attribute buffer : UVs + if (textureId) { + glEnableVertexAttribArray(shader->uvAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); + glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + } + // Draw the points glDrawArrays(GL_POINTS, 0, object->indexCount()); @@ -220,6 +256,12 @@ void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object) glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableVertexAttribArray(shader->posAtt()); + + if (textureId) { + glDisableVertexAttribArray(shader->uvAtt()); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + } } void Drawer::drawLine(ShaderHelper *shader) diff --git a/src/datavisualization/engine/drawer_p.h b/src/datavisualization/engine/drawer_p.h index ffcea315..0c96724c 100644 --- a/src/datavisualization/engine/drawer_p.h +++ b/src/datavisualization/engine/drawer_p.h @@ -75,11 +75,11 @@ public: inline GLfloat scaledFontSize() const { return m_scaledFontSize; } void drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLuint textureId = 0, - GLuint depthTextureId = 0); + GLuint depthTextureId = 0, GLuint textureId3D = 0); void drawSelectionObject(ShaderHelper *shader, AbstractObjectHelper *object); void drawSurfaceGrid(ShaderHelper *shader, SurfaceObject *object); void drawPoint(ShaderHelper *shader); - void drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object); + void drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object, GLuint textureId); void drawLine(ShaderHelper *shader); void drawLabel(const AbstractRenderItem &item, const LabelItem &labelItem, const QMatrix4x4 &viewmatrix, const QMatrix4x4 &projectionmatrix, diff --git a/src/datavisualization/engine/engine.pri b/src/datavisualization/engine/engine.pri index 96fa7fa9..60703e96 100644 --- a/src/datavisualization/engine/engine.pri +++ b/src/datavisualization/engine/engine.pri @@ -57,3 +57,5 @@ SOURCES += $$PWD/qabstract3dgraph.cpp \ RESOURCES += engine/engine.qrc OTHER_FILES += $$PWD/meshes/* $$PWD/shaders/* + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/engine/engine.qrc b/src/datavisualization/engine/engine.qrc index 673b6ee0..0ef169c9 100644 --- a/src/datavisualization/engine/engine.qrc +++ b/src/datavisualization/engine/engine.qrc @@ -57,5 +57,18 @@ <file alias="fragmentTexture">shaders/texture.frag</file> <file alias="fragmentTextureES2">shaders/texture_ES2.frag</file> <file alias="vertexTexture">shaders/texture.vert</file> + <file alias="fragmentTexturedSurfaceShadowFlat">shaders/surfaceTexturedShadowFlat.frag</file> + <file alias="fragmentSurfaceTexturedFlat">shaders/surfaceTexturedFlat.frag</file> + <file alias="fragmentTexture3D">shaders/texture3d.frag</file> + <file alias="vertexTexture3D">shaders/texture3d.vert</file> + <file alias="fragmentTexture3DSlice">shaders/texture3dslice.frag</file> + <file alias="vertexShadowNoMatrices">shaders/shadowNoMatrices.vert</file> + <file alias="vertexNoMatrices">shaders/defaultNoMatrices.vert</file> + <file alias="fragmentTexture3DLowDef">shaders/texture3dlowdef.frag</file> + <file alias="vertexPointES2_UV">shaders/point_ES2_UV.vert</file> + <file alias="fragment3DSliceFrames">shaders/3dsliceframes.frag</file> + <file alias="vertexPosition">shaders/position.vert</file> + <file alias="fragmentPositionMap">shaders/positionmap.frag</file> + <file alias="fragmentTexturedSurfaceShadow">shaders/surfaceTexturedShadow.frag</file> </qresource> </RCC> diff --git a/src/datavisualization/engine/q3dbars.cpp b/src/datavisualization/engine/q3dbars.cpp index bb7aca89..d42c11b7 100644 --- a/src/datavisualization/engine/q3dbars.cpp +++ b/src/datavisualization/engine/q3dbars.cpp @@ -331,6 +331,26 @@ QBar3DSeries *Q3DBars::selectedSeries() const } /*! + * \property Q3DBars::floorLevel + * + * The desired floor level for the bar graph in Y-axis data coordinates. + * The actual floor level cannot go below Y-axis minimum or above Y-axis maximum. + * Defaults to zero. + */ +void Q3DBars::setFloorLevel(float level) +{ + if (level != floorLevel()) { + dptr()->m_shared->setFloorLevel(level); + emit floorLevelChanged(level); + } +} + +float Q3DBars::floorLevel() const +{ + return dptrc()->m_shared->floorLevel(); +} + +/*! * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use, * addAxis is simply used to give the ownership of the \a axis to the graph. * The \a axis must not be null or added to another graph. diff --git a/src/datavisualization/engine/q3dbars.h b/src/datavisualization/engine/q3dbars.h index 7f9c981f..df1c846e 100644 --- a/src/datavisualization/engine/q3dbars.h +++ b/src/datavisualization/engine/q3dbars.h @@ -40,6 +40,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DBars : public QAbstract3DGraph Q_PROPERTY(QValue3DAxis *valueAxis READ valueAxis WRITE setValueAxis NOTIFY valueAxisChanged) Q_PROPERTY(QBar3DSeries *primarySeries READ primarySeries WRITE setPrimarySeries NOTIFY primarySeriesChanged) Q_PROPERTY(QBar3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) + Q_PROPERTY(float floorLevel READ floorLevel WRITE setFloorLevel NOTIFY floorLevelChanged) public: explicit Q3DBars(const QSurfaceFormat *format = 0, QWindow *parent = 0); @@ -75,6 +76,8 @@ public: QList<QAbstract3DAxis *> axes() const; QBar3DSeries *selectedSeries() const; + void setFloorLevel(float level); + float floorLevel() const; signals: void multiSeriesUniformChanged(bool uniform); @@ -86,6 +89,7 @@ signals: void valueAxisChanged(QValue3DAxis *axis); void primarySeriesChanged(QBar3DSeries *series); void selectedSeriesChanged(QBar3DSeries *series); + void floorLevelChanged(float level); private: Q3DBarsPrivate *dptr(); diff --git a/src/datavisualization/engine/q3dcamera.cpp b/src/datavisualization/engine/q3dcamera.cpp index 06fd570c..897c3779 100644 --- a/src/datavisualization/engine/q3dcamera.cpp +++ b/src/datavisualization/engine/q3dcamera.cpp @@ -110,8 +110,36 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty float Camera3D::zoomLevel * - * This property contains the the camera zoom level in percentage. 100.0 means there is no zoom - * in or out set in the camera. + * This property contains the the camera zoom level in percentage. The default value of \c{100.0} + * means there is no zoom in or out set in the camera. + * The value is limited within the bounds defined by minZoomLevel and maxZoomLevel properties. + * + * \sa minZoomLevel, maxZoomLevel + */ + +/*! + * \qmlproperty float Camera3D::minZoomLevel + * + * This property contains the the minimum allowed camera zoom level. + * If the new minimum level is more than the existing maximum level, the maximum level is + * adjusted to the new minimum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * The minZoomLevel cannot be set below \c{1.0}. + * Defaults to \c{10.0}. + * + * \sa zoomLevel, maxZoomLevel + */ + +/*! + * \qmlproperty float Camera3D::maxZoomLevel + * + * This property contains the the maximum allowed camera zoom level. + * If the new maximum level is less than the existing minimum level, the minimum level is + * adjusted to the new maximum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * Defaults to \c{500.0f}. + * + * \sa zoomLevel, minZoomLevel */ /*! @@ -137,6 +165,19 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty vector3d Camera3D::target + * \since QtDataVisualization 1.2 + * + * Holds the camera \a target as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}. + * + * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate + * the edges of the corresponding axis range. Any values outside this range are clamped to the edge. + * + * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on + * the horizontal background. + */ + +/*! * Constructs a new 3D camera with position set to origin, up direction facing towards the Y-axis * and looking at origin by default. An optional \a parent parameter can be given and is then passed * to QObject constructor. @@ -160,22 +201,11 @@ Q3DCamera::~Q3DCamera() */ void Q3DCamera::copyValuesFrom(const Q3DObject &source) { - Q3DObject::copyValuesFrom(source); + // Note: Do not copy values from parent, as we are handling the position internally const Q3DCamera &sourceCamera = static_cast<const Q3DCamera &>(source); - d_ptr->m_target.setX(sourceCamera.d_ptr->m_target.x()); - d_ptr->m_target.setY(sourceCamera.d_ptr->m_target.y()); - d_ptr->m_target.setZ(sourceCamera.d_ptr->m_target.z()); - - d_ptr->m_up.setX(sourceCamera.d_ptr->m_up.x()); - d_ptr->m_up.setY(sourceCamera.d_ptr->m_up.y()); - d_ptr->m_up.setZ(sourceCamera.d_ptr->m_up.z()); - - float *values = new float[16]; - sourceCamera.d_ptr->m_viewMatrix.copyDataTo(values); - d_ptr->m_viewMatrix = QMatrix4x4(values); - delete[] values; + d_ptr->m_requestedTarget = sourceCamera.d_ptr->m_requestedTarget; d_ptr->m_xRotation = sourceCamera.d_ptr->m_xRotation; d_ptr->m_yRotation = sourceCamera.d_ptr->m_yRotation; @@ -189,6 +219,8 @@ void Q3DCamera::copyValuesFrom(const Q3DObject &source) d_ptr->m_wrapYRotation = sourceCamera.d_ptr->m_wrapYRotation; d_ptr->m_zoomLevel = sourceCamera.d_ptr->m_zoomLevel; + d_ptr->m_minZoomLevel = sourceCamera.d_ptr->m_minZoomLevel; + d_ptr->m_maxZoomLevel = sourceCamera.d_ptr->m_maxZoomLevel; d_ptr->m_activePreset = sourceCamera.d_ptr->m_activePreset; } @@ -389,6 +421,9 @@ void Q3DCamera::setCameraPreset(CameraPreset preset) break; } + // All presets target the center of the graph + setTarget(zeroVector); + if (d_ptr->m_activePreset != preset) { d_ptr->m_activePreset = preset; setDirty(true); @@ -399,8 +434,11 @@ void Q3DCamera::setCameraPreset(CameraPreset preset) /*! * \property Q3DCamera::zoomLevel * - * This property contains the the camera zoom level in percentage. \c 100.0f means there is no zoom - * in or out set in the camera. + * This property contains the the camera zoom level in percentage. The default value of \c{100.0f} + * means there is no zoom in or out set in the camera. + * The value is limited within the bounds defined by minZoomLevel and maxZoomLevel properties. + * + * \sa minZoomLevel, maxZoomLevel */ float Q3DCamera::zoomLevel() const { @@ -409,10 +447,73 @@ float Q3DCamera::zoomLevel() const void Q3DCamera::setZoomLevel(float zoomLevel) { - if (d_ptr->m_zoomLevel != zoomLevel) { - d_ptr->m_zoomLevel = zoomLevel; + float newZoomLevel = qBound(d_ptr->m_minZoomLevel, zoomLevel, d_ptr->m_maxZoomLevel); + + if (d_ptr->m_zoomLevel != newZoomLevel) { + d_ptr->m_zoomLevel = newZoomLevel; + setDirty(true); + emit zoomLevelChanged(newZoomLevel); + } +} + +/*! + * \property Q3DCamera::minZoomLevel + * + * This property contains the the minimum allowed camera zoom level. + * If the new minimum level is more than the existing maximum level, the maximum level is + * adjusted to the new minimum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * The minZoomLevel cannot be set below \c{1.0f}. + * Defaults to \c{10.0f}. + * + * \sa zoomLevel, maxZoomLevel + */ +float Q3DCamera::minZoomLevel() const +{ + return d_ptr->m_minZoomLevel; +} + +void Q3DCamera::setMinZoomLevel(float zoomLevel) +{ + // Don't allow minimum to be below one, as that can cause zoom to break. + float newMinLevel = qMax(zoomLevel, 1.0f); + if (d_ptr->m_minZoomLevel != newMinLevel) { + d_ptr->m_minZoomLevel = newMinLevel; + if (d_ptr->m_maxZoomLevel < newMinLevel) + setMaxZoomLevel(newMinLevel); + setZoomLevel(d_ptr->m_zoomLevel); setDirty(true); - emit zoomLevelChanged(zoomLevel); + emit minZoomLevelChanged(newMinLevel); + } +} + +/*! + * \property Q3DCamera::maxZoomLevel + * + * This property contains the the maximum allowed camera zoom level. + * If the new maximum level is less than the existing minimum level, the minimum level is + * adjusted to the new maximum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * Defaults to \c{500.0f}. + * + * \sa zoomLevel, minZoomLevel + */ +float Q3DCamera::maxZoomLevel() const +{ + return d_ptr->m_maxZoomLevel; +} + +void Q3DCamera::setMaxZoomLevel(float zoomLevel) +{ + // Don't allow maximum to be below one, as that can cause zoom to break. + float newMaxLevel = qMax(zoomLevel, 1.0f); + if (d_ptr->m_maxZoomLevel != newMaxLevel) { + d_ptr->m_maxZoomLevel = newMaxLevel; + if (d_ptr->m_minZoomLevel > newMaxLevel) + setMinZoomLevel(newMaxLevel); + setZoomLevel(d_ptr->m_zoomLevel); + setDirty(true); + emit maxZoomLevelChanged(newMaxLevel); } } @@ -459,16 +560,61 @@ void Q3DCamera::setWrapYRotation(bool isEnabled) /*! * Utility function that sets the camera rotations and distance.\a horizontal and \a vertical * define the camera rotations to be used. - * Optional \a zoom parameter can be given to set the zoom percentage of the camera in range of - * \c{10.0f - 500.0f}. + * Optional \a zoom parameter can be given to set the zoom percentage of the camera within + * the bounds defined by minZoomLevel and maxZoomLevel properties. */ void Q3DCamera::setCameraPosition(float horizontal, float vertical, float zoom) { - setZoomLevel(qBound(10.0f, zoom, 500.0f)); + setZoomLevel(zoom); setXRotation(horizontal); setYRotation(vertical); } +/*! + * \property Q3DCamera::target + * \since QtDataVisualization 1.2 + * + * Holds the camera \a target as a QVector3D. Defaults to \c {QVector3D(0.0, 0.0, 0.0)}. + * + * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate + * the edges of the corresponding axis range. Any values outside this range are clamped to the edge. + * + * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on + * the horizontal background. + */ +QVector3D Q3DCamera::target() const +{ + return d_ptr->m_requestedTarget; +} + +void Q3DCamera::setTarget(const QVector3D &target) +{ + QVector3D newTarget = target; + + if (newTarget.x() < -1.0f) + newTarget.setX(-1.0f); + else if (newTarget.x() > 1.0f) + newTarget.setX(1.0f); + + if (newTarget.y() < -1.0f) + newTarget.setY(-1.0f); + else if (newTarget.y() > 1.0f) + newTarget.setY(1.0f); + + if (newTarget.z() < -1.0f) + newTarget.setZ(-1.0f); + else if (newTarget.z() > 1.0f) + newTarget.setZ(1.0f); + + if (d_ptr->m_requestedTarget != newTarget) { + if (d_ptr->m_activePreset != CameraPresetNone) + d_ptr->m_activePreset = CameraPresetNone; + d_ptr->m_requestedTarget = newTarget; + setDirty(true); + emit targetChanged(newTarget); + } +} + Q3DCameraPrivate::Q3DCameraPrivate(Q3DCamera *q) : q_ptr(q), m_isViewMatrixUpdateActive(true), @@ -479,6 +625,8 @@ Q3DCameraPrivate::Q3DCameraPrivate(Q3DCamera *q) : m_maxXRotation(180.0f), m_maxYRotation(90.0f), m_zoomLevel(100.0f), + m_minZoomLevel(10.0f), + m_maxZoomLevel(500.0f), m_wrapXRotation(true), m_wrapYRotation(false), m_activePreset(Q3DCamera::CameraPresetNone) @@ -645,9 +793,9 @@ void Q3DCameraPrivate::updateViewMatrix(float zoomAdjustment) QMatrix4x4 viewMatrix; // Apply to view matrix - viewMatrix.lookAt(q_ptr->position(), m_target, m_up); + viewMatrix.lookAt(q_ptr->position(), m_actualTarget, m_up); // Compensate for translation (if d_ptr->m_target is off origin) - viewMatrix.translate(m_target.x(), m_target.y(), m_target.z()); + viewMatrix.translate(m_actualTarget.x(), m_actualTarget.y(), m_actualTarget.z()); // Apply rotations // Handle x and z rotation when y -angle is other than 0 viewMatrix.rotate(m_xRotation, 0, qCos(qDegreesToRadians(m_yRotation)), @@ -657,7 +805,7 @@ void Q3DCameraPrivate::updateViewMatrix(float zoomAdjustment) // handle zoom by scaling viewMatrix.scale(zoom / 100.0f); // Compensate for translation (if d_ptr->m_target is off origin) - viewMatrix.translate(-m_target.x(), -m_target.y(), -m_target.z()); + viewMatrix.translate(-m_actualTarget.x(), -m_actualTarget.y(), -m_actualTarget.z()); setViewMatrix(viewMatrix); } @@ -713,9 +861,9 @@ void Q3DCameraPrivate::setBaseOrientation(const QVector3D &basePosition, const QVector3D &target, const QVector3D &baseUp) { - if (q_ptr->position() != basePosition || m_target != target || m_up != baseUp) { + if (q_ptr->position() != basePosition || m_actualTarget != target || m_up != baseUp) { q_ptr->setPosition(basePosition); - m_target = target; + m_actualTarget = target; m_up = baseUp; q_ptr->setDirty(true); } @@ -737,20 +885,33 @@ QVector3D Q3DCameraPrivate::calculatePositionRelativeToCamera(const QVector3D &r float distanceModifier) const { // Move the position with camera - GLfloat radiusFactor = cameraDistance * (1.5f + distanceModifier); - GLfloat xAngle; - GLfloat yAngle; + const float radiusFactor = cameraDistance * (1.5f + distanceModifier); + float xAngle; + float yAngle; + if (!fixedRotation) { xAngle = qDegreesToRadians(m_xRotation); - yAngle = qDegreesToRadians(m_yRotation); + float yRotation = m_yRotation; + // Light must not be paraller to eye vector, so fudge the y rotation a bit. + // Note: This needs redoing if we ever allow arbitrary light positioning. + const float yMargin = 0.1f; // Smaller margins cause weird shadow artifacts on tops of bars + const float absYRotation = qAbs(yRotation); + if (absYRotation < 90.0f + yMargin && absYRotation > 90.0f - yMargin) { + if (yRotation < 0.0f) + yRotation = -90.0f + yMargin; + else + yRotation = 90.0f - yMargin; + } + yAngle = qDegreesToRadians(yRotation); } else { xAngle = qDegreesToRadians(fixedRotation); yAngle = 0; } - GLfloat radius = (radiusFactor + relativePosition.y()); // set radius to match the highest height of the position - GLfloat zPos = radius * qCos(xAngle) * qCos(yAngle); - GLfloat xPos = radius * qSin(xAngle) * qCos(yAngle); - GLfloat yPos = (radiusFactor + relativePosition.y()) * qSin(yAngle); + // Set radius to match the highest height of the position + const float radius = (radiusFactor + relativePosition.y()); + const float zPos = radius * qCos(xAngle) * qCos(yAngle); + const float xPos = radius * qSin(xAngle) * qCos(yAngle); + const float yPos = radius * qSin(yAngle); // Keep in the set position in relation to camera return QVector3D(-xPos + relativePosition.x(), diff --git a/src/datavisualization/engine/q3dcamera.h b/src/datavisualization/engine/q3dcamera.h index e9ad6dd2..5b539ccf 100644 --- a/src/datavisualization/engine/q3dcamera.h +++ b/src/datavisualization/engine/q3dcamera.h @@ -35,6 +35,9 @@ class QT_DATAVISUALIZATION_EXPORT Q3DCamera : public Q3DObject Q_PROPERTY(CameraPreset cameraPreset READ cameraPreset WRITE setCameraPreset NOTIFY cameraPresetChanged) Q_PROPERTY(bool wrapXRotation READ wrapXRotation WRITE setWrapXRotation NOTIFY wrapXRotationChanged) Q_PROPERTY(bool wrapYRotation READ wrapYRotation WRITE setWrapYRotation NOTIFY wrapYRotationChanged) + Q_PROPERTY(QVector3D target READ target WRITE setTarget NOTIFY targetChanged REVISION 1) + Q_PROPERTY(float minZoomLevel READ minZoomLevel WRITE setMinZoomLevel NOTIFY minZoomLevelChanged REVISION 1) + Q_PROPERTY(float maxZoomLevel READ maxZoomLevel WRITE setMaxZoomLevel NOTIFY maxZoomLevelChanged REVISION 1) public: enum CameraPreset { @@ -86,9 +89,16 @@ public: float zoomLevel() const; void setZoomLevel(float zoomLevel); + float minZoomLevel() const; + void setMinZoomLevel(float zoomLevel); + float maxZoomLevel() const; + void setMaxZoomLevel(float zoomLevel); void setCameraPosition(float horizontal, float vertical, float zoom = 100.0f); + QVector3D target() const; + void setTarget(const QVector3D &target); + signals: void xRotationChanged(float rotation); void yRotationChanged(float rotation); @@ -96,6 +106,9 @@ signals: void cameraPresetChanged(Q3DCamera::CameraPreset preset); void wrapXRotationChanged(bool isEnabled); void wrapYRotationChanged(bool isEnabled); + Q_REVISION(1) void targetChanged(const QVector3D &target); + Q_REVISION(1) void minZoomLevelChanged(float zoomLevel); + Q_REVISION(1) void maxZoomLevelChanged(float zoomLevel); private: QScopedPointer<Q3DCameraPrivate> d_ptr; diff --git a/src/datavisualization/engine/q3dcamera_p.h b/src/datavisualization/engine/q3dcamera_p.h index 01e7a508..b7826a5b 100644 --- a/src/datavisualization/engine/q3dcamera_p.h +++ b/src/datavisualization/engine/q3dcamera_p.h @@ -84,22 +84,25 @@ signals: public: Q3DCamera *q_ptr; - QVector3D m_target; + QVector3D m_actualTarget; QVector3D m_up; QMatrix4x4 m_viewMatrix; bool m_isViewMatrixUpdateActive; - GLfloat m_xRotation; - GLfloat m_yRotation; - GLfloat m_minXRotation; - GLfloat m_minYRotation; - GLfloat m_maxXRotation; - GLfloat m_maxYRotation; - GLfloat m_zoomLevel; + float m_xRotation; + float m_yRotation; + float m_minXRotation; + float m_minYRotation; + float m_maxXRotation; + float m_maxYRotation; + float m_zoomLevel; + float m_minZoomLevel; + float m_maxZoomLevel; bool m_wrapXRotation; bool m_wrapYRotation; Q3DCamera::CameraPreset m_activePreset; + QVector3D m_requestedTarget; friend class Bars3DRenderer; friend class Surface3DRenderer; diff --git a/src/datavisualization/engine/q3dlight.cpp b/src/datavisualization/engine/q3dlight.cpp index 03f094cb..92b7bd07 100644 --- a/src/datavisualization/engine/q3dlight.cpp +++ b/src/datavisualization/engine/q3dlight.cpp @@ -68,13 +68,8 @@ Q3DLightPrivate::~Q3DLightPrivate() void Q3DLightPrivate::sync(Q3DLight &other) { - // Copies changed values from this light to the other light. If the other light had same changes, - // those changes are discarded. - if (q_ptr->isDirty()) { - other.copyValuesFrom(*q_ptr); - q_ptr->setDirty(false); - other.setDirty(false); - } + Q_UNUSED(other); + // Do nothing. Only value light has to sync is the position, which we handle internally. } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/q3dobject.cpp b/src/datavisualization/engine/q3dobject.cpp index 56edb098..6b442ef8 100644 --- a/src/datavisualization/engine/q3dobject.cpp +++ b/src/datavisualization/engine/q3dobject.cpp @@ -53,9 +53,7 @@ Q3DObject::~Q3DObject() */ void Q3DObject::copyValuesFrom(const Q3DObject &source) { - d_ptr->m_position.setX(source.d_ptr->m_position.x()); - d_ptr->m_position.setY(source.d_ptr->m_position.y()); - d_ptr->m_position.setZ(source.d_ptr->m_position.z()); + d_ptr->m_position = source.d_ptr->m_position; setDirty(true); } @@ -74,6 +72,9 @@ Q3DScene *Q3DObject::parentScene() * \property Q3DObject::position * * This property contains the 3D position of the object. + * + * \note Currently setting this property has no effect, as the positions of Q3DObjects in the + * scene are handled internally. */ QVector3D Q3DObject::position() const { @@ -97,7 +98,7 @@ void Q3DObject::setDirty(bool dirty) { d_ptr->m_isDirty = dirty; if (parentScene()) - parentScene()->d_ptr->m_sceneDirty = true; + parentScene()->d_ptr->markDirty(); } /*! diff --git a/src/datavisualization/engine/q3dscene.cpp b/src/datavisualization/engine/q3dscene.cpp index 9464bc8d..e4015c2b 100644 --- a/src/datavisualization/engine/q3dscene.cpp +++ b/src/datavisualization/engine/q3dscene.cpp @@ -96,9 +96,33 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty point Scene3D::graphPositionQuery + * + * This property contains the coordinates for the user input that should be processed + * by the scene as a graph position query. If this is set to value other than + * invalidSelectionPoint, the graph tries to match a graph position to the given \a point + * within the primary viewport. + * After the rendering pass this property is returned to its default state of + * invalidSelectionPoint. The queried graph position can be read from + * AbstractGraph3D::queriedGraphPosition property after the next render pass. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs allow graph position queries only at the graph floor level. + * + * \sa AbstractGraph3D::queriedGraphPosition + */ + +/*! * \qmlproperty bool Scene3D::slicingActive * - * This property contains whether 2D slicing view is currently active or not. + * This property contains whether 2D slicing view is currently active or not. If setting it, you + * must make sure AbstractGraph3D::selectionMode has either + * \l{QAbstract3DGraph::SelectionRow}{AbstractGraph3D.SelectionRow} or + * \l{QAbstract3DGraph::SelectionColumn}{AbstractGraph3D.SelectionColumn} flag set, and there is a + * valid selection. * \note Not all visualizations support the 2D slicing view. */ @@ -135,7 +159,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * A constant property providing an invalid point for selection. */ - /*! * Constructs a basic scene with one light and one camera in it. An * optional \a parent parameter can be given and is then passed to QObject constructor. @@ -195,33 +218,35 @@ void Q3DScene::setPrimarySubViewport(const QRect &primarySubViewport) /*! * Returns whether the given \a point resides inside the primary subview or not. * \return \c true if the point is inside the primary subview. + * \note If subviews are superimposed, and the given \a point resides inside both, result is + * \c true only when the primary subview is on top. */ bool Q3DScene::isPointInPrimarySubView(const QPoint &point) { int x = point.x(); int y = point.y(); - int areaMinX = d_ptr->m_primarySubViewport.x(); - int areaMaxX = d_ptr->m_primarySubViewport.x() + d_ptr->m_primarySubViewport.width(); - int areaMinY = d_ptr->m_primarySubViewport.y(); - int areaMaxY = d_ptr->m_primarySubViewport.y() + d_ptr->m_primarySubViewport.height(); - - return ( x > areaMinX && x < areaMaxX && y > areaMinY && y < areaMaxY ); + bool isInSecondary = d_ptr->isInArea(d_ptr->m_secondarySubViewport, x, y); + if (!isInSecondary || (isInSecondary && !d_ptr->m_isSecondarySubviewOnTop)) + return d_ptr->isInArea(d_ptr->m_primarySubViewport, x, y); + else + return false; } /*! * Returns whether the given \a point resides inside the secondary subview or not. * \return \c true if the point is inside the secondary subview. + * \note If subviews are superimposed, and the given \a point resides inside both, result is + * \c true only when the secondary subview is on top. */ bool Q3DScene::isPointInSecondarySubView(const QPoint &point) { int x = point.x(); int y = point.y(); - int areaMinX = d_ptr->m_secondarySubViewport.x(); - int areaMaxX = d_ptr->m_secondarySubViewport.x() + d_ptr->m_secondarySubViewport.width(); - int areaMinY = d_ptr->m_secondarySubViewport.y(); - int areaMaxY = d_ptr->m_secondarySubViewport.y() + d_ptr->m_secondarySubViewport.height(); - - return ( x > areaMinX && x < areaMaxX && y > areaMinY && y < areaMaxY ); + bool isInPrimary = d_ptr->isInArea(d_ptr->m_primarySubViewport, x, y); + if (!isInPrimary || (isInPrimary && d_ptr->m_isSecondarySubviewOnTop)) + return d_ptr->isInArea(d_ptr->m_secondarySubViewport, x, y); + else + return false; } /*! @@ -254,7 +279,7 @@ void Q3DScene::setSecondarySubViewport(const QRect &secondarySubViewport) * \property Q3DScene::selectionQueryPosition * * This property contains the coordinates for the user input that should be processed - * by the scene as selection. If this is set to value other than invalidSelectionPoint() the + * by the scene as a selection. If this is set to value other than invalidSelectionPoint(), the * graph tries to select a data item, axis label, or a custom item at the given \a point within * the primary viewport. * After the rendering pass the property is returned to its default state of @@ -289,9 +314,47 @@ QPoint Q3DScene::invalidSelectionPoint() } /*! + * \property Q3DScene::graphPositionQuery + * + * This property contains the coordinates for the user input that should be processed + * by the scene as a graph position query. If this is set to value other than + * invalidSelectionPoint(), the graph tries to match a graph position to the given \a point + * within the primary viewport. + * After the rendering pass this property is returned to its default state of + * invalidSelectionPoint(). The queried graph position can be read from + * QAbstract3DGraph::queriedGraphPosition property after the next render pass. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs allow graph position queries only at the graph floor level. + * + * \sa QAbstract3DGraph::queriedGraphPosition + */ +void Q3DScene::setGraphPositionQuery(const QPoint &point) +{ + if (point != d_ptr->m_graphPositionQueryPosition) { + d_ptr->m_graphPositionQueryPosition = point; + d_ptr->m_changeTracker.graphPositionQueryPositionChanged = true; + d_ptr->m_sceneDirty = true; + + emit graphPositionQueryChanged(point); + emit d_ptr->needRender(); + } +} + +QPoint Q3DScene::graphPositionQuery() const +{ + return d_ptr->m_graphPositionQueryPosition; +} + +/*! * \property Q3DScene::slicingActive * - * This property contains whether 2D slicing view is currently active or not. + * This property contains whether 2D slicing view is currently active or not. If setting it, you + * must make sure QAbstract3DGraph::selectionMode has either QAbstract3DGraph::SelectionRow or + * QAbstract3DGraph::SelectionColumn flag set, and there is a valid selection. * \note Not all visualizations support the 2D slicing view. */ bool Q3DScene::isSlicingActive() const @@ -306,6 +369,10 @@ void Q3DScene::setSlicingActive(bool isSlicing) d_ptr->m_changeTracker.slicingActivatedChanged = true; d_ptr->m_sceneDirty = true; + // Set secondary subview behind primary to achieve default functionality (= clicking on + // primary disables slice) + setSecondarySubviewOnTop(!isSlicing); + d_ptr->calculateSubViewports(); emit slicingActiveChanged(isSlicing); emit d_ptr->needRender(); @@ -447,7 +514,9 @@ Q3DScenePrivate::Q3DScenePrivate(Q3DScene *q) : m_light(), m_isUnderSideCameraEnabled(false), m_isSlicingActive(false), - m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()) + m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()), + m_graphPositionQueryPosition(Q3DScene::invalidSelectionPoint()), + m_sceneDirty(true) { } @@ -491,6 +560,11 @@ void Q3DScenePrivate::sync(Q3DScenePrivate &other) m_changeTracker.selectionQueryPositionChanged = false; other.m_changeTracker.selectionQueryPositionChanged = false; } + if (m_changeTracker.graphPositionQueryPositionChanged) { + other.q_ptr->setGraphPositionQuery(q_ptr->graphPositionQuery()); + m_changeTracker.graphPositionQueryPositionChanged = false; + other.m_changeTracker.graphPositionQueryPositionChanged = false; + } if (m_changeTracker.cameraChanged) { m_camera->setDirty(true); m_changeTracker.cameraChanged = false; @@ -649,4 +723,19 @@ void Q3DScenePrivate::setLightPositionRelativeToCamera(const QVector3D &relative distanceModifier)); } +void Q3DScenePrivate::markDirty() +{ + m_sceneDirty = true; + emit needRender(); +} + +bool Q3DScenePrivate::isInArea(const QRect &area, int x, int y) const +{ + int areaMinX = area.x(); + int areaMaxX = area.x() + area.width(); + int areaMinY = area.y(); + int areaMaxY = area.y() + area.height(); + return ( x >= areaMinX && x <= areaMaxX && y >= areaMinY && y <= areaMaxY ); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/q3dscene.h b/src/datavisualization/engine/q3dscene.h index 1699b125..a46b4d7b 100644 --- a/src/datavisualization/engine/q3dscene.h +++ b/src/datavisualization/engine/q3dscene.h @@ -41,6 +41,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DScene : public QObject Q_PROPERTY(Q3DCamera* activeCamera READ activeCamera WRITE setActiveCamera NOTIFY activeCameraChanged) Q_PROPERTY(Q3DLight* activeLight READ activeLight WRITE setActiveLight NOTIFY activeLightChanged) Q_PROPERTY(float devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) + Q_PROPERTY(QPoint graphPositionQuery READ graphPositionQuery WRITE setGraphPositionQuery NOTIFY graphPositionQueryChanged REVISION 1) public: Q3DScene(QObject *parent = 0); @@ -60,6 +61,9 @@ public: QPoint selectionQueryPosition() const; static QPoint invalidSelectionPoint(); + void setGraphPositionQuery(const QPoint &point); + QPoint graphPositionQuery() const; + void setSlicingActive(bool isSlicing); bool isSlicingActive() const; @@ -85,6 +89,7 @@ signals: void activeLightChanged(Q3DLight *light); void devicePixelRatioChanged(float pixelRatio); void selectionQueryPositionChanged(const QPoint &position); + Q_REVISION(1) void graphPositionQueryChanged(const QPoint &position); private: QScopedPointer<Q3DScenePrivate> d_ptr; diff --git a/src/datavisualization/engine/q3dscene_p.h b/src/datavisualization/engine/q3dscene_p.h index 2c69e5e0..d0f6e735 100644 --- a/src/datavisualization/engine/q3dscene_p.h +++ b/src/datavisualization/engine/q3dscene_p.h @@ -47,6 +47,7 @@ struct Q3DSceneChangeBitField { bool slicingActivatedChanged : 1; bool devicePixelRatioChanged : 1; bool selectionQueryPositionChanged : 1; + bool graphPositionQueryPositionChanged : 1; bool windowSizeChanged : 1; Q3DSceneChangeBitField() @@ -59,6 +60,7 @@ struct Q3DSceneChangeBitField { slicingActivatedChanged(true), devicePixelRatioChanged(true), selectionQueryPositionChanged(false), + graphPositionQueryPositionChanged(false), windowSizeChanged(true) { } @@ -89,6 +91,10 @@ public: float fixedRotation = 0.0f, float distanceModifier = 0.0f); + void markDirty(); + + bool isInArea(const QRect &area, int x, int y) const; + signals: void needRender(); @@ -106,6 +112,7 @@ public: bool m_isUnderSideCameraEnabled; bool m_isSlicingActive; QPoint m_selectionQueryPosition; + QPoint m_graphPositionQueryPosition; QSize m_windowSize; QRect m_glViewport; QRect m_glPrimarySubViewport; diff --git a/src/datavisualization/engine/q3dsurface.cpp b/src/datavisualization/engine/q3dsurface.cpp index c5ce29d7..ce3e7f4c 100644 --- a/src/datavisualization/engine/q3dsurface.cpp +++ b/src/datavisualization/engine/q3dsurface.cpp @@ -98,6 +98,8 @@ Q3DSurface::Q3DSurface(const QSurfaceFormat *format, QWindow *parent) dptr()->m_shared->initializeOpenGL(); QObject::connect(dptr()->m_shared, &Surface3DController::selectedSeriesChanged, this, &Q3DSurface::selectedSeriesChanged); + QObject::connect(dptr()->m_shared, &Surface3DController::flipHorizontalGridChanged, + this, &Q3DSurface::flipHorizontalGridChanged); } /*! @@ -230,6 +232,31 @@ QSurface3DSeries *Q3DSurface::selectedSeries() const } /*! + * \property Q3DSurface::flipHorizontalGrid + * \since QtDataVisualization 1.2 + * + * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more + * useful to display the horizontal axis grid on top of the graph rather than on the bottom. + * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with + * a top-down viewpoint. + * + * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background + * of the graph. + * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph + * from the horizontal background. + * Defaults to \c{false}. + */ +void Q3DSurface::setFlipHorizontalGrid(bool flip) +{ + dptr()->m_shared->setFlipHorizontalGrid(flip); +} + +bool Q3DSurface::flipHorizontalGrid() const +{ + return dptrc()->m_shared->flipHorizontalGrid(); +} + +/*! * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use, * addAxis is simply used to give the ownership of the \a axis to the graph. * The \a axis must not be null or added to another graph. diff --git a/src/datavisualization/engine/q3dsurface.h b/src/datavisualization/engine/q3dsurface.h index 9868c844..86740519 100644 --- a/src/datavisualization/engine/q3dsurface.h +++ b/src/datavisualization/engine/q3dsurface.h @@ -34,6 +34,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DSurface : public QAbstract3DGraph Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged) Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged) Q_PROPERTY(QSurface3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) + Q_PROPERTY(bool flipHorizontalGrid READ flipHorizontalGrid WRITE setFlipHorizontalGrid NOTIFY flipHorizontalGridChanged) public: explicit Q3DSurface(const QSurfaceFormat *format = 0, QWindow *parent = 0); @@ -55,12 +56,15 @@ public: QList<QValue3DAxis *> axes() const; QSurface3DSeries *selectedSeries() const; + void setFlipHorizontalGrid(bool flip); + bool flipHorizontalGrid() const; signals: void axisXChanged(QValue3DAxis *axis); void axisYChanged(QValue3DAxis *axis); void axisZChanged(QValue3DAxis *axis); void selectedSeriesChanged(QSurface3DSeries *series); + void flipHorizontalGridChanged(bool flip); private: Q3DSurfacePrivate *dptr(); diff --git a/src/datavisualization/engine/qabstract3dgraph.cpp b/src/datavisualization/engine/qabstract3dgraph.cpp index b8fa92e8..e51d9ce4 100644 --- a/src/datavisualization/engine/qabstract3dgraph.cpp +++ b/src/datavisualization/engine/qabstract3dgraph.cpp @@ -22,6 +22,7 @@ #include "qabstract3dinputhandler_p.h" #include "q3dscene_p.h" #include "qutils.h" +#include "utils_p.h" #include <QtGui/QGuiApplication> #include <QtGui/QOpenGLContext> @@ -29,6 +30,9 @@ #include <QtGui/QPainter> #include <QtGui/QOpenGLFramebufferObject> #include <QtGui/QOffscreenSurface> +#if defined(Q_OS_OSX) +#include <qpa/qplatformnativeinterface.h> +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -148,7 +152,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION \value OptimizationDefault Provides the full feature set at a reasonable performance. \value OptimizationStatic - Beta level feature. Optimizes the rendering of static data sets at the expense of some features. + Optimizes the rendering of static data sets at the expense of some features. */ /*! @@ -169,11 +173,7 @@ QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFor if (format) { surfaceFormat = *format; // Make sure renderable type is correct -#if !defined(QT_OPENGL_ES_2) - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGL); -#else - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); -#endif + surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); } else { surfaceFormat = qDefaultSurfaceFormat(); } @@ -197,17 +197,24 @@ QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFor qDebug() << "GLSL version:" << (const char *)shaderVersion; #endif -#if !defined(QT_OPENGL_ES_2) - // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not. - QStringList splitversionstr = - QString::fromLatin1((const char *)shaderVersion).split(QChar::fromLatin1(' ')); - if (splitversionstr[0].toFloat() < 1.2) - qFatal("GLSL version must be 1.20 or higher. Try installing latest display drivers."); -#else - Q_UNUSED(shaderVersion) -#endif + if (!Utils::isOpenGLES()) { + // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not. + QStringList splitversionstr = + QString::fromLatin1((const char *)shaderVersion).split(QChar::fromLatin1(' ')); + if (splitversionstr[0].toFloat() < 1.2) + qFatal("GLSL version must be 1.20 or higher. Try installing latest display drivers."); + } d_ptr->renderLater(); + +#if defined(Q_OS_OSX) + // Enable touch events for Mac touchpads + typedef void * (*EnableTouch)(QWindow*, bool); + EnableTouch enableTouch = + (EnableTouch)QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow"); + if (enableTouch) + enableTouch(this, true); +#endif } /*! @@ -400,7 +407,7 @@ void QAbstract3DGraph::clearSelection() * \return index to the added item if add was successful, -1 if trying to add a null item, and * index of the item if trying to add an already added item. * - * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt() + * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt(), customItems() * * \since QtDataVisualization 1.1 */ @@ -455,6 +462,16 @@ void QAbstract3DGraph::releaseCustomItem(QCustom3DItem *item) } /*! + * \return list of all added custom items. + * \since QtDataVisualization 1.2 + * \sa addCustomItem() + */ +QList<QCustom3DItem *> QAbstract3DGraph::customItems() const +{ + return d_ptr->m_visualController->customItems(); +} + +/*! * Can be used to query the index of the selected label after receiving \c selectedElementChanged * signal with any label type. Selection is valid until the next \c selectedElementChanged signal. * @@ -545,6 +562,8 @@ QAbstract3DGraph::ElementType QAbstract3DGraph::selectedElement() const * \since QtDataVisualization 1.1 * * \return rendered image. + * + * \note OpenGL ES2 does not support anitialiasing, so \a msaaSamples is always forced to \c{0}. */ QImage QAbstract3DGraph::renderToImage(int msaaSamples, const QSize &imageSize) { @@ -591,8 +610,15 @@ qreal QAbstract3DGraph::currentFps() const * \property QAbstract3DGraph::orthoProjection * \since QtDataVisualization 1.1 * - * If \c {true}, orthographic projection will be used for displaying the graph. Defaults to \c{false}. + * If \c {true}, orthographic projection will be used for displaying the graph. + * \note Orthographic projection can be used to create 2D graphs by replacing the default input + * handler with one that doesn't allow rotating the graph and setting the camera to view the graph + * directly from the side or from the top. Also, axis labels typically need to be rotated when + * viewing the graph from the sides. + * Defaults to \c{false}. * \note Shadows will be disabled when set to \c{true}. + * + * \sa QAbstract3DAxis::labelAutoRotation, Q3DCamera::cameraPreset */ void QAbstract3DGraph::setOrthoProjection(bool enable) { @@ -608,14 +634,16 @@ bool QAbstract3DGraph::isOrthoProjection() const * \property QAbstract3DGraph::aspectRatio * \since QtDataVisualization 1.1 * - * Aspect ratio of the graph data. This is the ratio of data scaling between horizontal and - * vertical axes. Defaults to \c{2.0}. + * The aspect ratio is the ratio of the graph scaling between the longest axis on the horizontal + * plane and the Y-axis. Defaults to \c{2.0}. * * \note Has no effect on Q3DBars. + * + * \sa horizontalAspectRatio */ void QAbstract3DGraph::setAspectRatio(qreal ratio) { - d_ptr->m_visualController->setAspectRatio(float(ratio)); + d_ptr->m_visualController->setAspectRatio(ratio); } qreal QAbstract3DGraph::aspectRatio() const @@ -626,14 +654,20 @@ qreal QAbstract3DGraph::aspectRatio() const /*! * \property QAbstract3DGraph::optimizationHints * - * Defines if the rendering optimization is default or static. Default mode provides the full feature set at - * reasonable performance. Static is a beta level feature and currently supports only a subset of the - * features on the Scatter graph. Missing features are object gradient for mesh objects, both gradients - * for points, and diffuse and specular color on rotations. At this point static is intended just for - * introducing a new feature. It optimizes graph rendering and is ideal for large non-changing data - * sets. It is slower with dynamic data changes and item rotations. Selection is not optimized, so using it - * with massive data sets is not advisable. + * Defines if the rendering optimization is default or static. Default mode provides the full + * feature set at reasonable performance. Static mode optimizes graph rendering and is ideal for + * large non-changing data sets. It is slower with dynamic data changes and item rotations. + * Selection is not optimized, so using it with massive data sets is not advisable. + * Static works only on the Scatter graph. * Defaults to \c{OptimizationDefault}. + * + * \note On some environments, large graphs using static optimization may not render, because + * all of the items are rendered using a single draw call, and different graphics drivers have + * different maximum vertice counts per call that they support. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * To work around this issue, choose an item mesh with low vertex count or use the point mesh. + * + * \sa QAbstract3DSeries::mesh */ void QAbstract3DGraph::setOptimizationHints(OptimizationHints hints) { @@ -646,6 +680,195 @@ QAbstract3DGraph::OptimizationHints QAbstract3DGraph::optimizationHints() const } /*! + * \property QAbstract3DGraph::polar + * \since QtDataVisualization 1.2 + * + * If \c {true}, the horizontal axes are changed into polar axes. The X axis becomes the + * angular axis and the Z axis becomes the radial axis. + * Polar mode is not available for bar graphs. + * + * Defaults to \c{false}. + * + * \sa orthoProjection, radialLabelOffset + */ +void QAbstract3DGraph::setPolar(bool enable) +{ + d_ptr->m_visualController->setPolar(enable); +} + +bool QAbstract3DGraph::isPolar() const +{ + return d_ptr->m_visualController->isPolar(); +} + +/*! + * \property QAbstract3DGraph::radialLabelOffset + * \since QtDataVisualization 1.2 + * + * This property specifies the normalized horizontal offset for the axis labels of the radial + * polar axis. The value 0.0 indicates the labels should be drawn next to the 0-angle angular + * axis grid line. The value 1.0 indicates the labels are drawn on their normal place at the edge + * of the graph background. + * This property is ignored if polar property value is \c{false}. Defaults to 1.0. + * + * \sa polar + */ +void QAbstract3DGraph::setRadialLabelOffset(float offset) +{ + d_ptr->m_visualController->setRadialLabelOffset(offset); +} + +float QAbstract3DGraph::radialLabelOffset() const +{ + return d_ptr->m_visualController->radialLabelOffset(); +} + +/*! + * \property QAbstract3DGraph::horizontalAspectRatio + * \since QtDataVisualization 1.2 + * + * The horizontal aspect ratio is the ratio of the graph scaling between the X and Z axes. + * Value of 0.0 indicates automatic scaling according to axis ranges. + * Defaults to \c{0.0}. + * + * \note Has no effect on Q3DBars, which handles scaling on the horizontal plane via + * \l{Q3DBars::barThickness}{barThickness} and \l{Q3DBars::barSpacing}{barSpacing} properties. + * Polar graphs also ignore this property. + * + * \sa aspectRatio, polar, Q3DBars::barThickness, Q3DBars::barSpacing + */ +void QAbstract3DGraph::setHorizontalAspectRatio(qreal ratio) +{ + d_ptr->m_visualController->setHorizontalAspectRatio(ratio); +} + +qreal QAbstract3DGraph::horizontalAspectRatio() const +{ + return d_ptr->m_visualController->horizontalAspectRatio(); +} + +/*! + * \property QAbstract3DGraph::reflection + * \since QtDataVisualization 1.2 + * + * Sets floor reflections on or off. Defaults to \c{false}. + * + * \note Affects only Q3DBars. + * + * \note In Q3DBars graphs holding both positive and negative values, reflections are not supported + * for custom items that intersect the floor plane. In that case, reflections should be turned off + * to avoid incorrect rendering. + * + * \note If using custom surface format, stencil buffer needs to be defined + * (QSurfaceFormat::setStencilBufferSize()) for reflections to work. + * + * \sa reflectivity + */ +void QAbstract3DGraph::setReflection(bool enable) +{ + d_ptr->m_visualController->setReflection(enable); +} + +bool QAbstract3DGraph::isReflection() const +{ + return d_ptr->m_visualController->reflection(); +} + +/*! + * \property QAbstract3DGraph::reflectivity + * \since QtDataVisualization 1.2 + * + * Adjusts floor reflectivity, larger number being more reflective. Valid range is \c{[0...1]}. + * Defaults to \c{0.5}. + * + * \note Affects only Q3DBars. + * + * \sa reflection + */ +void QAbstract3DGraph::setReflectivity(qreal reflectivity) +{ + d_ptr->m_visualController->setReflectivity(reflectivity); +} + +qreal QAbstract3DGraph::reflectivity() const +{ + return d_ptr->m_visualController->reflectivity(); +} + +/*! + * \property QAbstract3DGraph::locale + * \since QtDataVisualization 1.2 + * + * Sets the locale used for formatting various numeric labels. + * Defaults to \c{"C"} locale. + * + * \sa QValue3DAxis::labelFormat + */ +void QAbstract3DGraph::setLocale(const QLocale &locale) +{ + d_ptr->m_visualController->setLocale(locale); +} + +QLocale QAbstract3DGraph::locale() const +{ + return d_ptr->m_visualController->locale(); +} + +/*! + * \property QAbstract3DGraph::queriedGraphPosition + * \since QtDataVisualization 1.2 + * + * This read-only property contains the latest graph position values along each axis queried using + * Q3DScene::graphPositionQuery. The values are normalized to range \c{[-1, 1]}. + * If the queried position was outside the graph bounds, the values + * will not reflect the real position, but will instead be some undefined position outside + * the range \c{[-1, 1]}. The value will be undefined before any queries are made. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs only allow querying graph position at the graph floor level, + * so the Y-value is always zero for bar graphs and the valid queries can be only made at + * screen positions that contain the floor of the graph. + * + * \sa Q3DScene::graphPositionQuery + */ +QVector3D QAbstract3DGraph::queriedGraphPosition() const +{ + return d_ptr->m_visualController->queriedGraphPosition(); +} + +/*! + * \property QAbstract3DGraph::margin + * \since QtDataVisualization 1.2 + * + * This property contains the absolute value used for graph margin. The graph margin is the space + * left between the edge of the plottable graph area and the edge of the graph background. + * If the margin value is negative, the margins are determined automatically and can vary according + * to size of the items in the series and the type of the graph. + * The value is interpreted as a fraction of Y-axis range, provided the graph aspect ratios have + * not beed changed from the defaults. + * Defaults to \c{-1.0}. + * + * \note Having smaller than the automatically determined margin on scatter graph can cause + * the scatter items at the edges of the graph to overlap with the graph background. + * + * \note On scatter and surface graphs, if the margin is comparatively small to the axis label + * size, the positions of the edge labels of the axes are adjusted to avoid overlap with + * the edge labels of the neighboring axes. + */ +void QAbstract3DGraph::setMargin(qreal margin) +{ + d_ptr->m_visualController->setMargin(margin); +} + +qreal QAbstract3DGraph::margin() const +{ + return d_ptr->m_visualController->margin(); +} + +/*! * \internal */ bool QAbstract3DGraph::event(QEvent *event) @@ -795,6 +1018,23 @@ void QAbstract3DGraphPrivate::setVisualController(Abstract3DController *controll QObject::connect(m_visualController, &Abstract3DController::aspectRatioChanged, q_ptr, &QAbstract3DGraph::aspectRatioChanged); + QObject::connect(m_visualController, &Abstract3DController::polarChanged, q_ptr, + &QAbstract3DGraph::polarChanged); + QObject::connect(m_visualController, &Abstract3DController::radialLabelOffsetChanged, q_ptr, + &QAbstract3DGraph::radialLabelOffsetChanged); + QObject::connect(m_visualController, &Abstract3DController::horizontalAspectRatioChanged, q_ptr, + &QAbstract3DGraph::horizontalAspectRatioChanged); + + QObject::connect(m_visualController, &Abstract3DController::reflectionChanged, q_ptr, + &QAbstract3DGraph::reflectionChanged); + QObject::connect(m_visualController, &Abstract3DController::reflectivityChanged, q_ptr, + &QAbstract3DGraph::reflectivityChanged); + QObject::connect(m_visualController, &Abstract3DController::localeChanged, q_ptr, + &QAbstract3DGraph::localeChanged); + QObject::connect(m_visualController, &Abstract3DController::queriedGraphPositionChanged, q_ptr, + &QAbstract3DGraph::queriedGraphPositionChanged); + QObject::connect(m_visualController, &Abstract3DController::marginChanged, q_ptr, + &QAbstract3DGraph::marginChanged); } void QAbstract3DGraphPrivate::handleDevicePixelRatioChange() @@ -849,8 +1089,10 @@ QImage QAbstract3DGraphPrivate::renderToImage(int msaaSamples, const QSize &imag // Render the wanted frame offscreen m_context->makeCurrent(m_offscreenSurface); fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - fboFormat.setInternalTextureFormat(GL_RGB); - fboFormat.setSamples(msaaSamples); + if (!Utils::isOpenGLES()) { + fboFormat.setInternalTextureFormat(GL_RGB); + fboFormat.setSamples(msaaSamples); + } fbo = new QOpenGLFramebufferObject(imageSize, fboFormat); if (fbo->isValid()) { QRect originalViewport = m_visualController->m_scene->viewport(); diff --git a/src/datavisualization/engine/qabstract3dgraph.h b/src/datavisualization/engine/qabstract3dgraph.h index 59f61aae..a0c26a42 100644 --- a/src/datavisualization/engine/qabstract3dgraph.h +++ b/src/datavisualization/engine/qabstract3dgraph.h @@ -25,6 +25,7 @@ #include <QtDataVisualization/qabstract3dinputhandler.h> #include <QtGui/QWindow> #include <QtGui/QOpenGLFunctions> +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -50,6 +51,14 @@ class QT_DATAVISUALIZATION_EXPORT QAbstract3DGraph : public QWindow, protected Q Q_PROPERTY(ElementType selectedElement READ selectedElement NOTIFY selectedElementChanged) Q_PROPERTY(qreal aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged) Q_PROPERTY(OptimizationHints optimizationHints READ optimizationHints WRITE setOptimizationHints NOTIFY optimizationHintsChanged) + Q_PROPERTY(bool polar READ isPolar WRITE setPolar NOTIFY polarChanged) + Q_PROPERTY(float radialLabelOffset READ radialLabelOffset WRITE setRadialLabelOffset NOTIFY radialLabelOffsetChanged) + Q_PROPERTY(qreal horizontalAspectRatio READ horizontalAspectRatio WRITE setHorizontalAspectRatio NOTIFY horizontalAspectRatioChanged) + Q_PROPERTY(bool reflection READ isReflection WRITE setReflection NOTIFY reflectionChanged) + Q_PROPERTY(qreal reflectivity READ reflectivity WRITE setReflectivity NOTIFY reflectivityChanged) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged) + Q_PROPERTY(QVector3D queriedGraphPosition READ queriedGraphPosition NOTIFY queriedGraphPositionChanged) + Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged) protected: explicit QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFormat *format, @@ -126,6 +135,7 @@ public: void removeCustomItem(QCustom3DItem *item); void removeCustomItemAt(const QVector3D &position); void releaseCustomItem(QCustom3DItem *item); + QList<QCustom3DItem *> customItems() const; int selectedLabelIndex() const; QAbstract3DAxis *selectedAxis() const; @@ -150,6 +160,29 @@ public: void setOptimizationHints(OptimizationHints hints); OptimizationHints optimizationHints() const; + void setPolar(bool enable); + bool isPolar() const; + + void setRadialLabelOffset(float offset); + float radialLabelOffset() const; + + void setHorizontalAspectRatio(qreal ratio); + qreal horizontalAspectRatio() const; + + void setReflection(bool enable); + bool isReflection() const; + + void setReflectivity(qreal reflectivity); + qreal reflectivity() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + QVector3D queriedGraphPosition() const; + + void setMargin(qreal margin); + qreal margin() const; + protected: bool event(QEvent *event); void resizeEvent(QResizeEvent *event); @@ -173,6 +206,14 @@ signals: void orthoProjectionChanged(bool enabled); void aspectRatioChanged(qreal ratio); void optimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints); + void polarChanged(bool enabled); + void radialLabelOffsetChanged(float offset); + void horizontalAspectRatioChanged(qreal ratio); + void reflectionChanged(bool enabled); + void reflectivityChanged(qreal reflectivity); + void localeChanged(const QLocale &locale); + void queriedGraphPositionChanged(const QVector3D &data); + void marginChanged(qreal margin); private: Q_DISABLE_COPY(QAbstract3DGraph) diff --git a/src/datavisualization/engine/scatter3drenderer.cpp b/src/datavisualization/engine/scatter3drenderer.cpp index dd50188b..f6367153 100644 --- a/src/datavisualization/engine/scatter3drenderer.cpp +++ b/src/datavisualization/engine/scatter3drenderer.cpp @@ -33,12 +33,9 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -//#define USE_UNIFORM_SCALING // Scale x and z uniformly, or based on autoscaled values - const GLfloat defaultMinSize = 0.01f; const GLfloat defaultMaxSize = 0.1f; const GLfloat itemScaler = 3.0f; -const GLfloat gridLineWidth = 0.005f; Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) : Abstract3DRenderer(controller), @@ -46,30 +43,27 @@ Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) m_updateLabels(false), m_dotShader(0), m_dotGradientShader(0), - #if defined(QT_OPENGL_ES_2) + m_staticSelectedItemGradientShader(0), + m_staticSelectedItemShader(0), m_pointShader(0), - #endif m_depthShader(0), m_selectionShader(0), m_backgroundShader(0), - m_labelShader(0), + m_staticGradientPointShader(0), m_bgrTexture(0), - m_depthTexture(0), m_selectionTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), m_selectionDepthBuffer(0), m_shadowQualityToShader(100.0f), m_shadowQualityMultiplier(3), - m_heightNormalizer(1.0f), - m_scaleFactor(0), + m_scaleX(0.0f), + m_scaleY(0.0f), + m_scaleZ(0.0f), m_selectedItemIndex(Scatter3DController::invalidSelectionIndex()), m_selectedSeriesCache(0), m_oldSelectedSeriesCache(0), - m_areaSize(QSizeF(0.0, 0.0)), m_dotSizeScale(1.0f), - m_hasHeightAdjustmentChanged(true), - m_backgroundMargin(defaultMaxSize), m_maxItemSize(0.0f), m_clickedIndex(Scatter3DController::invalidSelectionIndex()), m_havePointSeries(false), @@ -77,15 +71,13 @@ Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) m_haveUniformColorMeshSeries(false), m_haveGradientMeshSeries(false) { - m_axisCacheY.setScale(2.0f); - m_axisCacheY.setTranslate(-1.0f); - - initializeOpenGLFunctions(); initializeOpenGL(); } Scatter3DRenderer::~Scatter3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -94,11 +86,13 @@ Scatter3DRenderer::~Scatter3DRenderer() m_textureHelper->deleteTexture(&m_bgrTexture); } delete m_dotShader; + delete m_staticSelectedItemGradientShader; + delete m_staticSelectedItemShader; delete m_dotGradientShader; delete m_depthShader; delete m_selectionShader; delete m_backgroundShader; - delete m_labelShader; + delete m_staticGradientPointShader; } void Scatter3DRenderer::initializeOpenGL() @@ -106,28 +100,17 @@ void Scatter3DRenderer::initializeOpenGL() Abstract3DRenderer::initializeOpenGL(); // Initialize shaders - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) - // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. - initDepthShader(); -#else - // Init point shader - initPointShader(); -#endif + if (!m_isOpenGLES) { + initDepthShader(); // For shadows + loadGridLineMesh(); + } else { + initPointShader(); + } // Init selection shader initSelectionShader(); -#if !defined(QT_OPENGL_ES_2) - // Load grid line mesh - loadGridLineMesh(); -#endif - - // Load label mesh - loadLabelMesh(); - // Set view port glViewport(m_primarySubViewport.x(), m_primarySubViewport.y(), @@ -138,6 +121,53 @@ void Scatter3DRenderer::initializeOpenGL() loadBackgroundMesh(); } +void Scatter3DRenderer::fixCameraTarget(QVector3D &target) +{ + target.setX(target.x() * m_scaleX); + target.setY(target.y() * m_scaleY); + target.setZ(target.z() * -m_scaleZ); +} + +void Scatter3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) +{ + // The inputs are the item bounds in OpenGL coordinates. + // The outputs limit these bounds to visible ranges, normalized to range [-1, 1] + // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those + float itemRangeX = (maxBounds.x() - minBounds.x()); + float itemRangeY = (maxBounds.y() - minBounds.y()); + float itemRangeZ = (maxBounds.z() - minBounds.z()); + + if (minBounds.x() < -m_scaleX) + minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_scaleX) / itemRangeX)); + else + minBounds.setX(-1.0f); + + if (minBounds.y() < -m_scaleY) + minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + m_scaleY) / itemRangeY))); + else + minBounds.setY(1.0f); + + if (minBounds.z() < -m_scaleZ) + minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_scaleZ) / itemRangeZ))); + else + minBounds.setZ(1.0f); + + if (maxBounds.x() > m_scaleX) + maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_scaleX) / itemRangeX)); + else + maxBounds.setX(1.0f); + + if (maxBounds.y() > m_scaleY) + maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - m_scaleY) / itemRangeY))); + else + maxBounds.setY(-1.0f); + + if (maxBounds.z() > m_scaleZ) + maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_scaleZ) / itemRangeZ))); + else + maxBounds.setZ(-1.0f); +} + void Scatter3DRenderer::updateData() { calculateSceneScalingFactors(); @@ -145,24 +175,31 @@ void Scatter3DRenderer::updateData() foreach (SeriesRenderCache *baseCache, m_renderCacheList) { ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache); - if (cache->isVisible() && cache->dataDirty()) { + if (cache->isVisible()) { const QScatter3DSeries *currentSeries = cache->series(); ScatterRenderItemArray &renderArray = cache->renderArray(); QScatterDataProxy *dataProxy = currentSeries->dataProxy(); const QScatterDataArray &dataArray = *dataProxy->array(); int dataSize = dataArray.size(); totalDataSize += dataSize; - if (dataSize != renderArray.size()) - renderArray.resize(dataSize); + if (cache->dataDirty()) { + if (dataSize != renderArray.size()) + renderArray.resize(dataSize); + + for (int i = 0; i < dataSize; i++) + updateRenderItem(dataArray.at(i), renderArray[i]); - for (int i = 0; i < dataSize; i++) - updateRenderItem(dataArray.at(i), renderArray[i]); - cache->setDataDirty(false); + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) + cache->setStaticBufferDirty(true); + + cache->setDataDirty(false); + } } } if (totalDataSize) { - m_dotSizeScale = GLfloat(qBound(defaultMinSize, 2.0f / float(qSqrt(qreal(totalDataSize))), + m_dotSizeScale = GLfloat(qBound(defaultMinSize, + 2.0f / float(qSqrt(qreal(totalDataSize))), defaultMaxSize)); } @@ -179,6 +216,7 @@ void Scatter3DRenderer::updateData() points = new ScatterPointBufferHelper(); cache->setBufferPoints(points); } + points->setScaleY(m_scaleY); points->load(cache); } else { ScatterObjectBufferHelper *object = cache->bufferObject(); @@ -187,7 +225,9 @@ void Scatter3DRenderer::updateData() cache->setBufferObject(object); } if (renderArraySize != cache->oldArraySize() - || cache->object()->objectFile() != cache->oldMeshFileName()) { + || cache->object()->objectFile() != cache->oldMeshFileName() + || cache->staticBufferDirty()) { + object->setScaleY(m_scaleY); object->fullLoad(cache, m_dotSizeScale); cache->setOldArraySize(renderArraySize); cache->setOldMeshFileName(cache->object()->objectFile()); @@ -195,6 +235,8 @@ void Scatter3DRenderer::updateData() object->update(cache, m_dotSizeScale); } } + + cache->setStaticBufferDirty(false); } } } @@ -205,9 +247,28 @@ void Scatter3DRenderer::updateData() void Scatter3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList) { + int seriesCount = seriesList.size(); + + // Check OptimizationStatic specific issues before populate marks changeTracker done + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) { + for (int i = 0; i < seriesCount; i++) { + QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(seriesList[i]); + if (scatterSeries->isVisible()) { + QAbstract3DSeriesChangeBitField &changeTracker = scatterSeries->d_ptr->m_changeTracker; + ScatterSeriesRenderCache *cache = + static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(scatterSeries)); + if (cache) { + if (changeTracker.baseGradientChanged || changeTracker.colorStyleChanged) + cache->setStaticObjectUVDirty(true); + if (cache->itemSize() != scatterSeries->itemSize()) + cache->setStaticBufferDirty(true); + } + } + } + } + Abstract3DRenderer::updateSeries(seriesList); - int seriesCount = seriesList.size(); float maxItemSize = 0.0f; float itemSize = 0.0f; bool noSelection = true; @@ -243,13 +304,28 @@ void Scatter3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesLis else m_haveGradientMeshSeries = true; } + + if (cache->staticBufferDirty()) { + if (cache->mesh() != QAbstract3DSeries::MeshPoint) { + ScatterObjectBufferHelper *object = cache->bufferObject(); + object->update(cache, m_dotSizeScale); + } + cache->setStaticBufferDirty(false); + } + if (cache->staticObjectUVDirty()) { + if (cache->mesh() == QAbstract3DSeries::MeshPoint) { + ScatterPointBufferHelper *object = cache->bufferPoints(); + object->updateUVs(cache); + } else { + ScatterObjectBufferHelper *object = cache->bufferObject(); + object->updateUVs(cache); + } + cache->setStaticObjectUVDirty(false); + } } } m_maxItemSize = maxItemSize; - if (maxItemSize > defaultMaxSize) - m_backgroundMargin = maxItemSize / itemScaler; - else - m_backgroundMargin = defaultMaxSize; + calculateSceneScalingFactors(); if (noSelection) { if (!selectionLabel().isEmpty()) @@ -268,6 +344,8 @@ void Scatter3DRenderer::updateItems(const QVector<Scatter3DController::ChangeIte ScatterSeriesRenderCache *cache = 0; const QScatter3DSeries *prevSeries = 0; const QScatterDataArray *dataArray = 0; + const bool optimizationStatic = m_cachedOptimizationHint.testFlag( + QAbstract3DGraph::OptimizationStatic); foreach (Scatter3DController::ChangeItem item, items) { QScatter3DSeries *currentSeries = item.series; @@ -282,7 +360,43 @@ void Scatter3DRenderer::updateItems(const QVector<Scatter3DController::ChangeIte } if (cache->isVisible()) { const int index = item.index; - updateRenderItem(dataArray->at(index), cache->renderArray()[index]); + if (index >= cache->renderArray().size()) + continue; // Items removed from array for same render + bool oldVisibility; + ScatterRenderItem &item = cache->renderArray()[index]; + if (optimizationStatic) + oldVisibility = item.isVisible(); + updateRenderItem(dataArray->at(index), item); + if (optimizationStatic) { + if (!cache->visibilityChanged() && oldVisibility != item.isVisible()) + cache->setVisibilityChanged(true); + cache->updateIndices().append(index); + } + } + } + if (optimizationStatic) { + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { + ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache); + if (cache->isVisible() && cache->updateIndices().size()) { + if (cache->mesh() == QAbstract3DSeries::MeshPoint) { + cache->bufferPoints()->update(cache); + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + cache->bufferPoints()->updateUVs(cache); + } else { + if (cache->visibilityChanged()) { + // If any change changes item visibility, full load is needed to + // resize the buffers. + cache->updateIndices().clear(); + cache->bufferObject()->fullLoad(cache, m_dotSizeScale); + } else { + cache->bufferObject()->update(cache, m_dotSizeScale); + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + cache->bufferObject()->updateUVs(cache); + } + } + cache->updateIndices().clear(); + } + cache->setVisibilityChanged(false); } } } @@ -291,14 +405,46 @@ void Scatter3DRenderer::updateScene(Q3DScene *scene) { scene->activeCamera()->d_ptr->setMinYRotation(-90.0f); - if (m_hasHeightAdjustmentChanged) { - // Set initial camera position. Also update if height adjustment has changed. - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, zeroVector, - upVector); - m_hasHeightAdjustmentChanged = false; + Abstract3DRenderer::updateScene(scene); +} + +void Scatter3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels) +{ + Abstract3DRenderer::updateAxisLabels(orientation, labels); + + // Angular axis label dimensions affect the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Scatter3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible) +{ + Abstract3DRenderer::updateAxisTitleVisibility(orientation, visible); + + // Angular axis title existence affects the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Scatter3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint) +{ + Abstract3DRenderer::updateOptimizationHint(hint); + + Abstract3DRenderer::reInitShaders(); + + if (m_isOpenGLES && hint.testFlag(QAbstract3DGraph::OptimizationStatic) + && !m_staticGradientPointShader) { + initStaticPointShaders(QStringLiteral(":/shaders/vertexPointES2_UV"), + QStringLiteral(":/shaders/fragmentLabel")); } +} - Abstract3DRenderer::updateScene(scene); +void Scatter3DRenderer::updateMargin(float margin) +{ + Abstract3DRenderer::updateMargin(margin); + calculateSceneScalingFactors(); } void Scatter3DRenderer::resetClickedStatus() @@ -374,6 +520,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) m_yFlipped = true; else m_yFlipped = false; + m_yFlippedForGrid = m_yFlipped; // Polar axis grid drawing in abstract needs this // Calculate background rotation if (!m_zFlipped && !m_xFlipped) @@ -389,169 +536,182 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QVector3D lightPos = m_cachedScene->activeLight()->position(); // Introduce regardless of shadow quality to simplify logic - QMatrix4x4 depthViewMatrix; - QMatrix4x4 depthProjectionMatrix; QMatrix4x4 depthProjectionViewMatrix; + ShaderHelper *pointSelectionShader; + if (!m_isOpenGLES) { #if !defined(QT_OPENGL_ES_2) - if (m_havePointSeries) { - glEnable(GL_POINT_SMOOTH); - glEnable(GL_PROGRAM_POINT_SIZE); - } - - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Render scene into a depth texture for using with shadow mapping - // Bind depth shader - m_depthShader->bind(); - - // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows. - glViewport(0, 0, - m_primarySubViewport.width() * m_shadowQualityMultiplier, - m_primarySubViewport.height() * m_shadowQualityMultiplier); - - // Enable drawing to framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); - glClear(GL_DEPTH_BUFFER_BIT); - - // Set front face culling to reduce self-shadowing issues - glCullFace(GL_FRONT); - - // Get the depth view matrix - // It may be possible to hack lightPos here if we want to make some tweaks to shadow - QVector3D depthLightPos = activeCamera->d_ptr->calculatePositionRelativeToCamera( - zeroVector, 0.0f, 2.5f / m_autoScaleAdjustment); - depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector); - // Set the depth projection matrix - depthProjectionMatrix.perspective(15.0f, viewPortRatio, 3.0f, 100.0f); - depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix; - - // Draw dots to depth buffer - foreach (SeriesRenderCache *baseCache, m_renderCacheList) { - if (baseCache->isVisible()) { - ScatterSeriesRenderCache *cache = - static_cast<ScatterSeriesRenderCache *>(baseCache); - ObjectHelper *dotObj = cache->object(); - QQuaternion seriesRotation(cache->meshRotation()); - const ScatterRenderItemArray &renderArray = cache->renderArray(); - const int renderArraySize = renderArray.size(); - bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint); - float itemSize = cache->itemSize() / itemScaler; - if (itemSize == 0.0f) - itemSize = m_dotSizeScale; - if (drawingPoints) { - // Scale points based on shadow quality for shadows, not by zoom level - glPointSize(itemSize * 100.0f * m_shadowQualityMultiplier); - } - QVector3D modelScaler(itemSize, itemSize, itemSize); + if (m_havePointSeries) { + glEnable(GL_POINT_SMOOTH); + glEnable(GL_PROGRAM_POINT_SIZE); + } - if (!optimizationDefault - && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) - || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { - continue; - } + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Render scene into a depth texture for using with shadow mapping + // Bind depth shader + m_depthShader->bind(); + + // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows. + glViewport(0, 0, + m_primarySubViewport.width() * m_shadowQualityMultiplier, + m_primarySubViewport.height() * m_shadowQualityMultiplier); + + // Enable drawing to framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); + glClear(GL_DEPTH_BUFFER_BIT); + + // Set front face culling to reduce self-shadowing issues + glCullFace(GL_FRONT); + + QMatrix4x4 depthViewMatrix; + QMatrix4x4 depthProjectionMatrix; + + // Get the depth view matrix + // It may be possible to hack lightPos here if we want to make some tweaks to shadow + QVector3D depthLightPos = activeCamera->d_ptr->calculatePositionRelativeToCamera( + zeroVector, 0.0f, 2.5f / m_autoScaleAdjustment); + depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector); + // Set the depth projection matrix + depthProjectionMatrix.perspective(15.0f, viewPortRatio, 3.0f, 100.0f); + depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix; + + // Draw dots to depth buffer + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { + if (baseCache->isVisible()) { + ScatterSeriesRenderCache *cache = + static_cast<ScatterSeriesRenderCache *>(baseCache); + ObjectHelper *dotObj = cache->object(); + QQuaternion seriesRotation(cache->meshRotation()); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const int renderArraySize = renderArray.size(); + bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint); + float itemSize = cache->itemSize() / itemScaler; + if (itemSize == 0.0f) + itemSize = m_dotSizeScale; + if (drawingPoints) { + // Scale points based on shadow quality for shadows, not by zoom level + m_funcs_2_1->glPointSize(itemSize * 100.0f * m_shadowQualityMultiplier); + } + QVector3D modelScaler(itemSize, itemSize, itemSize); - int loopCount = 1; - if (optimizationDefault) - loopCount = renderArraySize; - for (int dot = 0; dot < loopCount; dot++) { - const ScatterRenderItem &item = renderArray.at(dot); - if (!item.isVisible() && optimizationDefault) + if (!optimizationDefault + && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) + || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { continue; - - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - - if (optimizationDefault) { - modelMatrix.translate(item.translation()); - if (!drawingPoints) { - if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) - modelMatrix.rotate(seriesRotation * item.rotation()); - modelMatrix.scale(modelScaler); - } } - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + int loopCount = 1; + if (optimizationDefault) + loopCount = renderArraySize; + for (int dot = 0; dot < loopCount; dot++) { + const ScatterRenderItem &item = renderArray.at(dot); + if (!item.isVisible() && optimizationDefault) + continue; - m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; - if (drawingPoints) { - if (optimizationDefault) - m_drawer->drawPoint(m_depthShader); - else - m_drawer->drawPoints(m_depthShader, cache->bufferPoints()); - } else { if (optimizationDefault) { - // 1st attribute buffer : vertices - glEnableVertexAttribArray(m_depthShader->posAtt()); - glBindBuffer(GL_ARRAY_BUFFER, dotObj->vertexBuf()); - glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, - (void *)0); - - // Index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dotObj->elementBuf()); + modelMatrix.translate(item.translation()); + if (!drawingPoints) { + if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) + modelMatrix.rotate(seriesRotation * item.rotation()); + modelMatrix.scale(modelScaler); + } + } - // Draw the triangles - glDrawElements(GL_TRIANGLES, dotObj->indexCount(), GL_UNSIGNED_SHORT, - (void *)0); + MVPMatrix = depthProjectionViewMatrix * modelMatrix; - // Free buffers - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); - glDisableVertexAttribArray(m_depthShader->posAtt()); + if (drawingPoints) { + if (optimizationDefault) + m_drawer->drawPoint(m_depthShader); + else + m_drawer->drawPoints(m_depthShader, cache->bufferPoints(), 0); } else { - ScatterObjectBufferHelper *object = cache->bufferObject(); - // 1st attribute buffer : vertices - glEnableVertexAttribArray(m_depthShader->posAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf()); - glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, - (void *)0); - - // Index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); - - // Draw the triangles - glDrawElements(GL_TRIANGLES, object->indexCount(), - object->indicesType(), (void *)0); - - // Free buffers - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDisableVertexAttribArray(m_depthShader->posAtt()); + if (optimizationDefault) { + // 1st attribute buffer : vertices + glEnableVertexAttribArray(m_depthShader->posAtt()); + glBindBuffer(GL_ARRAY_BUFFER, dotObj->vertexBuf()); + glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, + (void *)0); + + // Index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dotObj->elementBuf()); + + // Draw the triangles + glDrawElements(GL_TRIANGLES, dotObj->indexCount(), GL_UNSIGNED_SHORT, + (void *)0); + + // Free buffers + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableVertexAttribArray(m_depthShader->posAtt()); + } else { + ScatterObjectBufferHelper *object = cache->bufferObject(); + // 1st attribute buffer : vertices + glEnableVertexAttribArray(m_depthShader->posAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf()); + glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, + (void *)0); + + // Index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); + + // Draw the triangles + glDrawElements(GL_TRIANGLES, object->indexCount(), + object->indicesType(), (void *)0); + + // Free buffers + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableVertexAttribArray(m_depthShader->posAtt()); + } } } } } - } - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); - // Disable drawing to framebuffer (= enable drawing to screen) - glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); + // Disable drawing to framebuffer (= enable drawing to screen) + glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); - // Reset culling to normal - glCullFace(GL_BACK); + // Reset culling to normal + glCullFace(GL_BACK); - // Revert to original viewport - glViewport(m_primarySubViewport.x(), - m_primarySubViewport.y(), - m_primarySubViewport.width(), - m_primarySubViewport.height()); + // Revert to original viewport + glViewport(m_primarySubViewport.x(), + m_primarySubViewport.y(), + m_primarySubViewport.width(), + m_primarySubViewport.height()); + } +#endif + pointSelectionShader = m_selectionShader; + } else { + pointSelectionShader = m_pointShader; } - ShaderHelper *pointSelectionShader = m_selectionShader; -#else - ShaderHelper *pointSelectionShader = m_pointShader; -#endif ShaderHelper *selectionShader = m_selectionShader; + // Do position mapping when necessary + if (m_graphPositionQueryPending) { + QVector3D graphDimensions(m_scaleX, m_scaleY, m_scaleZ); + queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle); + emit needRender(); + } + // Skip selection mode drawing if we have no selection mode if (m_cachedSelectionMode > QAbstract3DGraph::SelectionNone && SelectOnScene == m_selectionState - && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())) { + && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty()) + && m_selectionTexture) { // Draw dots to selection buffer glBindFramebuffer(GL_FRAMEBUFFER, m_selectionFrameBuffer); glViewport(0, 0, @@ -578,8 +738,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (itemSize == 0.0f) itemSize = m_dotSizeScale; #if !defined(QT_OPENGL_ES_2) - if (drawingPoints) - glPointSize(itemSize * activeCamera->zoomLevel()); // Scale points based on zoom + if (drawingPoints && !m_isOpenGLES) + m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel()); #endif QVector3D modelScaler(itemSize, itemSize, itemSize); @@ -627,9 +787,10 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + viewMatrix, projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -639,6 +800,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QVector4D clickedColor = Utils::getSelection(m_inputPosition, m_viewport.height()); selectionColorToSeriesAndIndex(clickedColor, m_clickedIndex, m_clickedSeries); + m_clickResolved = true; emit needRender(); @@ -683,13 +845,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) previousMeshColorStyle = Q3DTheme::ColorStyleRangeGradient; m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), 0.0f); } - glEnable(GL_TEXTURE_2D); } else { dotShader = pointSelectionShader; - previousDrawingPoints = true; - dotShader->bind(); } + float rangeGradientYScaler = 0.5f / m_scaleY; + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { if (baseCache->isVisible()) { ScatterSeriesRenderCache *cache = @@ -710,14 +871,16 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (itemSize == 0.0f) itemSize = m_dotSizeScale; #if !defined(QT_OPENGL_ES_2) - if (drawingPoints) - glPointSize(itemSize * activeCamera->zoomLevel()); // Scale points based on zoom + if (drawingPoints && !m_isOpenGLES) + m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel()); #endif QVector3D modelScaler(itemSize, itemSize, itemSize); + int gradientImageHeight = cache->gradientImage().height(); + int maxGradientPositition = gradientImageHeight - 1; if (!optimizationDefault && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) - || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { + || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { continue; } @@ -725,10 +888,18 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (drawingPoints != previousDrawingPoints || (!drawingPoints && (colorStyleIsUniform != (previousMeshColorStyle - == Q3DTheme::ColorStyleUniform)))) { + == Q3DTheme::ColorStyleUniform))) + || (!optimizationDefault && drawingPoints)) { previousDrawingPoints = drawingPoints; if (drawingPoints) { - dotShader = pointSelectionShader; + if (!optimizationDefault && rangeGradientPoints) { + if (m_isOpenGLES) + dotShader = m_staticGradientPointShader; + else + dotShader = m_labelShader; + } else { + dotShader = pointSelectionShader; + } } else { if (colorStyleIsUniform) dotShader = m_dotShader; @@ -740,13 +911,13 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (!drawingPoints && !colorStyleIsUniform && previousMeshColorStyle != colorStyle) { if (colorStyle == Q3DTheme::ColorStyleObjectGradient) { - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientMin(), 0.0f); - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), - 0.5f); + dotShader->setUniformValue(dotShader->gradientMin(), 0.0f); + dotShader->setUniformValue(dotShader->gradientHeight(), + 0.5f); } else { // Each dot is of uniform color according to its Y-coordinate - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), - 0.0f); + dotShader->setUniformValue(dotShader->gradientHeight(), + 0.0f); } } @@ -760,6 +931,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) int loopCount = 1; if (optimizationDefault) loopCount = renderArraySize; + for (int i = 0; i < loopCount; i++) { ScatterRenderItem &item = renderArray[i]; if (!item.isVisible() && optimizationDefault) @@ -791,7 +963,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (rangeGradientPoints) { // Drawing points with range gradient // Get color from gradient based on items y position converted to percent - int position = int(item.translation().y() * 50.0f) + 50; + int position = ((item.translation().y() + m_scaleY) * rangeGradientYScaler) * gradientImageHeight; + position = qMin(maxGradientPositition, position); // clamp to edge dotColor = Utils::vectorFromColor( cache->gradientImage().pixel(0, position)); } else { @@ -801,6 +974,9 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) gradientTexture = cache->baseGradientTexture(); } + if (!optimizationDefault && rangeGradientPoints) + gradientTexture = cache->baseGradientTexture(); + GLfloat lightStrength = m_cachedTheme->lightStrength(); if (optimizationDefault && selectedSeries && (m_selectedItemIndex == i)) { if (useColor) @@ -808,13 +984,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) else gradientTexture = cache->singleHighlightGradientTexture(); lightStrength = m_cachedTheme->highlightLightStrength(); - // Insert data to ScatterRenderItem - // We don't have ownership, so don't delete the previous one + // Save the reference to the item to be used in label drawing selectedItem = &item; dotSelectionFound = true; // Save selected item size (adjusted with font size) for selection label // positioning - selectedItemSize = itemSize + (m_cachedTheme->font().pointSizeF() / 500.0f); + selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f; } if (!drawingPoints) { @@ -829,10 +1004,10 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) dotShader->setUniformValue(dotShader->color(), dotColor); } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) { dotShader->setUniformValue(dotShader->gradientMin(), - (item.translation().y() + 1.0f) / 2.0f); + (item.translation().y() + m_scaleY) + * rangeGradientYScaler); } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { if (!drawingPoints) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; @@ -853,11 +1028,9 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (optimizationDefault) m_drawer->drawPoint(dotShader); else - m_drawer->drawPoints(dotShader, cache->bufferPoints()); + m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture); } - } else -#endif - { + } else { if (!drawingPoints) { // Set shadowless shader bindings dotShader->setUniformValue(dotShader->lightS(), lightStrength); @@ -871,100 +1044,139 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (optimizationDefault) m_drawer->drawPoint(dotShader); else - m_drawer->drawPoints(dotShader, cache->bufferPoints()); + m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture); } } } + // Draw the selected item on static optimization if (!optimizationDefault && selectedSeries && m_selectedItemIndex != Scatter3DController::invalidSelectionIndex()) { ScatterRenderItem &item = renderArray[m_selectedItemIndex]; - ObjectHelper *dotObj = cache->object(); + if (item.isVisible()) { + ShaderHelper *selectionShader; + if (drawingPoints) { + selectionShader = pointSelectionShader; + } else { + if (colorStyleIsUniform) + selectionShader = m_staticSelectedItemShader; + else + selectionShader = m_staticSelectedItemGradientShader; + } + selectionShader->bind(); - QMatrix4x4 modelMatrix; - QMatrix4x4 itModelMatrix; + ObjectHelper *dotObj = cache->object(); - modelMatrix.translate(item.translation()); - if (!drawingPoints) { - if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { - QQuaternion totalRotation = seriesRotation * item.rotation(); - modelMatrix.rotate(totalRotation); - itModelMatrix.rotate(totalRotation); + QMatrix4x4 modelMatrix; + QMatrix4x4 itModelMatrix; + + modelMatrix.translate(item.translation()); + if (!drawingPoints) { + if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { + QQuaternion totalRotation = seriesRotation * item.rotation(); + modelMatrix.rotate(totalRotation); + itModelMatrix.rotate(totalRotation); + } + modelMatrix.scale(modelScaler); + itModelMatrix.scale(modelScaler); + + selectionShader->setUniformValue(selectionShader->lightP(), + lightPos); + selectionShader->setUniformValue(selectionShader->view(), + viewMatrix); + selectionShader->setUniformValue(selectionShader->ambientS(), + m_cachedTheme->ambientLightStrength()); + selectionShader->setUniformValue(selectionShader->lightColor(), + lightColor); } - modelMatrix.scale(modelScaler); - itModelMatrix.scale(modelScaler); - } - QMatrix4x4 MVPMatrix; + QMatrix4x4 MVPMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else - MVPMatrix = projectionViewMatrix * modelMatrix; + MVPMatrix = projectionViewMatrix * modelMatrix; #endif - if (useColor) - dotColor = cache->singleHighlightColor(); - else - gradientTexture = cache->singleHighlightGradientTexture(); - GLfloat lightStrength = m_cachedTheme->highlightLightStrength(); - // Save the reference to the item to be used on label drawing - selectedItem = &item; - dotSelectionFound = true; - // Save selected item size (adjusted with font size) for selection label - // positioning - selectedItemSize = itemSize + (m_cachedTheme->font().pointSizeF() / 500.0f); - - if (!drawingPoints) { - // Set shader bindings - dotShader->setUniformValue(dotShader->model(), modelMatrix); - dotShader->setUniformValue(dotShader->nModel(), - itModelMatrix.inverted().transposed()); - } + if (useColor) + dotColor = cache->singleHighlightColor(); + else + gradientTexture = cache->singleHighlightGradientTexture(); + GLfloat lightStrength = m_cachedTheme->highlightLightStrength(); + // Save the reference to the item to be used in label drawing + selectedItem = &item; + dotSelectionFound = true; + // Save selected item size (adjusted with font size) for selection label + // positioning + selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f; - dotShader->setUniformValue(dotShader->MVP(), MVPMatrix); - if (useColor) { - dotShader->setUniformValue(dotShader->color(), dotColor); - } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) { - dotShader->setUniformValue(dotShader->gradientMin(), - (item.translation().y() + 1.0f) / 2.0f); - } + if (!drawingPoints) { + // Set shader bindings + selectionShader->setUniformValue(selectionShader->model(), modelMatrix); + selectionShader->setUniformValue(selectionShader->nModel(), + itModelMatrix.inverted().transposed()); + if (!colorStyleIsUniform) { + if (colorStyle == Q3DTheme::ColorStyleObjectGradient) { + selectionShader->setUniformValue(selectionShader->gradientMin(), + 0.0f); + selectionShader->setUniformValue(selectionShader->gradientHeight(), + 0.5f); + } else { + // Each dot is of uniform color according to its Y-coordinate + selectionShader->setUniformValue(selectionShader->gradientHeight(), + 0.0f); + selectionShader->setUniformValue(selectionShader->gradientMin(), + (item.translation().y() + m_scaleY) + * rangeGradientYScaler); + } + } + } - if (!drawingPoints) { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, 1.0f); - } + selectionShader->setUniformValue(selectionShader->MVP(), MVPMatrix); + if (useColor) + selectionShader->setUniformValue(selectionShader->color(), dotColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { if (!drawingPoints) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - dotShader->setUniformValue(dotShader->depth(), depthMVPMatrix); - dotShader->setUniformValue(dotShader->lightS(), lightStrength / 10.0f); - - // Draw the object - m_drawer->drawObject(dotShader, dotObj, gradientTexture, m_depthTexture); - } else { - // Draw the object - m_drawer->drawPoint(dotShader); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, 1.0f); } - } else -#endif - { - if (!drawingPoints) { - // Set shadowless shader bindings - dotShader->setUniformValue(dotShader->lightS(), lightStrength); - // Draw the object - m_drawer->drawObject(dotShader, dotObj, gradientTexture); + + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone + && !m_isOpenGLES) { + if (!drawingPoints) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + selectionShader->setUniformValue(selectionShader->shadowQ(), + m_shadowQualityToShader); + selectionShader->setUniformValue(selectionShader->depth(), + depthMVPMatrix); + selectionShader->setUniformValue(selectionShader->lightS(), + lightStrength / 10.0f); + + // Draw the object + m_drawer->drawObject(selectionShader, dotObj, gradientTexture, + m_depthTexture); + } else { + // Draw the object + m_drawer->drawPoint(selectionShader); + } } else { - // Draw the object - m_drawer->drawPoint(dotShader); + if (!drawingPoints) { + // Set shadowless shader bindings + selectionShader->setUniformValue(selectionShader->lightS(), + lightStrength); + // Draw the object + m_drawer->drawObject(selectionShader, dotObj, gradientTexture); + } else { + // Draw the object + m_drawer->drawPoint(selectionShader); + } } - } - if (!drawingPoints) - glDisable(GL_POLYGON_OFFSET_FILL); + if (!drawingPoints) + glDisable(GL_POLYGON_OFFSET_FILL); + } + dotShader->bind(); } } } @@ -987,25 +1199,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - QVector3D bgScale(xScale, 1.0f + m_backgroundMargin, zScale); -#else // ..and this if we want uniform scaling based on largest dimension - QVector3D bgScale((m_graphAspectRatio + m_backgroundMargin), - 1.0f + m_backgroundMargin, - (m_graphAspectRatio + m_backgroundMargin)); -#endif + QVector3D bgScale(m_scaleXWithBackground, m_scaleYWithBackground, + m_scaleZWithBackground); modelMatrix.scale(bgScale); // If we're viewing from below, background object must be flipped if (m_yFlipped) { - modelMatrix.rotate(180.0f, 1.0, 0.0, 0.0); + modelMatrix.rotate(m_xFlipRotation); modelMatrix.rotate(270.0f - backgroundRotation, 0.0f, 1.0f, 0.0f); } else { modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); @@ -1031,8 +1230,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength() * 2.0f); m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(), @@ -1043,9 +1241,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) // Draw the object m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#endif - { + } else { // Set shadowless shader bindings m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), m_cachedTheme->lightStrength()); @@ -1055,16 +1251,17 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - // Disable textures - glDisable(GL_TEXTURE_2D); - // Draw grid lines + QVector3D gridLineScaleX(m_scaleXWithBackground, gridLineWidth, gridLineWidth); + QVector3D gridLineScaleZ(gridLineWidth, gridLineWidth, m_scaleZWithBackground); + QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth); + if (m_cachedTheme->isGridEnabled()) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; // Bind line shader lineShader->bind(); @@ -1076,15 +1273,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadowed shader bindings lineShader->setUniformValue(lineShader->shadowQ(), m_shadowQualityToShader); lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 20.0f); - } else -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1094,242 +1288,213 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QQuaternion lineXRotation; if (m_xFlipped) - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineYRotation = m_yRightAngleRotationNeg; else - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineYRotation = m_yRightAngleRotation; - if (m_yFlipped) - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + if (m_yFlippedForGrid) + lineXRotation = m_xRightAngleRotation; else - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineXRotation = m_xRightAngleRotationNeg; - GLfloat yFloorLinePosition = -1.0f - m_backgroundMargin + gridLineOffset; - if (m_yFlipped) + GLfloat yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset; + if (m_yFlippedForGrid) yFloorLinePosition = -yFloorLinePosition; // Rows (= Z) if (m_axisCacheZ.segmentCount() > 0) { // Floor lines int gridLineCount = m_axisCacheZ.gridLineCount(); + if (m_polarGraph) { + drawRadialGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - QVector3D gridLineScaler(xScale, gridLineWidth, gridLineWidth); -#else // ..and this if we want uniform scaling based on largest dimension - QVector3D gridLineScaler((m_graphAspectRatio + m_backgroundMargin), - gridLineWidth, gridLineWidth); -#endif + modelMatrix.translate(0.0f, yFloorLinePosition, + m_axisCacheZ.gridLinePosition(line)); - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(0.0f, yFloorLinePosition, - m_axisCacheZ.gridLinePosition(line)); + modelMatrix.scale(gridLineScaleX); + itModelMatrix.scale(gridLineScaleX); - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); + MVPMatrix = projectionViewMatrix * modelMatrix; - MVPMatrix = projectionViewMatrix * modelMatrix; + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); - -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - // Set shadow shader bindings - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); + } else { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + // Set shadow shader bindings + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - // Side wall lines - gridLineScaler = QVector3D(gridLineWidth, 1.0f + m_backgroundMargin, gridLineWidth); -#ifndef USE_UNIFORM_SCALING - GLfloat lineXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineXTrans) - lineXTrans = m_maxItemSize - gridLineOffset; -#else - GLfloat lineXTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; -#endif - if (!m_xFlipped) - lineXTrans = -lineXTrans; + // Side wall lines + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; + if (!m_xFlipped) + lineXTrans = -lineXTrans; - modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); -#if !defined(QT_OPENGL_ES_2) - modelMatrix.rotate(lineYRotation); - itModelMatrix.rotate(lineYRotation); -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - MVPMatrix = projectionViewMatrix * modelMatrix; + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + modelMatrix.rotate(lineYRotation); + itModelMatrix.rotate(lineYRotation); + } - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } // Columns (= X) if (m_axisCacheX.segmentCount() > 0) { -#if defined(QT_OPENGL_ES_2) - lineXRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif + if (m_isOpenGLES) + lineXRotation = m_yRightAngleRotation; // Floor lines int gridLineCount = m_axisCacheX.gridLineCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - QVector3D gridLineScaler(gridLineWidth, gridLineWidth, zScale); -#else - QVector3D gridLineScaler(gridLineWidth, gridLineWidth, - m_graphAspectRatio + m_backgroundMargin); -#endif - - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, - 0.0f); + if (m_polarGraph) { + drawAngularGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, + 0.0f); - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); + modelMatrix.scale(gridLineScaleZ); + itModelMatrix.scale(gridLineScaleZ); - MVPMatrix = projectionViewMatrix * modelMatrix; + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - - // Back wall lines -#ifndef USE_UNIFORM_SCALING - GLfloat lineZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineZTrans) - lineZTrans = m_maxItemSize - gridLineOffset; -#else - GLfloat lineZTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; -#endif - if (!m_zFlipped) - lineZTrans = -lineZTrans; - gridLineScaler = QVector3D(gridLineWidth, 1.0f + m_backgroundMargin, gridLineWidth); + // Back wall lines + GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; + if (!m_zFlipped) + lineZTrans = -lineZTrans; - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); -#if !defined(QT_OPENGL_ES_2) - if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - } -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - MVPMatrix = projectionViewMatrix * modelMatrix; + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + if (m_zFlipped) { + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); + } + } - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } @@ -1338,21 +1503,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) // Back wall int gridLineCount = m_axisCacheY.gridLineCount(); -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat lineZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineZTrans) - lineZTrans = m_maxItemSize - gridLineOffset; - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - QVector3D gridLineScaler(xScale, gridLineWidth, gridLineWidth); -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat lineZTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; - QVector3D gridLineScaler((m_graphAspectRatio + m_backgroundMargin), - gridLineWidth, gridLineWidth); -#endif + GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; + if (!m_zFlipped) lineZTrans = -lineZTrans; @@ -1363,12 +1515,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) modelMatrix.translate(0.0f, m_axisCacheY.gridLinePosition(line), lineZTrans); - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.scale(gridLineScaleX); + itModelMatrix.scale(gridLineScaleX); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1379,38 +1531,25 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } // Side wall -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat lineXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineXTrans) - lineXTrans = m_maxItemSize - gridLineOffset; - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + m_backgroundMargin; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, zScale); -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat lineXTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, - m_graphAspectRatio + m_backgroundMargin); -#endif + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; + if (!m_xFlipped) lineXTrans = -lineXTrans; @@ -1421,8 +1560,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) modelMatrix.translate(lineXTrans, m_axisCacheY.gridLinePosition(line), 0.0f); - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.scale(gridLineScaleZ); + itModelMatrix.scale(gridLineScaleZ); modelMatrix.rotate(lineYRotation); itModelMatrix.rotate(lineYRotation); @@ -1435,20 +1574,20 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } } } @@ -1489,7 +1628,6 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) glEnable(GL_DEPTH_TEST); } - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); // Release shader @@ -1512,7 +1650,6 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1532,15 +1669,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa // Z Labels if (m_axisCacheZ.segmentCount() > 0) { int labelCount = m_axisCacheZ.labelCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat labelXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor + labelMargin + m_backgroundMargin; - if (m_maxItemSize > labelXTrans) - labelXTrans = m_maxItemSize + labelMargin; -#else - GLfloat labelXTrans = m_graphAspectRatio + m_backgroundMargin + labelMargin; -#endif - GLfloat labelYTrans = -1.0f - m_backgroundMargin; + float labelXTrans = m_scaleXWithBackground + labelMargin; + float labelYTrans = -m_scaleYWithBackground; + if (m_polarGraph) { + labelXTrans *= m_radialLabelOffset; + // YTrans up only if over background + if (m_radialLabelOffset < 1.0f) + labelYTrans += gridLineOffset + gridLineWidth; + } Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_xFlipped) @@ -1550,7 +1686,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa if (labelAutoAngle == 0.0f) { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) labelRotation.setY(180.0f); else @@ -1562,7 +1698,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX) @@ -1621,13 +1757,35 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { - labelTrans.setZ(m_axisCacheZ.labelPosition(label)); - glPolygonOffset(offsetValue++ / -10.0f, 1.0f); - + const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); // Draw the label here + if (m_polarGraph) { + float direction = m_zFlipped ? -1.0f : 1.0f; + labelTrans.setZ((m_axisCacheZ.formatter()->labelPositions().at(label) + * -m_polarRadius + + m_drawer->scaledFontSize() + gridLineWidth) * direction); + } else { + labelTrans.setZ(m_axisCacheZ.labelPosition(label)); + } + if (label == 0 || label == (labelCount - 1)) { + // If the margin is small, adjust the position of the edge labels to avoid overlapping + // with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelTrans.z()) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleZWithBackground + labelMargin; + // No need to adjust quite as much on the front edges + if (label != startIndex) + labelOverlap /= 2.0f; + if (labelOverlap > 0.0f) { + if (label == 0) + labelTrans.setZ(labelTrans.z() - labelOverlap); + else + labelTrans.setZ(labelTrans.z() + labelOverlap); + } + } m_dummyRenderItem.setTranslation(labelTrans); - const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); if (drawSelection) { QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f, @@ -1642,7 +1800,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width())); } if (!drawSelection && m_axisCacheZ.isTitleVisible()) { - labelTrans.setZ(0.0f); + if (m_polarGraph) { + float titleZ = -m_polarRadius / 2.0f; + if (m_zFlipped) + titleZ = -titleZ; + labelTrans.setZ(titleZ); + } else { + labelTrans.setZ(0.0f); + } drawAxisTitleZ(labelRotation, labelTrans, totalRotation, m_dummyRenderItem, activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader); } @@ -1656,15 +1821,13 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheX.labelCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat labelZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + labelMargin + m_backgroundMargin; - if (m_maxItemSize > labelZTrans) - labelZTrans = m_maxItemSize + labelMargin; -#else - GLfloat labelZTrans = m_graphAspectRatio + m_backgroundMargin + labelMargin; -#endif - GLfloat labelYTrans = -1.0f - m_backgroundMargin; + float labelZTrans = 0.0f; + float labelYTrans = -m_scaleYWithBackground; + if (m_polarGraph) + labelYTrans += gridLineOffset + gridLineWidth; + else + labelZTrans = m_scaleZWithBackground + labelMargin; + Qt::AlignmentFlag alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_zFlipped) @@ -1675,7 +1838,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation = QVector3D(-90.0f, 90.0f, 0.0f); if (m_xFlipped) labelRotation.setY(-90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_xFlipped) labelRotation.setY(-90.0f); else @@ -1687,7 +1850,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation.setY(-90.0f); else labelRotation.setY(90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX) @@ -1735,6 +1898,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } QQuaternion totalRotation = Utils::calculateRotation(labelRotation); + if (m_polarGraph) { + if ((!m_yFlippedForGrid && (m_zFlipped != m_xFlipped)) + || (m_yFlippedForGrid && (m_zFlipped == m_xFlipped))) { + totalRotation *= m_zRightAngleRotation; + } else { + totalRotation *= m_zRightAngleRotationNeg; + } + } QVector3D labelTrans = QVector3D(0.0f, labelYTrans, labelZTrans); if (m_xFlipped) { startIndex = labelCount - 1; @@ -1746,14 +1917,64 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa indexStep = 1; } float offsetValue = 0.0f; - for (int label = startIndex; label != endIndex; label = label + indexStep) { - labelTrans.setX(m_axisCacheX.labelPosition(label)); + bool showLastLabel = false; + QVector<float> &labelPositions = m_axisCacheX.formatter()->labelPositions(); + int lastLabelPosIndex = labelPositions.size() - 1; + if (labelPositions.size() + && (labelPositions.at(lastLabelPosIndex) != 1.0f || labelPositions.at(0) != 0.0f)) { + // Avoid overlapping first and last label if they would get on same position + showLastLabel = true; + } + for (int label = startIndex; label != endIndex; label = label + indexStep) { glPolygonOffset(offsetValue++ / -10.0f, 1.0f); - // Draw the label here - m_dummyRenderItem.setTranslation(labelTrans); + if (m_polarGraph) { + // Calculate angular position + if (label == lastLabelPosIndex && !showLastLabel) + continue; + float labelPosition = labelPositions.at(label); + qreal angle = labelPosition * M_PI * 2.0; + labelTrans.setX((m_polarRadius + labelMargin) * float(qSin(angle))); + labelTrans.setZ(-(m_polarRadius + labelMargin) * float(qCos(angle))); + // Alignment depends on label angular position, as well as flips + Qt::AlignmentFlag vAlignment = Qt::AlignCenter; + Qt::AlignmentFlag hAlignment = Qt::AlignCenter; + const float centerMargin = 0.005f; + if (labelPosition < 0.25f - centerMargin || labelPosition > 0.75f + centerMargin) + vAlignment = m_zFlipped ? Qt::AlignTop : Qt::AlignBottom; + else if (labelPosition > 0.25f + centerMargin && labelPosition < 0.75f - centerMargin) + vAlignment = m_zFlipped ? Qt::AlignBottom : Qt::AlignTop; + + if (labelPosition < 0.50f - centerMargin && labelPosition > centerMargin) + hAlignment = m_zFlipped ? Qt::AlignRight : Qt::AlignLeft; + else if (labelPosition < 1.0f - centerMargin && labelPosition > 0.5f + centerMargin) + hAlignment = m_zFlipped ? Qt::AlignLeft : Qt::AlignRight; + if (m_yFlippedForGrid && vAlignment != Qt::AlignCenter) + vAlignment = (vAlignment == Qt::AlignTop) ? Qt::AlignBottom : Qt::AlignTop; + alignment = Qt::AlignmentFlag(vAlignment | hAlignment); + } else { + labelTrans.setX(m_axisCacheX.labelPosition(label)); + } const LabelItem &axisLabelItem = *m_axisCacheX.labelItems().at(label); + if (label == 0 || label == (labelCount - 1)) { + // If the margin is small, adjust the position of the edge labels to avoid overlapping + // with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelTrans.x()) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleXWithBackground + labelMargin; + // No need to adjust quite as much on the front edges + if (label != startIndex) + labelOverlap /= 2.0f; + if (labelOverlap > 0.0f) { + if (label == 0) + labelTrans.setX(labelTrans.x() + labelOverlap); + else + labelTrans.setX(labelTrans.x() - labelOverlap); + } + } + m_dummyRenderItem.setTranslation(labelTrans); if (drawSelection) { QVector4D labelColor = QVector4D(0.0f, label / 255.0f, 0.0f, @@ -1769,8 +1990,20 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } if (!drawSelection && m_axisCacheX.isTitleVisible()) { labelTrans.setX(0.0f); + bool radial = false; + if (m_polarGraph) { + if (m_xFlipped == m_zFlipped) + totalRotation *= m_zRightAngleRotation; + else + totalRotation *= m_zRightAngleRotationNeg; + if (m_yFlippedForGrid) + totalRotation *= QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f); + labelTrans.setZ(-m_polarRadius); + radial = true; + } drawAxisTitleX(labelRotation, labelTrans, totalRotation, m_dummyRenderItem, - activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader); + activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader, + radial); } } @@ -1782,22 +2015,13 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheY.labelCount(); -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat labelXTrans = (m_graphAspectRatio* m_areaSize.width()) - / m_scaleFactor + m_backgroundMargin; - GLfloat labelZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + m_backgroundMargin; - if (m_maxItemSize > labelXTrans) - labelXTrans = m_maxItemSize; - if (m_maxItemSize > labelZTrans) - labelZTrans = m_maxItemSize; -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat labelXTrans = m_graphAspectRatio + m_backgroundMargin; - GLfloat labelZTrans = labelXTrans; -#endif + + float labelXTrans = m_scaleXWithBackground; + float labelZTrans = m_scaleZWithBackground; + // Back & side wall - GLfloat labelMarginXTrans = labelMargin; - GLfloat labelMarginZTrans = labelMargin; + float labelMarginXTrans = labelMargin; + float labelMarginZTrans = labelMargin; QVector3D backLabelRotation(0.0f, -90.0f, 0.0f); QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f); Qt::AlignmentFlag backAlignment = @@ -1854,7 +2078,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label); - const GLfloat labelYTrans = m_axisCacheY.labelPosition(label); + float labelYTrans = m_axisCacheY.labelPosition(label); glPolygonOffset(offsetValue++ / -10.0f, 1.0f); @@ -1864,6 +2088,21 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa shader->setUniformValue(shader->color(), labelColor); } + if (label == startIndex) { + // If the margin is small, adjust the position of the edge label to avoid + // overlapping with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelYTrans) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleYWithBackground + labelMargin; + if (labelOverlap > 0.0f) { + if (label == 0) + labelYTrans += labelOverlap; + else + labelYTrans -= labelOverlap; + } + } + // Back wall labelTransBack.setY(labelYTrans); m_dummyRenderItem.setTranslation(labelTransBack); @@ -1958,10 +2197,8 @@ void Scatter3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality qual handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif } void Scatter3DRenderer::loadBackgroundMesh() @@ -1972,8 +2209,13 @@ void Scatter3DRenderer::loadBackgroundMesh() void Scatter3DRenderer::updateTextures() { + Abstract3DRenderer::updateTextures(); + // Drawer has changed; this flag needs to be checked when checking if we need to update labels m_updateLabels = true; + + if (m_polarGraph) + calculateSceneScalingFactors(); } void Scatter3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh) @@ -1991,35 +2233,83 @@ void Scatter3DRenderer::calculateTranslation(ScatterRenderItem &item) { // We need to normalize translations const QVector3D &pos = item.position(); - float xTrans = m_axisCacheX.positionAt(pos.x()); + float xTrans; float yTrans = m_axisCacheY.positionAt(pos.y()); - float zTrans = m_axisCacheZ.positionAt(pos.z()); + float zTrans; + if (m_polarGraph) { + calculatePolarXZ(pos, xTrans, zTrans); + } else { + xTrans = m_axisCacheX.positionAt(pos.x()); + zTrans = m_axisCacheZ.positionAt(pos.z()); + } item.setTranslation(QVector3D(xTrans, yTrans, zTrans)); } void Scatter3DRenderer::calculateSceneScalingFactors() { - m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min()) / 2.0f; - m_areaSize.setHeight((m_axisCacheZ.max() - m_axisCacheZ.min()) / 2.0f); - m_areaSize.setWidth((m_axisCacheX.max() - m_axisCacheX.min()) / 2.0f); - m_scaleFactor = qMax(m_areaSize.width(), m_areaSize.height()); - -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - float factorScaler = 2.0f * m_graphAspectRatio / m_scaleFactor; - m_axisCacheX.setScale(factorScaler * m_areaSize.width()); - m_axisCacheZ.setScale(-factorScaler * m_areaSize.height()); -#else // ..and this if we want uniform scaling based on largest dimension - m_axisCacheX.setScale(2.0f * m_graphAspectRatio); - m_axisCacheZ.setScale(-m_axisCacheX.scale()); -#endif - m_axisCacheX.setTranslate(-m_axisCacheX.scale() / 2.0f); - m_axisCacheZ.setTranslate(-m_axisCacheZ.scale() / 2.0f); + if (m_requestedMargin < 0.0f) { + if (m_maxItemSize > defaultMaxSize) + m_hBackgroundMargin = m_maxItemSize / itemScaler; + else + m_hBackgroundMargin = defaultMaxSize; + m_vBackgroundMargin = m_hBackgroundMargin; + } else { + m_hBackgroundMargin = m_requestedMargin; + m_vBackgroundMargin = m_requestedMargin; + } + if (m_polarGraph) { + float polarMargin = calculatePolarBackgroundMargin(); + m_hBackgroundMargin = qMax(m_hBackgroundMargin, polarMargin); + } + + float horizontalAspectRatio; + if (m_polarGraph) + horizontalAspectRatio = 1.0f; + else + horizontalAspectRatio = m_graphHorizontalAspectRatio; + + QSizeF areaSize; + if (horizontalAspectRatio == 0.0f) { + areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min()); + areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min()); + } else { + areaSize.setHeight(1.0f); + areaSize.setWidth(horizontalAspectRatio); + } + + float horizontalMaxDimension; + if (m_graphAspectRatio > 2.0f) { + horizontalMaxDimension = 2.0f; + m_scaleY = 2.0f / m_graphAspectRatio; + } else { + horizontalMaxDimension = m_graphAspectRatio; + m_scaleY = 1.0f; + } + if (m_polarGraph) + m_polarRadius = horizontalMaxDimension; + + float scaleFactor = qMax(areaSize.width(), areaSize.height()); + m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor; + m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor; + + m_scaleXWithBackground = m_scaleX + m_hBackgroundMargin; + m_scaleYWithBackground = m_scaleY + m_vBackgroundMargin; + m_scaleZWithBackground = m_scaleZ + m_hBackgroundMargin; + + m_axisCacheX.setScale(m_scaleX * 2.0f); + m_axisCacheY.setScale(m_scaleY * 2.0f); + m_axisCacheZ.setScale(-m_scaleZ * 2.0f); + m_axisCacheX.setTranslate(-m_scaleX); + m_axisCacheY.setTranslate(-m_scaleY); + m_axisCacheZ.setTranslate(m_scaleZ); + + updateCameraViewport(); + updateCustomItemPositions(); } void Scatter3DRenderer::initShaders(const QString &vertexShader, const QString &fragmentShader) { - if (m_dotShader) - delete m_dotShader; + delete m_dotShader; m_dotShader = new ShaderHelper(this, vertexShader, fragmentShader); m_dotShader->initialize(); } @@ -2027,16 +2317,30 @@ void Scatter3DRenderer::initShaders(const QString &vertexShader, const QString & void Scatter3DRenderer::initGradientShaders(const QString &vertexShader, const QString &fragmentShader) { - if (m_dotGradientShader) - delete m_dotGradientShader; + delete m_dotGradientShader; m_dotGradientShader = new ShaderHelper(this, vertexShader, fragmentShader); m_dotGradientShader->initialize(); + +} + +void Scatter3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader) +{ + delete m_staticSelectedItemShader; + m_staticSelectedItemShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_staticSelectedItemShader->initialize(); + + delete m_staticSelectedItemGradientShader; + m_staticSelectedItemGradientShader = new ShaderHelper(this, gradientVertexShader, + gradientFragmentShader); + m_staticSelectedItemGradientShader->initialize(); } void Scatter3DRenderer::initSelectionShader() { - if (m_selectionShader) - delete m_selectionShader; + delete m_selectionShader; m_selectionShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPlainColor"), QStringLiteral(":/shaders/fragmentPlainColor")); m_selectionShader->initialize(); @@ -2054,41 +2358,45 @@ void Scatter3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -#if !defined(QT_OPENGL_ES_2) void Scatter3DRenderer::initDepthShader() { - if (m_depthShader) - delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + if (!m_isOpenGLES) { + if (m_depthShader) + delete m_depthShader; + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Scatter3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + if (m_primarySubViewport.size().isEmpty()) + return; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), - m_depthFrameBuffer, - m_shadowQualityMultiplier); - if (!m_depthTexture) - lowerShadowQuality(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#else + void Scatter3DRenderer::initPointShader() { - if (m_pointShader) - delete m_pointShader; - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPointES2"), - QStringLiteral(":/shaders/fragmentPlainColor")); - m_pointShader->initialize(); + if (m_isOpenGLES) { + if (m_pointShader) + delete m_pointShader; + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPointES2"), + QStringLiteral(":/shaders/fragmentPlainColor")); + m_pointShader->initialize(); + } } -#endif void Scatter3DRenderer::initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) @@ -2099,12 +2407,13 @@ void Scatter3DRenderer::initBackgroundShaders(const QString &vertexShader, m_backgroundShader->initialize(); } -void Scatter3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) +void Scatter3DRenderer::initStaticPointShaders(const QString &vertexShader, + const QString &fragmentShader) { - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); + if (m_staticGradientPointShader) + delete m_staticGradientPointShader; + m_staticGradientPointShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_staticGradientPointShader->initialize(); } void Scatter3DRenderer::selectionColorToSeriesAndIndex(const QVector4D &color, @@ -2190,13 +2499,17 @@ QVector3D Scatter3DRenderer::convertPositionToTranslation(const QVector3D &posit float yTrans = 0.0f; float zTrans = 0.0f; if (!isAbsolute) { - xTrans = m_axisCacheX.positionAt(position.x()); + if (m_polarGraph) { + calculatePolarXZ(position, xTrans, zTrans); + } else { + xTrans = m_axisCacheX.positionAt(position.x()); + zTrans = m_axisCacheZ.positionAt(position.z()); + } yTrans = m_axisCacheY.positionAt(position.y()); - zTrans = m_axisCacheZ.positionAt(position.z()); } else { - xTrans = position.x() * m_axisCacheX.scale() / 2.0f; - yTrans = position.y(); - zTrans = position.z() * m_axisCacheZ.scale() / 2.0f; + xTrans = position.x() * m_scaleX; + yTrans = position.y() * m_scaleY; + zTrans = position.z() * -m_scaleZ; } return QVector3D(xTrans, yTrans, zTrans); } diff --git a/src/datavisualization/engine/scatter3drenderer_p.h b/src/datavisualization/engine/scatter3drenderer_p.h index 7f213179..b45b31a2 100644 --- a/src/datavisualization/engine/scatter3drenderer_p.h +++ b/src/datavisualization/engine/scatter3drenderer_p.h @@ -53,31 +53,28 @@ private: bool m_updateLabels; ShaderHelper *m_dotShader; ShaderHelper *m_dotGradientShader; -#if defined(QT_OPENGL_ES_2) + ShaderHelper *m_staticSelectedItemGradientShader; + ShaderHelper *m_staticSelectedItemShader; ShaderHelper *m_pointShader; -#endif ShaderHelper *m_depthShader; ShaderHelper *m_selectionShader; ShaderHelper *m_backgroundShader; - ShaderHelper *m_labelShader; + ShaderHelper *m_staticGradientPointShader; GLuint m_bgrTexture; - GLuint m_depthTexture; GLuint m_selectionTexture; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; GLuint m_selectionDepthBuffer; GLfloat m_shadowQualityToShader; GLint m_shadowQualityMultiplier; - GLfloat m_heightNormalizer; - GLfloat m_scaleFactor; + float m_scaleX; + float m_scaleY; + float m_scaleZ; int m_selectedItemIndex; ScatterSeriesRenderCache *m_selectedSeriesCache; ScatterSeriesRenderCache *m_oldSelectedSeriesCache; - QSizeF m_areaSize; GLfloat m_dotSizeScale; - bool m_hasHeightAdjustmentChanged; ScatterRenderItem m_dummyRenderItem; - GLfloat m_backgroundMargin; GLfloat m_maxItemSize; int m_clickedIndex; bool m_havePointSeries; @@ -94,6 +91,12 @@ public: SeriesRenderCache *createNewCache(QAbstract3DSeries *series); void updateItems(const QVector<Scatter3DController::ChangeItem> &items); void updateScene(Q3DScene *scene); + void updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels); + void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible); + void updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint); + void updateMargin(float margin); QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); @@ -107,10 +110,16 @@ public slots: protected: virtual void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); private: virtual void initShaders(const QString &vertexShader, const QString &fragmentShader); virtual void initGradientShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader); virtual void updateShadowQuality(QAbstract3DGraph::ShadowQuality quality); virtual void updateTextures(); virtual void fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh); @@ -122,14 +131,11 @@ private: void loadBackgroundMesh(); void initSelectionShader(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); + void initStaticPointShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) void initDepthShader(); void updateDepthBuffer(); -#else void initPointShader(); -#endif void calculateTranslation(ScatterRenderItem &item); void calculateSceneScalingFactors(); diff --git a/src/datavisualization/engine/scatterseriesrendercache.cpp b/src/datavisualization/engine/scatterseriesrendercache.cpp index e8888d19..4930dde1 100644 --- a/src/datavisualization/engine/scatterseriesrendercache.cpp +++ b/src/datavisualization/engine/scatterseriesrendercache.cpp @@ -27,10 +27,12 @@ ScatterSeriesRenderCache::ScatterSeriesRenderCache(QAbstract3DSeries *series, : SeriesRenderCache(series, renderer), m_itemSize(0.0f), m_selectionIndexOffset(0), + m_staticBufferDirty(false), m_oldRenderArraySize(0), m_oldMeshFileName(QString()), m_scatterBufferObj(0), - m_scatterBufferPoints(0) + m_scatterBufferPoints(0), + m_visibilityChanged(false) { } diff --git a/src/datavisualization/engine/scatterseriesrendercache_p.h b/src/datavisualization/engine/scatterseriesrendercache_p.h index 490e21fb..9c6e8e8f 100644 --- a/src/datavisualization/engine/scatterseriesrendercache_p.h +++ b/src/datavisualization/engine/scatterseriesrendercache_p.h @@ -53,6 +53,8 @@ public: inline float itemSize() const { return m_itemSize; } inline void setSelectionIndexOffset(int offset) { m_selectionIndexOffset = offset; } inline int selectionIndexOffset() const { return m_selectionIndexOffset; } + inline void setStaticBufferDirty(bool state) { m_staticBufferDirty = state; } + inline bool staticBufferDirty() const { return m_staticBufferDirty; } inline int oldArraySize() const { return m_oldRenderArraySize; } inline void setOldArraySize(int size) { m_oldRenderArraySize = size; } inline const QString &oldMeshFileName() const { return m_oldMeshFileName; } @@ -61,15 +63,23 @@ public: inline ScatterObjectBufferHelper *bufferObject() const { return m_scatterBufferObj; } inline void setBufferPoints(ScatterPointBufferHelper *object) { m_scatterBufferPoints = object; } inline ScatterPointBufferHelper *bufferPoints() const { return m_scatterBufferPoints; } + inline QVector<int> &updateIndices() { return m_updateIndices; } + inline QVector<int> &bufferIndices() { return m_bufferIndices; } + inline void setVisibilityChanged(bool changed) { m_visibilityChanged = changed; } + inline bool visibilityChanged() const { return m_visibilityChanged; } protected: ScatterRenderItemArray m_renderArray; float m_itemSize; int m_selectionIndexOffset; // Temporarily cached value for selection color calculations + bool m_staticBufferDirty; int m_oldRenderArraySize; // Used to detect if full buffer change needed QString m_oldMeshFileName; // Used to detect if full buffer change needed ScatterObjectBufferHelper *m_scatterBufferObj; ScatterPointBufferHelper *m_scatterBufferPoints; + QVector<int> m_updateIndices; // Used as temporary cache during item updates + QVector<int> m_bufferIndices; // Cache for mapping renderarray to mesh buffer + bool m_visibilityChanged; // Used to detect if full buffer change needed }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/selectionpointer.cpp b/src/datavisualization/engine/selectionpointer.cpp index 183d3f8e..b57ec511 100644 --- a/src/datavisualization/engine/selectionpointer.cpp +++ b/src/datavisualization/engine/selectionpointer.cpp @@ -122,9 +122,6 @@ void SelectionPointer::render(GLuint defaultFboHandle, bool useOrtho) MVPMatrix = projectionMatrix * viewMatrix * modelMatrix; - // Enable texturing - glEnable(GL_TEXTURE_2D); - QVector3D lightPos = m_cachedScene->activeLight()->position(); // @@ -186,9 +183,6 @@ void SelectionPointer::render(GLuint defaultFboHandle, bool useOrtho) // Release shader glUseProgram(0); - // Disable textures - glDisable(GL_TEXTURE_2D); - // Disable transparency glDisable(GL_BLEND); @@ -217,10 +211,12 @@ void SelectionPointer::setRotation(const QQuaternion &rotation) m_rotation = rotation; } -void SelectionPointer::setLabel(const QString &label) +void SelectionPointer::setLabel(const QString &label, bool themeChange) { - m_label = label; - m_drawer->generateLabelItem(m_labelItem, m_label); + if (themeChange || m_label != label) { + m_label = label; + m_drawer->generateLabelItem(m_labelItem, m_label); + } } void SelectionPointer::setPointerObject(ObjectHelper *object) @@ -236,7 +232,7 @@ void SelectionPointer::setLabelObject(ObjectHelper *object) void SelectionPointer::handleDrawerChange() { m_cachedTheme = m_drawer->theme(); - setLabel(m_label); + setLabel(m_label, true); } void SelectionPointer::updateBoundingRect(const QRect &rect) @@ -256,15 +252,16 @@ void SelectionPointer::initShaders() // The shader for the small point ball if (m_pointShader) delete m_pointShader; -#if !defined(QT_OPENGL_ES_2) - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragment")); -#else - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentES2")); -#endif - m_pointShader->initialize(); + if (Utils::isOpenGLES()) { + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentES2")); + } else { + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragment")); + } + + m_pointShader->initialize(); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/selectionpointer_p.h b/src/datavisualization/engine/selectionpointer_p.h index 7dc28024..08382b9f 100644 --- a/src/datavisualization/engine/selectionpointer_p.h +++ b/src/datavisualization/engine/selectionpointer_p.h @@ -49,7 +49,7 @@ public: void render(GLuint defaultFboHandle = 0, bool useOrtho = false); void setPosition(const QVector3D &position); - void setLabel(const QString &label); + void setLabel(const QString &label, bool themeChange = false); void setPointerObject(ObjectHelper *object); void setLabelObject(ObjectHelper *object); void handleDrawerChange(); diff --git a/src/datavisualization/engine/seriesrendercache.cpp b/src/datavisualization/engine/seriesrendercache.cpp index dc4b9db3..77af31c7 100644 --- a/src/datavisualization/engine/seriesrendercache.cpp +++ b/src/datavisualization/engine/seriesrendercache.cpp @@ -37,7 +37,8 @@ SeriesRenderCache::SeriesRenderCache(QAbstract3DSeries *series, Abstract3DRender m_valid(false), m_visible(false), m_renderer(renderer), - m_objectDirty(true) + m_objectDirty(true), + m_staticObjectUVDirty(false) { } @@ -91,9 +92,8 @@ void SeriesRenderCache::populate(bool newSeries) meshFileName = QStringLiteral(":/defaultMeshes/arrow"); break; case QAbstract3DSeries::MeshPoint: -#if defined(QT_OPENGL_ES_2) - qWarning("QAbstract3DSeries::MeshPoint is not fully supported on OpenGL ES2"); -#endif + if (Utils::isOpenGLES()) + qWarning("QAbstract3DSeries::MeshPoint is not fully supported on OpenGL ES2"); break; default: // Default to cube diff --git a/src/datavisualization/engine/seriesrendercache_p.h b/src/datavisualization/engine/seriesrendercache_p.h index 96b61b87..5047d671 100644 --- a/src/datavisualization/engine/seriesrendercache_p.h +++ b/src/datavisualization/engine/seriesrendercache_p.h @@ -71,6 +71,8 @@ public: inline bool isVisible() const { return m_visible; } inline void setDataDirty(bool state) { m_objectDirty = state; } inline bool dataDirty() const { return m_objectDirty; } + inline void setStaticObjectUVDirty(bool state) { m_staticObjectUVDirty = state; } + inline bool staticObjectUVDirty() { return m_staticObjectUVDirty; } protected: QAbstract3DSeries *m_series; @@ -94,6 +96,7 @@ protected: bool m_visible; Abstract3DRenderer *m_renderer; bool m_objectDirty; + bool m_staticObjectUVDirty; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/shaders/3dsliceframes.frag b/src/datavisualization/engine/shaders/3dsliceframes.frag new file mode 100644 index 00000000..44c080dc --- /dev/null +++ b/src/datavisualization/engine/shaders/3dsliceframes.frag @@ -0,0 +1,15 @@ +#version 120 + +uniform highp vec4 color_mdl; +uniform highp vec2 sliceFrameWidth; + +varying highp vec3 pos; + +void main() { + highp vec2 absPos = min(vec2(1.0, 1.0), abs(pos.xy)); + if (absPos.x > sliceFrameWidth.x || absPos.y > sliceFrameWidth.y) + gl_FragColor = color_mdl; + else + discard; +} + diff --git a/src/datavisualization/engine/shaders/default.frag b/src/datavisualization/engine/shaders/default.frag index d16055a3..0276ed95 100644 --- a/src/datavisualization/engine/shaders/default.frag +++ b/src/datavisualization/engine/shaders/default.frag @@ -32,6 +32,7 @@ void main() { materialAmbientColor + materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance + materialSpecularColor * lightStrength * pow(cosAlpha, 5) / distance; - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; + gl_FragColor = clamp(gl_FragColor, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/defaultNoMatrices.vert b/src/datavisualization/engine/shaders/defaultNoMatrices.vert new file mode 100644 index 00000000..cef10c19 --- /dev/null +++ b/src/datavisualization/engine/shaders/defaultNoMatrices.vert @@ -0,0 +1,26 @@ +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; +attribute highp vec3 vertexNormal_mdl; + +uniform highp mat4 MVP; +uniform highp mat4 V; +uniform highp vec3 lightPosition_wrld; + +varying highp vec3 lightPosition_wrld_frag; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 coords_mdl; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + coords_mdl = vertexPosition_mdl.xy; + position_wrld = vertexPosition_mdl; + vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz; + eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; + vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; + lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; + normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz; + lightPosition_wrld_frag = lightPosition_wrld; +} diff --git a/src/datavisualization/engine/shaders/default_ES2.frag b/src/datavisualization/engine/shaders/default_ES2.frag index 73d66d5b..60fa3c43 100644 --- a/src/datavisualization/engine/shaders/default_ES2.frag +++ b/src/datavisualization/engine/shaders/default_ES2.frag @@ -34,6 +34,6 @@ void main() { materialAmbientColor + materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance + materialSpecularColor * lightStrength * (cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha) / distance; - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; } diff --git a/src/datavisualization/engine/shaders/point_ES2_UV.vert b/src/datavisualization/engine/shaders/point_ES2_UV.vert new file mode 100644 index 00000000..f181db4f --- /dev/null +++ b/src/datavisualization/engine/shaders/point_ES2_UV.vert @@ -0,0 +1,12 @@ +uniform highp mat4 MVP; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; + +void main() { + gl_PointSize = 5.0; + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + UV = vertexUV; +} diff --git a/src/datavisualization/engine/shaders/position.vert b/src/datavisualization/engine/shaders/position.vert new file mode 100644 index 00000000..34849eae --- /dev/null +++ b/src/datavisualization/engine/shaders/position.vert @@ -0,0 +1,10 @@ +uniform highp mat4 MVP; + +attribute highp vec3 vertexPosition_mdl; + +varying highp vec3 pos; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + pos = vertexPosition_mdl; +} diff --git a/src/datavisualization/engine/shaders/positionmap.frag b/src/datavisualization/engine/shaders/positionmap.frag new file mode 100644 index 00000000..9a277ab8 --- /dev/null +++ b/src/datavisualization/engine/shaders/positionmap.frag @@ -0,0 +1,11 @@ +varying highp vec3 pos; + +void main() { + // This shader encodes the axis position into the vertex color, assuming the object + // is a cube filling the entire data area of the graph + gl_FragColor = vec4((pos.x + 1.0) / 2.0, + (pos.y + 1.0) / 2.0, + (-pos.z + 1.0) / 2.0, + 0.0); +} + diff --git a/src/datavisualization/engine/shaders/shadowNoMatrices.vert b/src/datavisualization/engine/shaders/shadowNoMatrices.vert new file mode 100644 index 00000000..31748503 --- /dev/null +++ b/src/datavisualization/engine/shaders/shadowNoMatrices.vert @@ -0,0 +1,36 @@ +#version 120 + +uniform highp mat4 MVP; +uniform highp mat4 V; +uniform highp mat4 M; +uniform highp mat4 depthMVP; +uniform highp vec3 lightPosition_wrld; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; +varying highp vec2 coords_mdl; + +const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + coords_mdl = vertexPosition_mdl.xy; + shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0); + position_wrld = vertexPosition_mdl; + vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz; + eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; + lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz; + normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; +} diff --git a/src/datavisualization/engine/shaders/shadowNoTex.frag b/src/datavisualization/engine/shaders/shadowNoTex.frag index b2e7adfc..84e2f209 100644 --- a/src/datavisualization/engine/shaders/shadowNoTex.frag +++ b/src/datavisualization/engine/shaders/shadowNoTex.frag @@ -61,6 +61,6 @@ void main() { (materialAmbientColor + materialDiffuseColor * lightStrength * cosTheta + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/surface.frag b/src/datavisualization/engine/shaders/surface.frag index f17dd73e..238e5fed 100644 --- a/src/datavisualization/engine/shaders/surface.frag +++ b/src/datavisualization/engine/shaders/surface.frag @@ -11,9 +11,11 @@ uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/surfaceFlat.frag b/src/datavisualization/engine/shaders/surfaceFlat.frag index 748fb3dd..1a0bbdeb 100644 --- a/src/datavisualization/engine/shaders/surfaceFlat.frag +++ b/src/datavisualization/engine/shaders/surfaceFlat.frag @@ -13,9 +13,11 @@ uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/surfaceFlat.vert b/src/datavisualization/engine/shaders/surfaceFlat.vert index 102bea78..0f953f4c 100644 --- a/src/datavisualization/engine/shaders/surfaceFlat.vert +++ b/src/datavisualization/engine/shaders/surfaceFlat.vert @@ -4,6 +4,7 @@ attribute highp vec3 vertexPosition_mdl; attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; uniform highp mat4 MVP; uniform highp mat4 V; @@ -11,6 +12,7 @@ uniform highp mat4 M; uniform highp mat4 itM; uniform highp vec3 lightPosition_wrld; +varying highp vec2 UV; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; @@ -26,4 +28,5 @@ void main() { vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; } diff --git a/src/datavisualization/engine/shaders/surfaceShadowFlat.frag b/src/datavisualization/engine/shaders/surfaceShadowFlat.frag index 0613a40c..7eaba7f5 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowFlat.frag +++ b/src/datavisualization/engine/shaders/surfaceShadowFlat.frag @@ -2,20 +2,22 @@ #extension GL_EXT_gpu_shader4 : require -varying highp vec3 coords_mdl; +varying highp vec2 coords_mdl; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; uniform highp sampler2DShadow shadowMap; uniform sampler2D textureSampler; -varying highp vec4 shadowCoord; uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp float shadowQuality; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725), @@ -35,7 +37,7 @@ highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.14383161, -0.14100790)); void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; @@ -49,7 +51,7 @@ void main() { highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); highp float bias = 0.005 * tan(acos(cosTheta)); - bias = clamp(bias, 0.0, 0.01); + bias = clamp(bias, 0.001, 0.01); vec4 shadCoords = shadowCoord; shadCoords.z -= bias; diff --git a/src/datavisualization/engine/shaders/surfaceShadowFlat.vert b/src/datavisualization/engine/shaders/surfaceShadowFlat.vert index 8da7b196..98fdde3f 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowFlat.vert +++ b/src/datavisualization/engine/shaders/surfaceShadowFlat.vert @@ -2,9 +2,6 @@ #extension GL_EXT_gpu_shader4 : require -attribute highp vec3 vertexPosition_mdl; -attribute highp vec3 vertexNormal_mdl; - uniform highp mat4 MVP; uniform highp mat4 V; uniform highp mat4 M; @@ -12,12 +9,17 @@ uniform highp mat4 itM; uniform highp mat4 depthMVP; uniform highp vec3 lightPosition_wrld; +attribute highp vec3 vertexPosition_mdl; +attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; varying highp vec4 shadowCoord; -varying highp vec3 coords_mdl; +varying highp vec2 coords_mdl; const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, @@ -26,12 +28,12 @@ const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, void main() { gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); - coords_mdl = vertexPosition_mdl; + coords_mdl = vertexPosition_mdl.xy; shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0); position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz; vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz; eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; - vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; - lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; + lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz; normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; } diff --git a/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag b/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag index 1acf8f69..985214be 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag +++ b/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag @@ -5,15 +5,17 @@ varying highp vec3 position_wrld; varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; uniform highp sampler2DShadow shadowMap; uniform sampler2D textureSampler; -varying highp vec4 shadowCoord; uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp float shadowQuality; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725), @@ -33,7 +35,7 @@ highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.14383161, -0.14100790)); void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; @@ -47,7 +49,7 @@ void main() { highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); highp float bias = 0.005 * tan(acos(cosTheta)); - bias = clamp(bias, 0.0, 0.01); + bias = clamp(bias, 0.001, 0.01); vec4 shadCoords = shadowCoord; shadCoords.z -= bias; diff --git a/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag b/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag new file mode 100644 index 00000000..7c654e0c --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag @@ -0,0 +1,39 @@ +#version 120 + +#extension GL_EXT_gpu_shader4 : require + +varying highp vec3 coords_mdl; +varying highp vec3 position_wrld; +flat varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 UV; + +uniform sampler2D textureSampler; +uniform highp vec3 lightPosition_wrld; +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp vec4 lightColor; + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb; + + highp float distance = length(lightPosition_wrld - position_wrld); + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + gl_FragColor.rgb = + materialAmbientColor + + materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance + + materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance; + gl_FragColor.a = 1.0; +} + diff --git a/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag b/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag new file mode 100644 index 00000000..a4259565 --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag @@ -0,0 +1,67 @@ +#version 120 + +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp float shadowQuality; +uniform highp sampler2D textureSampler; +uniform highp sampler2DShadow shadowMap; +uniform highp vec4 lightColor; + +varying highp vec4 shadowCoord; +varying highp vec2 UV; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; + +highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), + vec2(0.94558609, -0.76890725), + vec2(-0.094184101, -0.92938870), + vec2(0.34495938, 0.29387760), + vec2(-0.91588581, 0.45771432), + vec2(-0.81544232, -0.87912464), + vec2(-0.38277543, 0.27676845), + vec2(0.97484398, 0.75648379), + vec2(0.44323325, -0.97511554), + vec2(0.53742981, -0.47373420), + vec2(-0.26496911, -0.41893023), + vec2(0.79197514, 0.19090188), + vec2(-0.24188840, 0.99706507), + vec2(-0.81409955, 0.91437590), + vec2(0.19984126, 0.78641367), + vec2(0.14383161, -0.14100790)); + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb * 0.2; + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + highp float bias = 0.005 * tan(acos(cosTheta)); + bias = clamp(bias, 0.001, 0.01); + + vec4 shadCoords = shadowCoord; + shadCoords.z -= bias; + + highp float visibility = 0.6; + for (int i = 0; i < 15; i++) { + vec4 shadCoordsPD = shadCoords; + shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality; + shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality; + visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r; + } + + gl_FragColor.rgb = + (materialAmbientColor + + materialDiffuseColor * lightStrength * cosTheta + + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); + gl_FragColor.a = texture2D(textureSampler, UV).a; + gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag b/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag new file mode 100644 index 00000000..496f4777 --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag @@ -0,0 +1,72 @@ +#version 120 + +#extension GL_EXT_gpu_shader4 : require + +varying highp vec3 coords_mdl; +varying highp vec3 position_wrld; +flat varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 UV; + +uniform highp sampler2DShadow shadowMap; +uniform sampler2D textureSampler; +varying highp vec4 shadowCoord; +uniform highp vec3 lightPosition_wrld; +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp float shadowQuality; +uniform highp vec4 lightColor; + +highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), + vec2(0.94558609, -0.76890725), + vec2(-0.094184101, -0.92938870), + vec2(0.34495938, 0.29387760), + vec2(-0.91588581, 0.45771432), + vec2(-0.81544232, -0.87912464), + vec2(-0.38277543, 0.27676845), + vec2(0.97484398, 0.75648379), + vec2(0.44323325, -0.97511554), + vec2(0.53742981, -0.47373420), + vec2(-0.26496911, -0.41893023), + vec2(0.79197514, 0.19090188), + vec2(-0.24188840, 0.99706507), + vec2(-0.81409955, 0.91437590), + vec2(0.19984126, 0.78641367), + vec2(0.14383161, -0.14100790)); + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb; + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + highp float bias = 0.005 * tan(acos(cosTheta)); + bias = clamp(bias, 0.001, 0.01); + + vec4 shadCoords = shadowCoord; + shadCoords.z -= bias; + + highp float visibility = 0.6; + for (int i = 0; i < 15; i++) { + vec4 shadCoordsPD = shadCoords; + shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality; + shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality; + visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r; + } + + gl_FragColor.rgb = + (materialAmbientColor + + materialDiffuseColor * lightStrength * cosTheta + + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); + gl_FragColor.a = 1.0; + gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); +} + diff --git a/src/datavisualization/engine/shaders/surface_ES2.frag b/src/datavisualization/engine/shaders/surface_ES2.frag index 58d13834..1d1bdc3e 100644 --- a/src/datavisualization/engine/shaders/surface_ES2.frag +++ b/src/datavisualization/engine/shaders/surface_ES2.frag @@ -10,9 +10,11 @@ uniform sampler2D textureSampler; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/texture3d.frag b/src/datavisualization/engine/shaders/texture3d.frag new file mode 100644 index 00000000..3f9c42ff --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.frag @@ -0,0 +1,155 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp vec3 textureDimensions; +uniform highp int sampleCount; // This is the maximum sample count +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha. +// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over +// entire volume, regardless of texture dimensions +const highp float alphaThicknesses = 32.0; + +void main() { + vec3 rayStart = pos; + + highp vec3 startBounds = minBounds; + highp vec3 endBounds = maxBounds; + if (rayDir.x < 0.0) { + startBounds.x = maxBounds.x; + endBounds.x = minBounds.x; + } + if (rayDir.y > 0.0) { + startBounds.y = maxBounds.y; + endBounds.y = minBounds.y; + } + if (rayDir.z > 0.0) { + startBounds.z = maxBounds.z; + endBounds.z = minBounds.z; + } + + // Calculate ray intersection endpoint + highp vec3 rayStop; + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 t = (endBounds - rayStart) * invRayDir; + highp float endT = min(t.x, min(t.y, t.z)); + rayStop = rayStart + endT * rayDir; + if (endT <= 0.0) + discard; + + // Convert intersections to texture coords + rayStart = 0.5 * (rayStart + 1.0); + rayStop = 0.5 * (rayStop + 1.0); + + highp vec3 ray = rayStop - rayStart; + + highp vec3 absRay = abs(ray); + highp vec3 invAbsRay = 1.0 / absRay; + highp float fullDist = length(ray); + highp vec3 curPos = rayStart; + + highp vec4 curColor = vec4(0, 0, 0, 0); + highp float curAlpha = 0.0; + highp float curLen = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + + highp vec4 destColor = vec4(0, 0, 0, 0); + highp float totalOpacity = 1.0; + + highp float extraAlphaMultiplier = fullDist * alphaThicknesses * alphaMultiplier; + + // nextEdges vector indicates the next edges of the texel boundaries along each axis that + // the ray is about to cross. The first edges are offset by a fraction of a texel to + // avoid artifacts from rounding errors later. + highp vec3 nextEdges = vec3(floor(curPos.x / textureDimensions.x) * textureDimensions.x, + floor(curPos.y / textureDimensions.y) * textureDimensions.y, + floor(curPos.z / textureDimensions.z) * textureDimensions.z); + + highp vec3 textureSteps = textureDimensions; + highp vec3 textureOffset = textureDimensions * 0.001; + if (ray.x > 0) { + nextEdges.x += textureDimensions.x + textureOffset.x; + } else { + nextEdges.x -= textureOffset.x; + textureSteps.x = -textureDimensions.x; + } + if (ray.y > 0) { + nextEdges.y += textureDimensions.y + textureOffset.y; + } else { + nextEdges.y -= textureOffset.y; + textureSteps.y = -textureDimensions.y; + } + if (ray.z > 0) { + nextEdges.z += textureDimensions.z + textureOffset.z; + } else { + nextEdges.z -= textureOffset.z; + textureSteps.z = -textureDimensions.z; + } + + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + for (int i = 0; i < sampleCount; i++) { + curColor = texture3D(textureSampler, curPos); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + // Find which dimension has least to go to figure out the next step distance + highp vec3 delta = abs(nextEdges - curPos); + highp vec3 modDelta = delta * invAbsRay; + highp float minDelta = min(modDelta.x, min(modDelta.y, modDelta.z)); + highp float stepSize; + if (minDelta == modDelta.x) { + nextEdges.x += textureSteps.x; + stepSize = delta.x * invAbsRay.x; + } + if (minDelta == modDelta.y) { + nextEdges.y += textureSteps.y; + stepSize = delta.y * invAbsRay.y; + } + if (minDelta == modDelta.z) { + nextEdges.z += textureSteps.z; + stepSize = delta.z * invAbsRay.z; + } + + curPos += stepSize * ray; + curLen += stepSize; + + if (curColor.a >= 0.0) { + if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0)) + curAlpha = 1.0; + else + curAlpha = curColor.a * extraAlphaMultiplier * stepSize; + highp float nextOpacity = totalOpacity - curAlpha; + // If opacity goes beyond full opacity, we need to adjust current alpha according + // to the fraction of the distance the material is visible, so that we don't get + // box artifacts around texels. + if (nextOpacity < 0.0) { + curAlpha *= totalOpacity / curAlpha; + nextOpacity = 0.0; + } + curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity); + totalOpacity = nextOpacity; + destColor.rgb += curRgb; + } + + if (curLen >= 1.0 || totalOpacity <= 0.0) + break; + } + + if (totalOpacity == 1.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalOpacity >= 0.0 && totalOpacity < 1.0) + destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity); + + destColor.a = (1.0 - totalOpacity); + gl_FragColor = clamp(destColor, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/texture3d.vert b/src/datavisualization/engine/shaders/texture3d.vert new file mode 100644 index 00000000..ef3f1b25 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.vert @@ -0,0 +1,36 @@ +uniform highp mat4 MVP; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; +uniform highp vec3 cameraPositionRelativeToModel; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; +attribute highp vec3 vertexNormal_mdl; + +varying highp vec3 pos; +varying highp vec3 rayDir; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + + highp vec3 minBoundsNorm = minBounds; + highp vec3 maxBoundsNorm = maxBounds; + + // Y and Z are flipped in bounds to be directly usable in texture calculations, + // so flip them back to normal for position calculations + minBoundsNorm.yz = -minBoundsNorm.yz; + maxBoundsNorm.yz = -maxBoundsNorm.yz; + + minBoundsNorm = 0.5 * (minBoundsNorm + 1.0); + maxBoundsNorm = 0.5 * (maxBoundsNorm + 1.0); + + pos = vertexPosition_mdl + + ((1.0 - vertexPosition_mdl) * minBoundsNorm) + - ((1.0 + vertexPosition_mdl) * (1.0 - maxBoundsNorm)); + + rayDir = -(cameraPositionRelativeToModel - pos); + + // Flip Y and Z so QImage bits work directly for texture and first image is in the front + rayDir.yz = -rayDir.yz; + pos.yz = -pos.yz; +} diff --git a/src/datavisualization/engine/shaders/texture3dlowdef.frag b/src/datavisualization/engine/shaders/texture3dlowdef.frag new file mode 100644 index 00000000..ed0d41ce --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3dlowdef.frag @@ -0,0 +1,109 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp vec3 textureDimensions; +uniform highp int sampleCount; // This is the maximum sample count +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha. +// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over +// entire volume, regardless of texture dimensions +const highp float alphaThicknesses = 32.0; +const highp float SQRT3 = 1.73205081; + +void main() { + vec3 rayStart = pos; + highp vec3 startBounds = minBounds; + highp vec3 endBounds = maxBounds; + if (rayDir.x < 0.0) { + startBounds.x = maxBounds.x; + endBounds.x = minBounds.x; + } + if (rayDir.y > 0.0) { + startBounds.y = maxBounds.y; + endBounds.y = minBounds.y; + } + if (rayDir.z > 0.0) { + startBounds.z = maxBounds.z; + endBounds.z = minBounds.z; + } + + // Calculate ray intersection endpoint + highp vec3 rayStop; + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 t = (endBounds - rayStart) * invRayDir; + highp float endT = min(t.x, min(t.y, t.z)); + if (endT <= 0.0) + discard; + rayStop = rayStart + endT * rayDir; + + // Convert intersections to texture coords + rayStart = 0.5 * (rayStart + 1.0); + rayStop = 0.5 * (rayStop + 1.0); + + highp vec3 ray = rayStop - rayStart; + + highp float fullDist = length(ray); + highp float stepSize = SQRT3 / sampleCount; + highp vec3 step = (SQRT3 * normalize(ray)) / sampleCount; + + rayStart += (step * 0.001); + + highp vec3 curPos = rayStart; + highp float curLen = 0.0; + highp vec4 curColor = vec4(0, 0, 0, 0); + highp float curAlpha = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + highp vec4 destColor = vec4(0, 0, 0, 0); + highp float totalOpacity = 1.0; + + highp float extraAlphaMultiplier = stepSize * alphaThicknesses * alphaMultiplier; + + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + for (int i = 0; i < sampleCount; i++) { + curColor = texture3D(textureSampler, curPos); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + if (curColor.a >= 0.0) { + if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0)) + curAlpha = 1.0; + else + curAlpha = curColor.a * extraAlphaMultiplier; + highp float nextOpacity = totalOpacity - curAlpha; + // If opacity goes beyond full opacity, we need to adjust current alpha according + // to the fraction of the distance the material is visible, so that we don't get + // box artifacts around texels. + if (nextOpacity < 0.0) { + curAlpha *= totalOpacity / curAlpha; + nextOpacity = 0.0; + } + + curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity); + destColor.rgb += curRgb; + totalOpacity = nextOpacity; + } + curPos += step; + curLen += stepSize; + if (curLen >= fullDist || totalOpacity <= 0.0) + break; + } + + if (totalOpacity == 1.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalOpacity >= 0.0 && totalOpacity < 1.0) + destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity); + + destColor.a = (1.0 - totalOpacity); + gl_FragColor = clamp(destColor, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/texture3dslice.frag b/src/datavisualization/engine/shaders/texture3dslice.frag new file mode 100644 index 00000000..c555af98 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3dslice.frag @@ -0,0 +1,155 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec3 volumeSliceIndices; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +const highp vec3 xPlaneNormal = vec3(1.0, 0, 0); +const highp vec3 yPlaneNormal = vec3(0, 1.0, 0); +const highp vec3 zPlaneNormal = vec3(0, 0, 1.0); + +void main() { + // Find out where ray intersects the slice planes + vec3 normRayDir = normalize(rayDir); + highp vec3 rayStart = pos; + highp float minT = 2.0f; + if (normRayDir.x != 0.0 && normRayDir.y != 0.0 && normRayDir.z != 0.0) { + highp vec3 boxBounds = vec3(1.0, 1.0, 1.0); + highp vec3 invRayDir = 1.0 / normRayDir; + if (normRayDir.x < 0) + boxBounds.x = -1.0; + if (normRayDir.y < 0) + boxBounds.y = -1.0; + if (normRayDir.z < 0) + boxBounds.z = -1.0; + highp vec3 t = (boxBounds - rayStart) * invRayDir; + minT = min(t.x, min(t.y, t.z)); + } + + highp vec3 xPoint = vec3(volumeSliceIndices.x, 0, 0); + highp vec3 yPoint = vec3(0, volumeSliceIndices.y, 0); + highp vec3 zPoint = vec3(0, 0, volumeSliceIndices.z); + highp float firstD = minT + 1.0; + highp float secondD = firstD; + highp float thirdD = firstD; + if (volumeSliceIndices.x >= -1.0) { + highp float dx = dot(xPoint - rayStart, xPlaneNormal) / dot(normRayDir, xPlaneNormal); + if (dx >= 0.0 && dx <= minT) + firstD = min(dx, firstD); + } + if (volumeSliceIndices.y >= -1.0) { + highp float dy = dot(yPoint - rayStart, yPlaneNormal) / dot(normRayDir, yPlaneNormal); + if (dy >= 0.0 && dy <= minT) { + if (dy < firstD) { + secondD = firstD; + firstD = dy; + } else { + secondD = dy; + } + } + } + if (volumeSliceIndices.z >= -1.0) { + highp float dz = dot(zPoint - rayStart, zPlaneNormal) / dot(normRayDir, zPlaneNormal); + if (dz >= 0.0) { + if (dz < firstD && dz <= minT) { + thirdD = secondD; + secondD = firstD; + firstD = dz; + } else if (dz < secondD){ + thirdD = secondD; + secondD = dz; + } else { + thirdD = dz; + } + } + } + + highp vec4 destColor = vec4(0.0, 0.0, 0.0, 0.0); + highp vec4 curColor = vec4(0.0, 0.0, 0.0, 0.0); + highp float totalAlpha = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + highp float curAlpha = 0.0; + + // Convert intersection to texture coords + + if (firstD <= minT) { + highp vec3 texelVec = rayStart + normRayDir * firstD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + if (curColor.a > 0.0) { + curAlpha = curColor.a; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + destColor.rgb = curColor.rgb * curAlpha; + totalAlpha = curAlpha; + } + } + if (secondD <= minT && totalAlpha < 1.0) { + texelVec = rayStart + normRayDir * secondD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a > 0.0) { + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += curAlpha; + } + } + if (thirdD <= minT && totalAlpha < 1.0) { + texelVec = rayStart + normRayDir * thirdD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (curColor.a > 0.0) { + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += curAlpha; + } + } + } + } + } + + if (totalAlpha == 0.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalAlpha > 0.0 && totalAlpha < 1.0) + destColor *= 1.0 / totalAlpha; + + destColor.a = totalAlpha; + gl_FragColor = clamp(destColor, 0.0, 1.0); +} + diff --git a/src/datavisualization/engine/surface3dcontroller.cpp b/src/datavisualization/engine/surface3dcontroller.cpp index c03bafd8..a6c086da 100644 --- a/src/datavisualization/engine/surface3dcontroller.cpp +++ b/src/datavisualization/engine/surface3dcontroller.cpp @@ -29,7 +29,8 @@ Surface3DController::Surface3DController(QRect rect, Q3DScene *scene) m_renderer(0), m_selectedPoint(invalidSelectionPosition()), m_selectedSeries(0), - m_flatShadingSupported(true) + m_flatShadingSupported(true), + m_flipHorizontalGrid(false) { // Setting a null axis creates a new default axis according to orientation and graph type. // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual @@ -79,6 +80,17 @@ void Surface3DController::synchDataToRenderer() m_renderer->updateSelectedPoint(m_selectedPoint, m_selectedSeries); m_changeTracker.selectedPointChanged = false; } + + if (m_changeTracker.flipHorizontalGridChanged) { + m_renderer->updateFlipHorizontalGrid(m_flipHorizontalGrid); + m_changeTracker.flipHorizontalGridChanged = false; + } + + if (m_changeTracker.surfaceTextureChanged) { + m_renderer->updateSurfaceTextures(m_changedTextures); + m_changeTracker.surfaceTextureChanged = false; + m_changedTextures.clear(); + } } void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation( @@ -140,6 +152,9 @@ void Surface3DController::addSeries(QAbstract3DSeries *series) QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series); if (surfaceSeries->selectedPoint() != invalidSelectionPosition()) setSelectedPoint(surfaceSeries->selectedPoint(), surfaceSeries, false); + + if (!surfaceSeries->texture().isNull()) + updateSurfaceTexture(surfaceSeries); } void Surface3DController::removeSeries(QAbstract3DSeries *series) @@ -168,6 +183,21 @@ QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList() return surfaceSeriesList; } +void Surface3DController::setFlipHorizontalGrid(bool flip) +{ + if (m_flipHorizontalGrid != flip) { + m_flipHorizontalGrid = flip; + m_changeTracker.flipHorizontalGridChanged = true; + emit flipHorizontalGridChanged(flip); + emitNeedRender(); + } +} + +bool Surface3DController::flipHorizontalGrid() const +{ + return m_flipHorizontalGrid; +} + void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode) { // Currently surface only supports row and column modes when also slicing @@ -429,6 +459,16 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) emitNeedRender(); } +void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series) +{ + m_changeTracker.surfaceTextureChanged = true; + + if (!m_changedTextures.contains(series)) + m_changedTextures.append(series); + + emitNeedRender(); +} + void Surface3DController::adjustAxisRanges() { QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX); diff --git a/src/datavisualization/engine/surface3dcontroller_p.h b/src/datavisualization/engine/surface3dcontroller_p.h index 2be74f35..8bcdea91 100644 --- a/src/datavisualization/engine/surface3dcontroller_p.h +++ b/src/datavisualization/engine/surface3dcontroller_p.h @@ -38,14 +38,18 @@ class Surface3DRenderer; class QSurface3DSeries; struct Surface3DChangeBitField { - bool selectedPointChanged : 1; - bool rowsChanged : 1; - bool itemChanged : 1; + bool selectedPointChanged : 1; + bool rowsChanged : 1; + bool itemChanged : 1; + bool flipHorizontalGridChanged : 1; + bool surfaceTextureChanged : 1; Surface3DChangeBitField() : selectedPointChanged(true), rowsChanged(false), - itemChanged(false) + itemChanged(false), + flipHorizontalGridChanged(true), + surfaceTextureChanged(true) { } }; @@ -73,6 +77,8 @@ private: bool m_flatShadingSupported; QVector<ChangeItem> m_changedItems; QVector<ChangeRow> m_changedRows; + bool m_flipHorizontalGrid; + QVector<QSurface3DSeries *> m_changedTextures; public: explicit Surface3DController(QRect rect, Q3DScene *scene = 0); @@ -101,6 +107,11 @@ public: virtual void removeSeries(QAbstract3DSeries *series); virtual QList<QSurface3DSeries *> surfaceSeriesList(); + void setFlipHorizontalGrid(bool flip); + bool flipHorizontalGrid() const; + + void updateSurfaceTexture(QSurface3DSeries *series); + public slots: void handleArrayReset(); void handleRowsAdded(int startIndex, int count); @@ -113,6 +124,7 @@ public slots: signals: void selectedSeriesChanged(QSurface3DSeries *series); + void flipHorizontalGridChanged(bool flip); private: Q_DISABLE_COPY(Surface3DController) diff --git a/src/datavisualization/engine/surface3drenderer.cpp b/src/datavisualization/engine/surface3drenderer.cpp index 38c8d8fe..37d6b463 100644 --- a/src/datavisualization/engine/surface3drenderer.cpp +++ b/src/datavisualization/engine/surface3drenderer.cpp @@ -30,10 +30,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION //#define SHOW_DEPTH_TEXTURE_SCENE -// Margin for background (1.10 make it 10% larger to avoid -// selection ball being drawn inside background) -const GLfloat backgroundMargin = 1.1f; -const GLfloat gridLineWidth = 0.005f; const GLfloat sliceZScale = 0.1f; const GLfloat sliceUnits = 2.5f; const uint greenMultiplier = 256; @@ -47,19 +43,16 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) m_backgroundShader(0), m_surfaceFlatShader(0), m_surfaceSmoothShader(0), + m_surfaceTexturedSmoothShader(0), + m_surfaceTexturedFlatShader(0), m_surfaceGridShader(0), m_surfaceSliceFlatShader(0), m_surfaceSliceSmoothShader(0), m_selectionShader(0), - m_labelShader(0), m_heightNormalizer(0.0f), - m_scaleFactor(0.0f), m_scaleX(0.0f), + m_scaleY(0.0f), m_scaleZ(0.0f), - m_scaleXWithBackground(0.0f), - m_scaleZWithBackground(0.0f), - m_depthTexture(0), - m_depthModelTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), m_selectionDepthBuffer(0), @@ -68,16 +61,12 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) m_flatSupported(true), m_selectionActive(false), m_shadowQualityMultiplier(3), - m_hasHeightAdjustmentChanged(true), m_selectedPoint(Surface3DController::invalidSelectionPosition()), m_selectedSeries(0), m_clickedPosition(Surface3DController::invalidSelectionPosition()), m_selectionTexturesDirty(false), m_noShadowTexture(0) { - m_axisCacheY.setScale(2.0f); - m_axisCacheY.setTranslate(-1.0f); - // Check if flat feature is supported ShaderHelper tester(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), QStringLiteral(":/shaders/fragmentSurfaceFlat")); @@ -90,12 +79,13 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) " Requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension."; } - initializeOpenGLFunctions(); initializeOpenGL(); } Surface3DRenderer::~Surface3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_depthFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -103,7 +93,6 @@ Surface3DRenderer::~Surface3DRenderer() m_textureHelper->deleteTexture(&m_noShadowTexture); m_textureHelper->deleteTexture(&m_depthTexture); - m_textureHelper->deleteTexture(&m_depthModelTexture); m_textureHelper->deleteTexture(&m_selectionResultTexture); } delete m_depthShader; @@ -111,10 +100,11 @@ Surface3DRenderer::~Surface3DRenderer() delete m_selectionShader; delete m_surfaceFlatShader; delete m_surfaceSmoothShader; + delete m_surfaceTexturedSmoothShader; + delete m_surfaceTexturedFlatShader; delete m_surfaceGridShader; delete m_surfaceSliceFlatShader; delete m_surfaceSliceSmoothShader; - delete m_labelShader; } void Surface3DRenderer::initializeOpenGL() @@ -123,25 +113,15 @@ void Surface3DRenderer::initializeOpenGL() // Initialize shaders initSurfaceShaders(); - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) - // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. - initDepthShader(); -#endif + if (!m_isOpenGLES) { + initDepthShader(); // For shadows + loadGridLineMesh(); + } // Init selection shader initSelectionShaders(); -#if !(defined QT_OPENGL_ES_2) - // Load grid line mesh - loadGridLineMesh(); -#endif - - // Load label mesh - loadLabelMesh(); - // Resize in case we've missed resize events // Resize calls initSelectionBuffer and initDepthBuffer, so they don't need to be called here handleResize(); @@ -155,6 +135,53 @@ void Surface3DRenderer::initializeOpenGL() m_noShadowTexture = m_textureHelper->create2DTexture(image, false, true, false, true); } +void Surface3DRenderer::fixCameraTarget(QVector3D &target) +{ + target.setX(target.x() * m_scaleX); + target.setY(target.y() * m_scaleY); + target.setZ(target.z() * -m_scaleZ); +} + +void Surface3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) +{ + // The inputs are the item bounds in OpenGL coordinates. + // The outputs limit these bounds to visible ranges, normalized to range [-1, 1] + // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those + float itemRangeX = (maxBounds.x() - minBounds.x()); + float itemRangeY = (maxBounds.y() - minBounds.y()); + float itemRangeZ = (maxBounds.z() - minBounds.z()); + + if (minBounds.x() < -m_scaleX) + minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_scaleX) / itemRangeX)); + else + minBounds.setX(-1.0f); + + if (minBounds.y() < -m_scaleY) + minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + m_scaleY) / itemRangeY))); + else + minBounds.setY(1.0f); + + if (minBounds.z() < -m_scaleZ) + minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_scaleZ) / itemRangeZ))); + else + minBounds.setZ(1.0f); + + if (maxBounds.x() > m_scaleX) + maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_scaleX) / itemRangeX)); + else + maxBounds.setX(1.0f); + + if (maxBounds.y() > m_scaleY) + maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - m_scaleY) / itemRangeY))); + else + maxBounds.setY(-1.0f); + + if (maxBounds.z() > m_scaleZ) + maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_scaleZ) / itemRangeZ))); + else + maxBounds.setZ(-1.0f); +} + void Surface3DRenderer::updateData() { calculateSceneScalingFactors(); @@ -264,6 +291,33 @@ void Surface3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesLis } } +void Surface3DRenderer::updateSurfaceTextures(QVector<QSurface3DSeries *> seriesList) +{ + foreach (QSurface3DSeries *series, seriesList) { + SurfaceSeriesRenderCache *cache = + static_cast<SurfaceSeriesRenderCache *>(m_renderCacheList.value(series)); + if (cache) { + GLuint oldTexture = cache->surfaceTexture(); + m_textureHelper->deleteTexture(&oldTexture); + cache->setSurfaceTexture(0); + + const QSurface3DSeries *currentSeries = cache->series(); + QSurfaceDataProxy *dataProxy = currentSeries->dataProxy(); + const QSurfaceDataArray &array = *dataProxy->array(); + + if (!series->texture().isNull()) { + cache->setSurfaceTexture(m_textureHelper->create2DTexture( + series->texture(), true, true, true)); + + if (cache->isFlatShadingEnabled()) + cache->surfaceObject()->coarseUVs(array, cache->dataArray()); + else + cache->surfaceObject()->smoothUVs(array, cache->dataArray()); + } + } + } +} + SeriesRenderCache *Surface3DRenderer::createNewCache(QAbstract3DSeries *series) { m_selectionTexturesDirty = true; @@ -301,10 +355,13 @@ void Surface3DRenderer::updateRows(const QVector<Surface3DController::ChangeRow> srcArray->at(row)->at(j + sampleSpace.x()); } - if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->updateCoarseRow(dstArray, row - sampleSpace.y()); - else - cache->surfaceObject()->updateSmoothRow(dstArray, row - sampleSpace.y()); + if (cache->isFlatShadingEnabled()) { + cache->surfaceObject()->updateCoarseRow(dstArray, row - sampleSpace.y(), + m_polarGraph); + } else { + cache->surfaceObject()->updateSmoothRow(dstArray, row - sampleSpace.y(), + m_polarGraph); + } } if (updateBuffers) cache->surfaceObject()->uploadBuffers(); @@ -343,9 +400,9 @@ void Surface3DRenderer::updateItems(const QVector<Surface3DController::ChangeIte (*(dstArray.at(y)))[x] = srcArray->at(point.x())->at(point.y()); if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->updateCoarseItem(dstArray, y, x); + cache->surfaceObject()->updateCoarseItem(dstArray, y, x, m_polarGraph); else - cache->surfaceObject()->updateSmoothItem(dstArray, y, x); + cache->surfaceObject()->updateSmoothItem(dstArray, y, x, m_polarGraph); } if (updateBuffers) cache->surfaceObject()->uploadBuffers(); @@ -538,10 +595,12 @@ void Surface3DRenderer::updateSliceObject(SurfaceSeriesRenderCache *cache, const QRect sliceRect(0, 0, sliceRow->size(), 2); if (sliceRow->size() > 0) { - if (cache->isFlatShadingEnabled()) - cache->sliceSurfaceObject()->setUpData(sliceDataArray, sliceRect, true, flipZX); - else - cache->sliceSurfaceObject()->setUpSmoothData(sliceDataArray, sliceRect, true, flipZX); + if (cache->isFlatShadingEnabled()) { + cache->sliceSurfaceObject()->setUpData(sliceDataArray, sliceRect, true, false, flipZX); + } else { + cache->sliceSurfaceObject()->setUpSmoothData(sliceDataArray, sliceRect, true, false, + flipZX); + } } } @@ -664,15 +723,6 @@ QRect Surface3DRenderer::calculateSampleRect(const QSurfaceDataArray &array) void Surface3DRenderer::updateScene(Q3DScene *scene) { - // Set initial camera position - // X must be 0 for rotation to work - we can use "setCameraRotation" for setting it later - if (m_hasHeightAdjustmentChanged) { - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, zeroVector, - upVector); - // For now this is used just to make things once. Proper use will come - m_hasHeightAdjustmentChanged = false; - } - Abstract3DRenderer::updateScene(scene); if (m_selectionActive @@ -716,6 +766,14 @@ void Surface3DRenderer::render(GLuint defaultFboHandle) void Surface3DRenderer::drawSlicedScene() { + if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow) + == m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) { + qWarning("Invalid selection mode. Either QAbstract3DGraph::SelectionRow or" + " QAbstract3DGraph::SelectionColumn must be set before calling" + " setSlicingActive(true)."); + return; + } + QVector3D lightPos; QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); @@ -751,6 +809,19 @@ void Surface3DRenderer::drawSlicedScene() AxisRenderCache &sliceCache = rowMode ? m_axisCacheX : m_axisCacheZ; GLfloat scaleXBackground = 0.0f; + if (rowMode) { + // Don't use the regular margin for polar, as the graph is not going to be to scale anyway, + // and polar graphs often have quite a bit of margin, resulting in ugly slices. + if (m_polarGraph) + scaleXBackground = m_scaleX + 0.1f; + else + scaleXBackground = m_scaleXWithBackground; + } else { + if (m_polarGraph) + scaleXBackground = m_scaleZ + 0.1f; + else + scaleXBackground = m_scaleZWithBackground; + } // Disable culling to avoid ugly conditionals with reversed axes and data glDisable(GL_CULL_FACE); @@ -767,11 +838,6 @@ void Surface3DRenderer::drawSlicedScene() drawGrid = true; } - if (rowMode) - scaleXBackground = m_scaleXWithBackground; - else - scaleXBackground = m_scaleZWithBackground; - QMatrix4x4 MVPMatrix; QMatrix4x4 modelMatrix; QMatrix4x4 itModelMatrix; @@ -790,9 +856,27 @@ void Surface3DRenderer::drawSlicedScene() surfaceShader->bind(); - GLuint colorTexture = cache->baseUniformTexture();; - if (cache->colorStyle() != Q3DTheme::ColorStyleUniform) + GLuint colorTexture = cache->baseUniformTexture(); + if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) { + colorTexture = cache->baseUniformTexture(); + surfaceShader->setUniformValue(surfaceShader->gradientMin(), 0.0f); + surfaceShader->setUniformValue(surfaceShader->gradientHeight(), 0.0f); + } else { colorTexture = cache->baseGradientTexture(); + if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) { + float objMin = cache->surfaceObject()->minYValue(); + float objMax = cache->surfaceObject()->maxYValue(); + float objRange = objMax - objMin; + surfaceShader->setUniformValue(surfaceShader->gradientMin(), + -(objMin / objRange)); + surfaceShader->setUniformValue(surfaceShader->gradientHeight(), + 1.0f / objRange); + } else { + surfaceShader->setUniformValue(surfaceShader->gradientMin(), 0.5f); + surfaceShader->setUniformValue(surfaceShader->gradientHeight(), + 1.0f / (m_scaleY * 2.0f)); + } + } // Set shader bindings surfaceShader->setUniformValue(surfaceShader->lightP(), lightPos); @@ -801,9 +885,10 @@ void Surface3DRenderer::drawSlicedScene() surfaceShader->setUniformValue(surfaceShader->nModel(), itModelMatrix.inverted().transposed()); surfaceShader->setUniformValue(surfaceShader->MVP(), MVPMatrix); - surfaceShader->setUniformValue(surfaceShader->lightS(), 0.15f); + surfaceShader->setUniformValue(surfaceShader->lightS(), 0.0f); surfaceShader->setUniformValue(surfaceShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 10.0f); surfaceShader->setUniformValue(surfaceShader->lightColor(), lightColor); m_drawer->drawObject(surfaceShader, cache->sliceSurfaceObject(), colorTexture); @@ -830,19 +915,16 @@ void Surface3DRenderer::drawSlicedScene() } } - // Disable textures - glDisable(GL_TEXTURE_2D); - glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Grid lines - if (m_cachedTheme->isGridEnabled() && m_heightNormalizer) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + if (m_cachedTheme->isGridEnabled()) { + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; // Bind line shader lineShader->bind(); @@ -853,7 +935,8 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->view(), viewMatrix); lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 10.0f); lineShader->setUniformValue(lineShader->lightS(), 0.0f); lineShader->setUniformValue(lineShader->lightColor(), lightColor); @@ -881,16 +964,15 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } // Vertical lines - QVector3D gridLineScaleY(gridLineWidth, backgroundMargin, gridLineWidth); + QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth); gridLineCount = sliceCache.gridLineCount(); for (int line = 0; line < gridLineCount; line++) { @@ -901,10 +983,11 @@ void Surface3DRenderer::drawSlicedScene() modelMatrix.translate(sliceCache.gridLinePosition(line), 0.0f, -1.0f); modelMatrix.scale(gridLineScaleY); itModelMatrix.scale(gridLineScaleY); -#if (defined QT_OPENGL_ES_2) - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -915,17 +998,15 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } // Draw labels m_labelShader->bind(); - glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -959,12 +1040,15 @@ void Surface3DRenderer::drawSlicedScene() labelNbr = 0; positionComp.setY(-0.1f); - labelTrans.setY(-backgroundMargin); + labelTrans.setY(-m_scaleYWithBackground); labelCount = sliceCache.labelCount(); for (int label = 0; label < labelCount; label++) { if (countLabelItems > labelNbr) { // Draw the label here - labelTrans.setX(sliceCache.labelPosition(label)); + if (rowMode) + labelTrans.setX(sliceCache.labelPosition(label)); + else + labelTrans.setX(-sliceCache.labelPosition(label)); m_dummyRenderItem.setTranslation(labelTrans); @@ -998,7 +1082,6 @@ void Surface3DRenderer::drawSlicedScene() m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera, false, false, Drawer::LabelMid, Qt::AlignBottom); - glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); @@ -1048,6 +1131,21 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) else m_xFlipped = true; + m_yFlippedForGrid = m_yFlipped; + if (m_flipHorizontalGrid) { + if (!m_useOrthoProjection) { + // Need to determine if camera is below graph top + float distanceToCenter = activeCamera->position().length() + / activeCamera->zoomLevel() / m_autoScaleAdjustment * 100.0f; + qreal cameraAngle = qreal(activeCamera->yRotation()) / 180.0 * M_PI; + float cameraYPos = float(qSin(cameraAngle)) * distanceToCenter; + m_yFlippedForGrid = cameraYPos < (m_scaleYWithBackground - m_oldCameraTarget.y()); + } else if (m_useOrthoProjection && activeCamera->yRotation() == 0.0f) { + // With ortho we only need to flip at angle zero, to fix label autorotation angles + m_yFlippedForGrid = !m_yFlipped; + } + } + // calculate background rotation based on view matrix rotation if (viewMatrix.row(0).x() > 0 && viewMatrix.row(0).z() <= 0) backgroundRotation = 270.0f; @@ -1065,9 +1163,8 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 depthProjectionViewMatrix; // Draw depth buffer -#if !defined(QT_OPENGL_ES_2) GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && + if (!m_isOpenGLES && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && (!m_renderCacheList.isEmpty() || !m_customRenderCache.isEmpty())) { // Render scene into a depth texture for using with shadow mapping // Enable drawing to depth framebuffer @@ -1097,6 +1194,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) / (GLfloat)m_primarySubViewport.height(), 3.0f, 100.0f); depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix; + // Surface is not closed, so don't cull anything glDisable(GL_CULL_FACE); foreach (SeriesRenderCache *baseCache, m_renderCacheList) { @@ -1104,45 +1202,9 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) SurfaceObject *object = cache->surfaceObject(); if (object->indexCount() && cache->surfaceVisible() && cache->isVisible() && cache->sampleSpace().width() >= 2 && cache->sampleSpace().height() >= 2) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - - MVPMatrix = depthProjectionViewMatrix * modelMatrix; - cache->setMVPMatrix(MVPMatrix); - m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); - - // 1st attribute buffer : vertices - glEnableVertexAttribArray(m_depthShader->posAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf()); - glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, - (void *)0); - - // Index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); - - // Draw the triangles - glDrawElements(GL_TRIANGLES, object->indexCount(), - object->indicesType(), (void *)0); - } - } - - glEnable(GL_CULL_FACE); - glCullFace(GL_FRONT); - - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - m_depthModelTexture, 0); - glClear(GL_DEPTH_BUFFER_BIT); - - foreach (SeriesRenderCache *baseCache, m_renderCacheList) { - SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache); - SurfaceObject *object = cache->surfaceObject(); - if (object->indexCount() && cache->surfaceVisible() && cache->isVisible() - && cache->sampleSpace().width() >= 2 && cache->sampleSpace().height() >= 2) { - m_depthShader->setUniformValue(m_depthShader->MVP(), cache->MVPMatrix()); + // No translation nor scaling for surfaces, therefore no modelMatrix + // Use directly projectionViewMatrix + m_depthShader->setUniformValue(m_depthShader->MVP(), depthProjectionViewMatrix); // 1st attribute buffer : vertices glEnableVertexAttribArray(m_depthShader->posAtt()); @@ -1165,9 +1227,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glDisableVertexAttribArray(m_depthShader->posAtt()); + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1182,15 +1248,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glEnable(GL_CULL_FACE); glCullFace(GL_BACK); } -#endif - // Enable texturing - glEnable(GL_TEXTURE_2D); + + // Do position mapping when necessary + if (m_graphPositionQueryPending) { + QVector3D graphDimensions(m_scaleX, m_scaleY, m_scaleZ); + queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle); + emit needRender(); + } // Draw selection buffer if (!m_cachedIsSlicingActivated && (!m_renderCacheList.isEmpty() || !m_customRenderCache.isEmpty()) && m_selectionState == SelectOnScene - && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone) { + && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone + && m_selectionResultTexture) { m_selectionShader->bind(); glBindFramebuffer(GL_FRAMEBUFFER, m_selectionFrameBuffer); glViewport(0, @@ -1208,18 +1279,17 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) foreach (SeriesRenderCache *baseCache, m_renderCacheList) { SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache); if (cache->surfaceObject()->indexCount() && cache->renderable()) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; + m_selectionShader->setUniformValue(m_selectionShader->MVP(), projectionViewMatrix); - MVPMatrix = projectionViewMatrix * modelMatrix; - m_selectionShader->setUniformValue(m_selectionShader->MVP(), MVPMatrix); + cache->surfaceObject()->activateSurfaceTexture(false); m_drawer->drawObject(m_selectionShader, cache->surfaceObject(), cache->selectionTexture()); } } m_surfaceGridShader->bind(); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -1237,6 +1307,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) + uint(clickedColor.w()) * alphaMultiplier; m_clickedPosition = selectionIdToSurfacePoint(selectionId); + m_clickResolved = true; emit needRender(); @@ -1249,7 +1320,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) // Draw the surface if (!m_renderCacheList.isEmpty()) { - // For surface we can see climpses from underneath + // For surface we can see glimpses from underneath glDisable(GL_CULL_FACE); bool drawGrid = false; @@ -1261,9 +1332,9 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 itModelMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + MVPMatrix = depthProjectionViewMatrix; #else - MVPMatrix = projectionViewMatrix * modelMatrix; + MVPMatrix = projectionViewMatrix; #endif cache->setMVPMatrix(MVPMatrix); @@ -1279,8 +1350,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) if (cache->surfaceVisible()) { ShaderHelper *shader = m_surfaceFlatShader; - if (!cache->isFlatShadingEnabled()) + if (cache->surfaceTexture()) + shader = m_surfaceTexturedFlatShader; + if (!cache->isFlatShadingEnabled()) { shader = m_surfaceSmoothShader; + if (cache->surfaceTexture()) + shader = m_surfaceTexturedSmoothShader; + } shader->bind(); // Set shader bindings @@ -1294,14 +1370,35 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength()); shader->setUniformValue(shader->lightColor(), lightColor); - GLuint gradientTexture; - if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) - gradientTexture = cache->baseUniformTexture(); - else - gradientTexture = cache->baseGradientTexture(); + // Set the surface texturing + cache->surfaceObject()->activateSurfaceTexture(false); + GLuint texture; + if (cache->surfaceTexture()) { + texture = cache->surfaceTexture(); + cache->surfaceObject()->activateSurfaceTexture(true); + } else { + if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) { + texture = cache->baseUniformTexture(); + shader->setUniformValue(shader->gradientMin(), 0.0f); + shader->setUniformValue(shader->gradientHeight(), 0.0f); + } else { + texture = cache->baseGradientTexture(); + if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) { + float objMin = cache->surfaceObject()->minYValue(); + float objMax = cache->surfaceObject()->maxYValue(); + float objRange = objMax - objMin; + shader->setUniformValue(shader->gradientMin(), -(objMin / objRange)); + shader->setUniformValue(shader->gradientHeight(), 1.0f / objRange); + } else { + shader->setUniformValue(shader->gradientMin(), 0.5f); + shader->setUniformValue(shader->gradientHeight(), + 1.0f / (m_scaleY * 2.0f)); + } + } + } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (!m_isOpenGLES && + m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; shader->setUniformValue(shader->shadowQ(), m_shadowQualityToShader); @@ -1309,17 +1406,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) shader->setUniformValue(shader->lightS(), adjustedLightStrength); // Draw the objects - m_drawer->drawObject(shader, cache->surfaceObject(), gradientTexture, - m_depthModelTexture); - } else -#endif - { + m_drawer->drawObject(shader, cache->surfaceObject(), texture, + m_depthTexture); + } else { // Set shadowless shader bindings - shader->setUniformValue(shader->lightS(), - m_cachedTheme->lightStrength()); - + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); // Draw the objects - m_drawer->drawObject(shader, cache->surfaceObject(), gradientTexture); + m_drawer->drawObject(shader, cache->surfaceObject(), texture); } } } @@ -1359,12 +1452,12 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - QVector3D bgScale(m_scaleXWithBackground, backgroundMargin, m_scaleZWithBackground); + QVector3D bgScale(m_scaleXWithBackground, m_scaleYWithBackground, m_scaleZWithBackground); modelMatrix.scale(bgScale); // If we're viewing from below, background object must be flipped if (m_yFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); modelMatrix.rotate(270.0f - backgroundRotation, 0.0f, 1.0f, 0.0f); } else { modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); @@ -1392,8 +1485,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength() * 2.0f); m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(), @@ -1406,11 +1498,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_noShadowTexture); else m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#else - Q_UNUSED(noShadows); -#endif - { + } else { // Set shadowless shader bindings m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), m_cachedTheme->lightStrength()); @@ -1423,14 +1511,14 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) // Draw grid lines QVector3D gridLineScaleX(m_scaleXWithBackground, gridLineWidth, gridLineWidth); QVector3D gridLineScaleZ(gridLineWidth, gridLineWidth, m_scaleZWithBackground); - QVector3D gridLineScaleY(gridLineWidth, backgroundMargin, gridLineWidth); + QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth); - if (m_cachedTheme->isGridEnabled() && m_heightNormalizer) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + if (m_cachedTheme->isGridEnabled()) { + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_surfaceGridShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; // Bind line shader lineShader->bind(); @@ -1442,15 +1530,12 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadowed shader bindings lineShader->setUniformValue(lineShader->shadowQ(), m_shadowQualityToShader); lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 20.0f); - } else -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1460,205 +1545,211 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QQuaternion lineXRotation; if (m_xFlipped) - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineYRotation = m_yRightAngleRotationNeg; else - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineYRotation = m_yRightAngleRotation; - if (m_yFlipped) - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + if (m_yFlippedForGrid) + lineXRotation = m_xRightAngleRotation; else - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineXRotation = m_xRightAngleRotationNeg; - GLfloat yFloorLinePosition = -backgroundMargin + gridLineOffset; - if (m_yFlipped) + float yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset; + if (m_yFlipped != m_flipHorizontalGrid) yFloorLinePosition = -yFloorLinePosition; // Rows (= Z) if (m_axisCacheZ.segmentCount() > 0) { - // Floor lines int gridLineCount = m_axisCacheZ.gridLineCount(); - - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(0.0f, yFloorLinePosition, - m_axisCacheZ.gridLinePosition(line)); - - modelMatrix.scale(gridLineScaleX); - itModelMatrix.scale(gridLineScaleX); - - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); - - MVPMatrix = projectionViewMatrix * modelMatrix; - - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); - -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + // Floor lines + if (m_polarGraph) { + drawRadialGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; + + modelMatrix.translate(0.0f, yFloorLinePosition, + m_axisCacheZ.gridLinePosition(line)); + + modelMatrix.scale(gridLineScaleX); + itModelMatrix.scale(gridLineScaleX); + + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); + + MVPMatrix = projectionViewMatrix * modelMatrix; + + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } + // Side wall lines + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; - // Side wall lines - GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; + if (!m_xFlipped) + lineXTrans = -lineXTrans; - if (!m_xFlipped) - lineXTrans = -lineXTrans; + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); + modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); - modelMatrix.scale(gridLineScaleY); - itModelMatrix.scale(gridLineScaleY); + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); -#if !defined(QT_OPENGL_ES_2) - modelMatrix.rotate(lineYRotation); - itModelMatrix.rotate(lineYRotation); -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif - - MVPMatrix = projectionViewMatrix * modelMatrix; - - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + modelMatrix.rotate(lineYRotation); + itModelMatrix.rotate(lineYRotation); + } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + MVPMatrix = projectionViewMatrix * modelMatrix; + + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } // Columns (= X) if (m_axisCacheX.segmentCount() > 0) { -#if defined(QT_OPENGL_ES_2) - lineXRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif + if (m_isOpenGLES) + lineXRotation = m_yRightAngleRotation; + // Floor lines int gridLineCount = m_axisCacheX.gridLineCount(); - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, - 0.0f); - - modelMatrix.scale(gridLineScaleZ); - itModelMatrix.scale(gridLineScaleZ); - - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); - - MVPMatrix = projectionViewMatrix * modelMatrix; - - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); - -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_polarGraph) { + drawAngularGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; + + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, + 0.0f); + + modelMatrix.scale(gridLineScaleZ); + itModelMatrix.scale(gridLineScaleZ); + + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); + + MVPMatrix = projectionViewMatrix * modelMatrix; + + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - // Back wall lines - GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; + // Back wall lines + GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; - if (!m_zFlipped) - lineZTrans = -lineZTrans; - - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); + if (!m_zFlipped) + lineZTrans = -lineZTrans; - modelMatrix.scale(gridLineScaleY); - itModelMatrix.scale(gridLineScaleY); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; -#if !defined(QT_OPENGL_ES_2) - if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - } -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); - MVPMatrix = projectionViewMatrix * modelMatrix; + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else if (m_zFlipped) { + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); + } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + MVPMatrix = projectionViewMatrix * modelMatrix; + + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } else { + m_drawer->drawLine(lineShader); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } @@ -1683,8 +1774,8 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.scale(gridLineScaleX); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1695,20 +1786,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } // Side wall @@ -1738,20 +1829,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + if (!m_isOpenGLES) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } } } @@ -1811,7 +1902,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1832,8 +1923,14 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa QVector3D positionZComp(0.0f, 0.0f, 0.0f); if (m_axisCacheZ.segmentCount() > 0) { int labelCount = m_axisCacheZ.labelCount(); - GLfloat labelXTrans = m_scaleXWithBackground + labelMargin; - GLfloat labelYTrans = -backgroundMargin; + float labelXTrans = m_scaleXWithBackground + labelMargin; + float labelYTrans = m_flipHorizontalGrid ? m_scaleYWithBackground : -m_scaleYWithBackground; + if (m_polarGraph) { + labelXTrans *= m_radialLabelOffset; + // YTrans up only if over background + if (m_radialLabelOffset < 1.0f) + labelYTrans += gridLineOffset + gridLineWidth; + } Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_xFlipped) @@ -1843,7 +1940,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa if (labelAutoAngle == 0.0f) { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) labelRotation.setY(180.0f); else @@ -1855,7 +1952,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX) @@ -1920,10 +2017,34 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { glPolygonOffset(offsetValue++ / -10.0f, 1.0f); + const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); // Draw the label here - labelTrans.setZ(m_axisCacheZ.labelPosition(label)); + if (m_polarGraph) { + float direction = m_zFlipped ? -1.0f : 1.0f; + labelTrans.setZ((m_axisCacheZ.formatter()->labelPositions().at(label) + * -m_polarRadius + + m_drawer->scaledFontSize() + gridLineWidth) * direction); + } else { + labelTrans.setZ(m_axisCacheZ.labelPosition(label)); + } + if (label == 0 || label == (labelCount - 1)) { + // If the margin is small, adjust the position of the edge labels to avoid overlapping + // with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelTrans.z()) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleZWithBackground + labelMargin; + // No need to adjust quite as much on the front edges + if (label != startIndex) + labelOverlap /= 2.0f; + if (labelOverlap > 0.0f) { + if (label == 0) + labelTrans.setZ(labelTrans.z() - labelOverlap); + else + labelTrans.setZ(labelTrans.z() + labelOverlap); + } + } m_dummyRenderItem.setTranslation(labelTrans); - const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); if (drawSelection) { QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f, @@ -1938,7 +2059,14 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width())); } if (!drawSelection && m_axisCacheZ.isTitleVisible()) { - labelTrans.setZ(0.0f); + if (m_polarGraph) { + float titleZ = -m_polarRadius / 2.0f; + if (m_zFlipped) + titleZ = -titleZ; + labelTrans.setZ(titleZ); + } else { + labelTrans.setZ(0.0f); + } drawAxisTitleZ(labelRotation, labelTrans, totalRotation, m_dummyRenderItem, activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader); } @@ -1952,8 +2080,13 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheX.labelCount(); - GLfloat labelZTrans = m_scaleZWithBackground + labelMargin; - GLfloat labelYTrans = -backgroundMargin; + float labelZTrans = 0.0f; + float labelYTrans = m_flipHorizontalGrid ? m_scaleYWithBackground : -m_scaleYWithBackground; + if (m_polarGraph) + labelYTrans += gridLineOffset + gridLineWidth; + else + labelZTrans = m_scaleZWithBackground + labelMargin; + Qt::AlignmentFlag alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_zFlipped) @@ -1964,7 +2097,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation = QVector3D(-90.0f, 90.0f, 0.0f); if (m_xFlipped) labelRotation.setY(-90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_xFlipped) labelRotation.setY(-90.0f); else @@ -1976,7 +2109,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation.setY(-90.0f); else labelRotation.setY(90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX) @@ -2022,7 +2155,16 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } } } + QQuaternion totalRotation = Utils::calculateRotation(labelRotation); + if (m_polarGraph) { + if ((!m_yFlippedForGrid && (m_zFlipped != m_xFlipped)) + || (m_yFlippedForGrid && (m_zFlipped == m_xFlipped))) { + totalRotation *= m_zRightAngleRotation; + } else { + totalRotation *= m_zRightAngleRotationNeg; + } + } QVector3D labelTrans = QVector3D(0.0f, labelYTrans, @@ -2038,12 +2180,64 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa indexStep = 1; } float offsetValue = 0.0f; + bool showLastLabel = false; + QVector<float> &labelPositions = m_axisCacheX.formatter()->labelPositions(); + int lastLabelPosIndex = labelPositions.size() - 1; + if (labelPositions.size() + && (labelPositions.at(lastLabelPosIndex) != 1.0f || labelPositions.at(0) != 0.0f)) { + // Avoid overlapping first and last label if they would get on same position + showLastLabel = true; + } + for (int label = startIndex; label != endIndex; label = label + indexStep) { glPolygonOffset(offsetValue++ / -10.0f, 1.0f); // Draw the label here - labelTrans.setX(m_axisCacheX.labelPosition(label)); - m_dummyRenderItem.setTranslation(labelTrans); + if (m_polarGraph) { + // Calculate angular position + if (label == lastLabelPosIndex && !showLastLabel) + continue; + float labelPosition = labelPositions.at(label); + qreal angle = labelPosition * M_PI * 2.0; + labelTrans.setX((m_polarRadius + labelMargin) * float(qSin(angle))); + labelTrans.setZ(-(m_polarRadius + labelMargin) * float(qCos(angle))); + // Alignment depends on label angular position, as well as flips + Qt::AlignmentFlag vAlignment = Qt::AlignCenter; + Qt::AlignmentFlag hAlignment = Qt::AlignCenter; + const float centerMargin = 0.005f; + if (labelPosition < 0.25f - centerMargin || labelPosition > 0.75f + centerMargin) + vAlignment = m_zFlipped ? Qt::AlignTop : Qt::AlignBottom; + else if (labelPosition > 0.25f + centerMargin && labelPosition < 0.75f - centerMargin) + vAlignment = m_zFlipped ? Qt::AlignBottom : Qt::AlignTop; + + if (labelPosition < 0.50f - centerMargin && labelPosition > centerMargin) + hAlignment = m_zFlipped ? Qt::AlignRight : Qt::AlignLeft; + else if (labelPosition < 1.0f - centerMargin && labelPosition > 0.5f + centerMargin) + hAlignment = m_zFlipped ? Qt::AlignLeft : Qt::AlignRight; + if (m_yFlippedForGrid && vAlignment != Qt::AlignCenter) + vAlignment = (vAlignment == Qt::AlignTop) ? Qt::AlignBottom : Qt::AlignTop; + alignment = Qt::AlignmentFlag(vAlignment | hAlignment); + } else { + labelTrans.setX(m_axisCacheX.labelPosition(label)); + } const LabelItem &axisLabelItem = *m_axisCacheX.labelItems().at(label); + if (label == 0 || label == (labelCount - 1)) { + // If the margin is small, adjust the position of the edge labels to avoid overlapping + // with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelTrans.x()) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleXWithBackground + labelMargin; + // No need to adjust quite as much on the front edges + if (label != startIndex) + labelOverlap /= 2.0f; + if (labelOverlap > 0.0f) { + if (label == 0) + labelTrans.setX(labelTrans.x() + labelOverlap); + else + labelTrans.setX(labelTrans.x() - labelOverlap); + } + } + m_dummyRenderItem.setTranslation(labelTrans); if (drawSelection) { QVector4D labelColor = QVector4D(0.0f, label / 255.0f, 0.0f, @@ -2059,8 +2253,20 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } if (!drawSelection && m_axisCacheX.isTitleVisible()) { labelTrans.setX(0.0f); + bool radial = false; + if (m_polarGraph) { + if (m_xFlipped == m_zFlipped) + totalRotation *= m_zRightAngleRotation; + else + totalRotation *= m_zRightAngleRotationNeg; + if (m_yFlippedForGrid) + totalRotation *= QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f); + labelTrans.setZ(-m_polarRadius); + radial = true; + } drawAxisTitleX(labelRotation, labelTrans, totalRotation, m_dummyRenderItem, - activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader); + activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader, + radial); } } // Y Labels @@ -2072,12 +2278,12 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheY.labelCount(); - GLfloat labelXTrans = m_scaleXWithBackground; - GLfloat labelZTrans = m_scaleZWithBackground; + float labelXTrans = m_scaleXWithBackground; + float labelZTrans = m_scaleZWithBackground; // Back & side wall - GLfloat labelMarginXTrans = labelMargin; - GLfloat labelMarginZTrans = labelMargin; + float labelMarginXTrans = labelMargin; + float labelMarginZTrans = labelMargin; QVector3D backLabelRotation(0.0f, -90.0f, 0.0f); QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f); Qt::AlignmentFlag backAlignment = @@ -2134,7 +2340,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label); - const GLfloat labelYTrans = m_axisCacheY.labelPosition(label); + float labelYTrans = m_axisCacheY.labelPosition(label); glPolygonOffset(offsetValue++ / -10.0f, 1.0f); @@ -2144,6 +2350,21 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa shader->setUniformValue(shader->color(), labelColor); } + if (label == startIndex) { + // If the margin is small, adjust the position of the edge label to avoid + // overlapping with labels of the other axes. + float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height(); + float labelOverlap = qAbs(labelYTrans) + + (scaleFactor * axisLabelItem.size().height() / 2.0f) + - m_scaleYWithBackground + labelMargin; + if (labelOverlap > 0.0f) { + if (label == 0) + labelYTrans += labelOverlap; + else + labelYTrans -= labelOverlap; + } + } + // Back wall labelTransBack.setY(labelYTrans); m_dummyRenderItem.setTranslation(labelTransBack); @@ -2174,10 +2395,8 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } glDisable(GL_POLYGON_OFFSET_FILL); - if (!drawSelection) { - glDisable(GL_TEXTURE_2D); + if (!drawSelection) glDisable(GL_BLEND); - } } void Surface3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode) @@ -2205,11 +2424,12 @@ void Surface3DRenderer::updateSelectionTextures() void Surface3DRenderer::createSelectionTexture(SurfaceSeriesRenderCache *cache, uint &lastSelectionId) { - // Create the selection ID image. Each grid corner gets 2x2 pixel area of - // ID color so that each vertex (data point) has 4x4 pixel area of ID color + // Create the selection ID image. Each grid corner gets 1 pixel area of + // ID color so that each vertex (data point) has 2x2 pixel area of ID color, + // except the vertices on the edges. const QRect &sampleSpace = cache->sampleSpace(); - int idImageWidth = (sampleSpace.width() - 1) * 4; - int idImageHeight = (sampleSpace.height() - 1) * 4; + int idImageWidth = (sampleSpace.width() - 1) * 2; + int idImageHeight = (sampleSpace.height() - 1) * 2; if (idImageHeight <= 0 || idImageWidth <= 0) { cache->setSelectionIdRange(-1, -1); @@ -2221,21 +2441,21 @@ void Surface3DRenderer::createSelectionTexture(SurfaceSeriesRenderCache *cache, uint idStart = lastSelectionId; uchar *bits = new uchar[idImageWidth * idImageHeight * 4 * sizeof(uchar)]; - for (int i = 0; i < idImageHeight; i += 4) { - for (int j = 0; j < idImageWidth; j += 4) { + for (int i = 0; i < idImageHeight; i += 2) { + for (int j = 0; j < idImageWidth; j += 2) { int p = (i * idImageWidth + j) * 4; uchar r, g, b, a; idToRGBA(lastSelectionId, &r, &g, &b, &a); - fillIdCorner(&bits[p], r, g, b, a, stride); + fillIdCorner(&bits[p], r, g, b, a); idToRGBA(lastSelectionId + 1, &r, &g, &b, &a); - fillIdCorner(&bits[p + 8], r, g, b, a, stride); + fillIdCorner(&bits[p + 4], r, g, b, a); idToRGBA(lastSelectionId + sampleSpace.width(), &r, &g, &b, &a); - fillIdCorner(&bits[p + 2 * stride], r, g, b, a, stride); + fillIdCorner(&bits[p + stride], r, g, b, a); idToRGBA(lastSelectionId + sampleSpace.width() + 1, &r, &g, &b, &a); - fillIdCorner(&bits[p + 2 * stride + 8], r, g, b, a, stride); + fillIdCorner(&bits[p + stride + 4], r, g, b, a); lastSelectionId++; } @@ -2263,24 +2483,12 @@ void Surface3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -void Surface3DRenderer::fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a, int stride) +void Surface3DRenderer::fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a) { p[0] = r; p[1] = g; p[2] = b; p[3] = a; - p[4] = r; - p[5] = g; - p[6] = b; - p[7] = a; - p[stride + 0] = r; - p[stride + 1] = g; - p[stride + 2] = b; - p[stride + 3] = a; - p[stride + 4] = r; - p[stride + 5] = g; - p[stride + 6] = b; - p[stride + 7] = a; } void Surface3DRenderer::idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a) @@ -2293,21 +2501,66 @@ void Surface3DRenderer::idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a void Surface3DRenderer::calculateSceneScalingFactors() { + // Margin for background (the default 0.10 makes it 10% larger to avoid + // selection ball being drawn inside background) + if (m_requestedMargin < 0.0f) { + m_hBackgroundMargin = 0.1f; + m_vBackgroundMargin = 0.1f; + } else { + m_hBackgroundMargin = m_requestedMargin; + m_vBackgroundMargin = m_requestedMargin; + } + if (m_polarGraph) { + float polarMargin = calculatePolarBackgroundMargin(); + m_hBackgroundMargin = qMax(m_hBackgroundMargin, polarMargin); + } + // Calculate scene scaling and translation factors m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min()); - m_areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min()); - m_areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min()); - m_scaleFactor = qMax(m_areaSize.width(), m_areaSize.height()); - m_scaleX = m_graphAspectRatio * m_areaSize.width() / m_scaleFactor; - m_scaleZ = m_graphAspectRatio * m_areaSize.height() / m_scaleFactor; - m_scaleXWithBackground = m_scaleX + backgroundMargin - 1.0f; - m_scaleZWithBackground = m_scaleZ + backgroundMargin - 1.0f; - - float factorScaler = 2.0f * m_graphAspectRatio / m_scaleFactor; - m_axisCacheX.setScale(factorScaler * m_areaSize.width()); - m_axisCacheZ.setScale(-factorScaler * m_areaSize.height()); - m_axisCacheX.setTranslate(-m_axisCacheX.scale() / 2.0f); - m_axisCacheZ.setTranslate(-m_axisCacheZ.scale() / 2.0f); + + float horizontalAspectRatio; + if (m_polarGraph) + horizontalAspectRatio = 1.0f; + else + horizontalAspectRatio = m_graphHorizontalAspectRatio; + + QSizeF areaSize; + if (horizontalAspectRatio == 0.0f) { + areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min()); + areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min()); + } else { + areaSize.setHeight(1.0f); + areaSize.setWidth(horizontalAspectRatio); + } + + float horizontalMaxDimension; + if (m_graphAspectRatio > 2.0f) { + horizontalMaxDimension = 2.0f; + m_scaleY = 2.0f / m_graphAspectRatio; + } else { + horizontalMaxDimension = m_graphAspectRatio; + m_scaleY = 1.0f; + } + if (m_polarGraph) + m_polarRadius = horizontalMaxDimension; + + float scaleFactor = qMax(areaSize.width(), areaSize.height()); + m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor; + m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor; + + m_scaleXWithBackground = m_scaleX + m_hBackgroundMargin; + m_scaleYWithBackground = m_scaleY + m_vBackgroundMargin; + m_scaleZWithBackground = m_scaleZ + m_hBackgroundMargin; + + m_axisCacheX.setScale(m_scaleX * 2.0f); + m_axisCacheY.setScale(m_scaleY * 2.0f); + m_axisCacheZ.setScale(-m_scaleZ * 2.0f); + m_axisCacheX.setTranslate(-m_scaleX); + m_axisCacheY.setTranslate(-m_scaleY); + m_axisCacheZ.setTranslate(m_scaleZ); + + updateCameraViewport(); + updateCustomItemPositions(); } void Surface3DRenderer::checkFlatSupport(SurfaceSeriesRenderCache *cache) @@ -2326,10 +2579,20 @@ void Surface3DRenderer::updateObjects(SurfaceSeriesRenderCache *cache, bool dime QSurfaceDataArray &dataArray = cache->dataArray(); const QRect &sampleSpace = cache->sampleSpace(); - if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->setUpData(dataArray, sampleSpace, dimensionChanged); - else - cache->surfaceObject()->setUpSmoothData(dataArray, sampleSpace, dimensionChanged); + const QSurface3DSeries *currentSeries = cache->series(); + QSurfaceDataProxy *dataProxy = currentSeries->dataProxy(); + const QSurfaceDataArray &array = *dataProxy->array(); + + if (cache->isFlatShadingEnabled()) { + cache->surfaceObject()->setUpData(dataArray, sampleSpace, dimensionChanged, m_polarGraph); + if (cache->surfaceTexture()) + cache->surfaceObject()->coarseUVs(array, dataArray); + } else { + cache->surfaceObject()->setUpSmoothData(dataArray, sampleSpace, dimensionChanged, + m_polarGraph); + if (cache->surfaceTexture()) + cache->surfaceObject()->smoothUVs(array, dataArray); + } } void Surface3DRenderer::updateSelectedPoint(const QPoint &position, QSurface3DSeries *series) @@ -2339,6 +2602,11 @@ void Surface3DRenderer::updateSelectedPoint(const QPoint &position, QSurface3DSe m_selectionDirty = true; } +void Surface3DRenderer::updateFlipHorizontalGrid(bool flip) +{ + m_flipHorizontalGrid = flip; +} + void Surface3DRenderer::resetClickedStatus() { m_clickedPosition = Surface3DController::invalidSelectionPosition(); @@ -2539,14 +2807,15 @@ void Surface3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality qual handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); -#endif } void Surface3DRenderer::updateTextures() { - // Do nothing, but required as it is pure virtual on parent + Abstract3DRenderer::updateTextures(); + + if (m_polarGraph) + calculateSceneScalingFactors(); } void Surface3DRenderer::updateSlicingActive(bool isSlicing) @@ -2556,12 +2825,13 @@ void Surface3DRenderer::updateSlicingActive(bool isSlicing) m_cachedIsSlicingActivated = isSlicing; - if (!m_cachedIsSlicingActivated) - initSelectionBuffer(); // We need to re-init selection buffer in case there has been a resize + if (!m_cachedIsSlicingActivated) { + // We need to re-init selection buffer in case there has been a resize + initSelectionBuffer(); + initCursorPositionBuffer(); + } -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); // Re-init depth buffer as well -#endif m_selectionDirty = true; @@ -2577,55 +2847,68 @@ void Surface3DRenderer::initShaders(const QString &vertexShader, const QString & Q_UNUSED(vertexShader); Q_UNUSED(fragmentShader); - // draw the shader for the surface according to smooth status, shadow and uniform color - if (m_surfaceFlatShader) - delete m_surfaceFlatShader; - if (m_surfaceSmoothShader) - delete m_surfaceSmoothShader; - if (m_surfaceSliceFlatShader) - delete m_surfaceSliceFlatShader; - if (m_surfaceSliceSmoothShader) - delete m_surfaceSliceSmoothShader; - -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentSurfaceShadowNoTex")); - } else { - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurface")); - } - m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurface")); - if (m_flatSupported) { + delete m_surfaceFlatShader; + delete m_surfaceSmoothShader; + delete m_surfaceTexturedSmoothShader; + delete m_surfaceTexturedFlatShader; + delete m_surfaceSliceFlatShader; + delete m_surfaceSliceSmoothShader; + + if (!m_isOpenGLES) { if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"), - QStringLiteral(":/shaders/fragmentSurfaceShadowFlat")); + m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentSurfaceShadowNoTex")); + m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"), + QStringLiteral(":/shaders/fragmentTexturedSurfaceShadow")); + } else { + m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurface")); + m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTexture")); + } + m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurface")); + if (m_flatSupported) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"), + QStringLiteral(":/shaders/fragmentSurfaceShadowFlat")); + m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"), + QStringLiteral(":/shaders/fragmentTexturedSurfaceShadowFlat")); + } else { + m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), + QStringLiteral(":/shaders/fragmentSurfaceFlat")); + m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), + QStringLiteral(":/shaders/fragmentSurfaceTexturedFlat")); + } + m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), + QStringLiteral(":/shaders/fragmentSurfaceFlat")); } else { - m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), - QStringLiteral(":/shaders/fragmentSurfaceFlat")); + m_surfaceFlatShader = 0; + m_surfaceSliceFlatShader = 0; + m_surfaceTexturedFlatShader = 0; } - m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), - QStringLiteral(":/shaders/fragmentSurfaceFlat")); } else { - m_surfaceFlatShader = 0; - m_surfaceSliceFlatShader = 0; + m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurfaceES2")); + m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurfaceES2")); + m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTextureES2")); + m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"), + QStringLiteral(":/shaders/fragmentTextureES2")); + m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurfaceES2")); + m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentSurfaceES2")); } -#else - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); -#endif + m_surfaceSmoothShader->initialize(); m_surfaceSliceSmoothShader->initialize(); + m_surfaceTexturedSmoothShader->initialize(); if (m_flatSupported) { m_surfaceFlatShader->initialize(); m_surfaceSliceFlatShader->initialize(); + m_surfaceTexturedFlatShader->initialize(); } } @@ -2660,45 +2943,33 @@ void Surface3DRenderer::initSurfaceShaders() handleShadowQualityChange(); } -void Surface3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) -{ - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); -} - -#if !defined(QT_OPENGL_ES_2) void Surface3DRenderer::initDepthShader() { - if (m_depthShader) + if (!m_isOpenGLES) { delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Surface3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); - m_textureHelper->deleteTexture(&m_depthModelTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + if (m_primarySubViewport.size().isEmpty()) + return; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), - m_depthFrameBuffer, - m_shadowQualityMultiplier); - m_textureHelper->fillDepthTexture(m_depthTexture, m_primarySubViewport.size(), - m_shadowQualityMultiplier, 1.0f); - m_depthModelTexture = m_textureHelper->createDepthTexture(m_primarySubViewport.size(), - m_shadowQualityMultiplier); - if (!m_depthTexture || !m_depthModelTexture) - lowerShadowQuality(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#endif QVector3D Surface3DRenderer::convertPositionToTranslation(const QVector3D &position, bool isAbsolute) @@ -2707,15 +2978,44 @@ QVector3D Surface3DRenderer::convertPositionToTranslation(const QVector3D &posit float yTrans = 0.0f; float zTrans = 0.0f; if (!isAbsolute) { - xTrans = m_axisCacheX.positionAt(position.x()); + if (m_polarGraph) { + calculatePolarXZ(position, xTrans, zTrans); + } else { + xTrans = m_axisCacheX.positionAt(position.x()); + zTrans = m_axisCacheZ.positionAt(position.z()); + } yTrans = m_axisCacheY.positionAt(position.y()); - zTrans = m_axisCacheZ.positionAt(position.z()); } else { xTrans = position.x() * m_scaleX; - yTrans = position.y(); - zTrans = position.z() * m_scaleZ; + yTrans = position.y() * m_scaleY; + zTrans = position.z() * -m_scaleZ; } return QVector3D(xTrans, yTrans, zTrans); } +void Surface3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels) +{ + Abstract3DRenderer::updateAxisLabels(orientation, labels); + + // Angular axis label dimensions affect the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Surface3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, bool visible) +{ + Abstract3DRenderer::updateAxisTitleVisibility(orientation, visible); + + // Angular axis title existence affects the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Surface3DRenderer::updateMargin(float margin) +{ + Abstract3DRenderer::updateMargin(margin); + calculateSceneScalingFactors(); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/surface3drenderer_p.h b/src/datavisualization/engine/surface3drenderer_p.h index efa8ff7e..57b6f9e6 100644 --- a/src/datavisualization/engine/surface3drenderer_p.h +++ b/src/datavisualization/engine/surface3drenderer_p.h @@ -51,19 +51,16 @@ private: ShaderHelper *m_backgroundShader; ShaderHelper *m_surfaceFlatShader; ShaderHelper *m_surfaceSmoothShader; + ShaderHelper *m_surfaceTexturedSmoothShader; + ShaderHelper *m_surfaceTexturedFlatShader; ShaderHelper *m_surfaceGridShader; ShaderHelper *m_surfaceSliceFlatShader; ShaderHelper *m_surfaceSliceSmoothShader; ShaderHelper *m_selectionShader; - ShaderHelper *m_labelShader; - GLfloat m_heightNormalizer; - GLfloat m_scaleFactor; - GLfloat m_scaleX; - GLfloat m_scaleZ; - GLfloat m_scaleXWithBackground; - GLfloat m_scaleZWithBackground; - GLuint m_depthTexture; - GLuint m_depthModelTexture; + float m_heightNormalizer; + float m_scaleX; + float m_scaleY; + float m_scaleZ; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; GLuint m_selectionDepthBuffer; @@ -73,13 +70,12 @@ private: bool m_selectionActive; AbstractRenderItem m_dummyRenderItem; GLint m_shadowQualityMultiplier; - QSizeF m_areaSize; - bool m_hasHeightAdjustmentChanged; QPoint m_selectedPoint; QSurface3DSeries *m_selectedSeries; QPoint m_clickedPosition; bool m_selectionTexturesDirty; GLuint m_noShadowTexture; + bool m_flipHorizontalGrid; public: explicit Surface3DRenderer(Surface3DController *controller); @@ -87,6 +83,7 @@ public: void updateData(); void updateSeries(const QList<QAbstract3DSeries *> &seriesList); + void updateSurfaceTextures(QVector<QSurface3DSeries *> seriesList); SeriesRenderCache *createNewCache(QAbstract3DSeries *series); void cleanCache(SeriesRenderCache *cache); void updateSelectionMode(QAbstract3DGraph::SelectionFlags mode); @@ -95,14 +92,22 @@ public: void updateScene(Q3DScene *scene); void updateSlicingActive(bool isSlicing); void updateSelectedPoint(const QPoint &position, QSurface3DSeries *series); + void updateFlipHorizontalGrid(bool flip); inline QPoint clickedPosition() const { return m_clickedPosition; } void resetClickedStatus(); QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); + void updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels); + void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible); + void updateMargin(float margin); void render(GLuint defaultFboHandle = 0); protected: void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); signals: void flatShadingSupportedChanged(bool supported); @@ -128,7 +133,6 @@ private: void calculateSceneScalingFactors(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionShaders(); void initSurfaceShaders(); void initSelectionBuffer(); @@ -136,13 +140,11 @@ private: void updateSelectionTextures(); void createSelectionTexture(SurfaceSeriesRenderCache *cache, uint &lastSelectionId); void idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a); - void fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a, int stride); + void fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a); void surfacePointSelected(const QPoint &point); void updateSelectionPoint(SurfaceSeriesRenderCache *cache, const QPoint &point, bool label); QPoint selectionIdToSurfacePoint(uint id); -#if !defined(QT_OPENGL_ES_2) void updateDepthBuffer(); -#endif void emitSelectedPointChanged(QPoint position); Q_DISABLE_COPY(Surface3DRenderer) diff --git a/src/datavisualization/engine/surfaceseriesrendercache.cpp b/src/datavisualization/engine/surfaceseriesrendercache.cpp index 1cce5288..f1ad752a 100644 --- a/src/datavisualization/engine/surfaceseriesrendercache.cpp +++ b/src/datavisualization/engine/surfaceseriesrendercache.cpp @@ -39,7 +39,8 @@ SurfaceSeriesRenderCache::SurfaceSeriesRenderCache(QAbstract3DSeries *series, m_sliceSelectionPointer(0), m_mainSelectionPointer(0), m_slicePointerActive(false), - m_mainPointerActive(false) + m_mainPointerActive(false), + m_surfaceTexture(0) { } @@ -62,8 +63,10 @@ void SurfaceSeriesRenderCache::populate(bool newSeries) void SurfaceSeriesRenderCache::cleanup(TextureHelper *texHelper) { - if (QOpenGLContext::currentContext()) + if (QOpenGLContext::currentContext()) { texHelper->deleteTexture(&m_selectionTexture); + texHelper->deleteTexture(&m_surfaceTexture); + } delete m_surfaceObj; delete m_sliceSurfaceObj; diff --git a/src/datavisualization/engine/surfaceseriesrendercache_p.h b/src/datavisualization/engine/surfaceseriesrendercache_p.h index b6254a75..4d04149f 100644 --- a/src/datavisualization/engine/surfaceseriesrendercache_p.h +++ b/src/datavisualization/engine/surfaceseriesrendercache_p.h @@ -85,6 +85,8 @@ public: inline bool slicePointerActive() const { return m_slicePointerActive; } inline void setMainPointerActivity(bool activity) { m_mainPointerActive = activity; } inline bool mainPointerActive() const { return m_mainPointerActive; } + inline void setSurfaceTexture(GLuint texture) { m_surfaceTexture = texture; } + inline GLuint surfaceTexture() const { return m_surfaceTexture; } protected: bool m_surfaceVisible; @@ -105,6 +107,7 @@ protected: SelectionPointer *m_mainSelectionPointer; bool m_slicePointerActive; bool m_mainPointerActive; + GLuint m_surfaceTexture; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/global/datavisualizationglobal_p.h b/src/datavisualization/global/datavisualizationglobal_p.h index abdac998..16bf7c6d 100644 --- a/src/datavisualization/global/datavisualizationglobal_p.h +++ b/src/datavisualization/global/datavisualizationglobal_p.h @@ -52,6 +52,7 @@ static const float gridLineOffset = 0.0035f; // Offset for lifting grid lines of // y position is added to the minimum height (or can be thought to be that much above or below the camera) static const QVector3D defaultLightPos = QVector3D(0.0f, 0.5f, 0.0f); static const QVector3D zeroVector = QVector3D(0.0f, 0.0f, 0.0f); +static const QVector3D oneVector = QVector3D(1.0f, 1.0f, 1.0f); static const QVector3D upVector = QVector3D(0.0f, 1.0f, 0.0f); static const QVector3D cameraDistanceVector = QVector3D(0.0f, 0.0f, cameraDistance); static const QQuaternion identityQuaternion; @@ -69,6 +70,7 @@ static const GLfloat gradientTextureWidth = 2.0f; static const GLfloat uniformTextureHeight = 64.0f; static const GLfloat uniformTextureWidth = 2.0f; static const GLfloat labelMargin = 0.05f; +static const GLfloat gridLineWidth = 0.005f; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/global/global.pri b/src/datavisualization/global/global.pri index 00a3eb65..a43ffb20 100644 --- a/src/datavisualization/global/global.pri +++ b/src/datavisualization/global/global.pri @@ -1,3 +1,5 @@ HEADERS += \ $$PWD/qdatavisualizationglobal.h \ $$PWD/datavisualizationglobal_p.h + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/global/qdatavisualizationglobal.h b/src/datavisualization/global/qdatavisualizationglobal.h index c637f408..a7f96361 100644 --- a/src/datavisualization/global/qdatavisualizationglobal.h +++ b/src/datavisualization/global/qdatavisualizationglobal.h @@ -21,11 +21,11 @@ #include <QtCore/qglobal.h> -#define QT_DATAVISUALIZATION_VERSION_STR "1.1.1" +#define QT_DATAVISUALIZATION_VERSION_STR "1.2.0" /* QT_DATAVISUALIZATION_VERSION is (major << 16) + (minor << 8) + patch. */ -#define QT_DATAVISUALIZATION_VERSION 0x010101 +#define QT_DATAVISUALIZATION_VERSION 0x010200 /* can be used like #if (QT_DATAVISUALIZATION_VERSION >= QT_DATAVISUALIZATION_VERSION_CHECK(1, 0, 0)) */ diff --git a/src/datavisualization/input/input.pri b/src/datavisualization/input/input.pri index 5a4c4a76..f6b9fd6a 100644 --- a/src/datavisualization/input/input.pri +++ b/src/datavisualization/input/input.pri @@ -10,3 +10,5 @@ SOURCES += \ $$PWD/qabstract3dinputhandler.cpp \ $$PWD/q3dinputhandler.cpp \ $$PWD/qtouch3dinputhandler.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/input/q3dinputhandler.cpp b/src/datavisualization/input/q3dinputhandler.cpp index f0044096..43cee84e 100644 --- a/src/datavisualization/input/q3dinputhandler.cpp +++ b/src/datavisualization/input/q3dinputhandler.cpp @@ -18,19 +18,20 @@ #include "datavisualizationglobal_p.h" #include "q3dinputhandler_p.h" +#include "abstract3dcontroller_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const int minZoomLevel = 10; -const int halfSizeZoomLevel = 50; -const int oneToOneZoomLevel = 100; -const int maxZoomLevel = 500; +static const int halfSizeZoomLevel = 50; +static const int oneToOneZoomLevel = 100; +static const int driftTowardCenterLevel = 175; +static const float wheelZoomDrift = 0.1f; -const int nearZoomRangeDivider = 12; -const int midZoomRangeDivider = 60; -const int farZoomRangeDivider = 120; +static const int nearZoomRangeDivider = 12; +static const int midZoomRangeDivider = 60; +static const int farZoomRangeDivider = 120; -const float rotationSpeed = 100.0f; +static const float rotationSpeed = 100.0f; /*! * \class Q3DInputHandler @@ -55,12 +56,61 @@ const float rotationSpeed = 100.0f; * \l {QAbstract3DGraph::selectionMode}{selection mode}. * \row * \li Mouse wheel - * \li Zoom in/out within default range (10...500%). + * \li Zoom in/out within the allowable zoom range set for Q3DCamera. * \row * \li Left click on the primary view when the secondary view is visible * \li Closes the secondary view. * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. * \endtable + * + * Rotation, zoom, and selection can each be individually disabled using + * corresponding properties of this class. + */ + +/*! + * \qmltype InputHandler3D + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates Q3DInputHandler + * \brief Basic wheel mouse based input handler. + * + * InputHandler3D is the basic input handler for wheel mouse type of input devices. + * + * See Q3DInputHandler documentation for more details. + */ + +/*! + * \qmlproperty bool InputHandler3D::rotationEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph rotation. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::zoomEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph zooming. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::selectionEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows selection from the graph. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::zoomAtTargetEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if zooming changes the camera target to the position of the input + * at the time of the zoom. + * Defaults to \c{true}. */ /*! @@ -92,29 +142,35 @@ void Q3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos Q_UNUSED(mousePos); #else if (Qt::LeftButton == event->button()) { - if (scene()->isSlicingActive()) { - if (scene()->isPointInPrimarySubView(mousePos)) + if (isSelectionEnabled()) { + if (scene()->isSlicingActive()) { + if (scene()->isPointInPrimarySubView(mousePos)) + setInputView(InputViewOnPrimary); + else if (scene()->isPointInSecondarySubView(mousePos)) + setInputView(InputViewOnSecondary); + else + setInputView(InputViewNone); + } else { + // update mouse positions to prevent jumping when releasing or repressing a button + setInputPosition(mousePos); + scene()->setSelectionQueryPosition(mousePos); setInputView(InputViewOnPrimary); - else if (scene()->isPointInSecondarySubView(mousePos)) - setInputView(InputViewOnSecondary); - else - setInputView(InputViewNone); - } else { - // update mouse positions to prevent jumping when releasing or repressing a button - setInputPosition(mousePos); - scene()->setSelectionQueryPosition(mousePos); - setInputView(InputViewOnPrimary); - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + } } } else if (Qt::MiddleButton == event->button()) { - // reset rotations - setInputPosition(QPoint(0, 0)); + if (isRotationEnabled()) { + // reset rotations + setInputPosition(QPoint(0, 0)); + } } else if (Qt::RightButton == event->button()) { - // disable rotating when in slice view - if (!scene()->isSlicingActive()) - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; - // update mouse positions to prevent jumping when releasing or repressing a button - setInputPosition(mousePos); + if (isRotationEnabled()) { + // disable rotating when in slice view + if (!scene()->isSlicingActive()) + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; + // update mouse positions to prevent jumping when releasing or repressing a button + setInputPosition(mousePos); + } } #endif } @@ -148,7 +204,8 @@ void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) #if defined(Q_OS_IOS) Q_UNUSED(mousePos); #else - if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState) { + if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState + && isRotationEnabled()) { // Calculate mouse movement since last frame float xRotation = scene()->activeCamera()->xRotation(); float yRotation = scene()->activeCamera()->yRotation(); @@ -174,34 +231,191 @@ void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) */ void Q3DInputHandler::wheelEvent(QWheelEvent *event) { - // disable zooming if in slice view - if (scene()->isSlicingActive()) - return; + if (isZoomEnabled()) { + // disable zooming if in slice view + if (scene()->isSlicingActive()) + return; - // Adjust zoom level based on what zoom range we're in. - int zoomLevel = scene()->activeCamera()->zoomLevel(); - if (zoomLevel > oneToOneZoomLevel) - zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; - else if (zoomLevel > halfSizeZoomLevel) - zoomLevel += event->angleDelta().y() / midZoomRangeDivider; - else - zoomLevel += event->angleDelta().y() / farZoomRangeDivider; - if (zoomLevel > maxZoomLevel) - zoomLevel = maxZoomLevel; - else if (zoomLevel < minZoomLevel) - zoomLevel = minZoomLevel; + // Adjust zoom level based on what zoom range we're in. + Q3DCamera *camera = scene()->activeCamera(); + int zoomLevel = int(camera->zoomLevel()); + const int minZoomLevel = int(camera->minZoomLevel()); + const int maxZoomLevel = int(camera->maxZoomLevel()); + if (zoomLevel > oneToOneZoomLevel) + zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; + else if (zoomLevel > halfSizeZoomLevel) + zoomLevel += event->angleDelta().y() / midZoomRangeDivider; + else + zoomLevel += event->angleDelta().y() / farZoomRangeDivider; + zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); - scene()->activeCamera()->setZoomLevel(zoomLevel); + if (isZoomAtTargetEnabled()) { + scene()->setGraphPositionQuery(event->pos()); + d_ptr->m_zoomAtTargetPending = true; + // If zoom at target is enabled, we don't want to zoom yet, as that causes + // jitter. Instead, we zoom next frame, when we apply the camera position. + d_ptr->m_requestedZoomLevel = zoomLevel; + d_ptr->m_driftMultiplier = wheelZoomDrift; + } else { + camera->setZoomLevel(zoomLevel); + } + } +} + +/*! + * \property Q3DInputHandler::rotationEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph rotation. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setRotationEnabled(bool enable) +{ + if (d_ptr->m_rotationEnabled != enable) { + d_ptr->m_rotationEnabled = enable; + emit rotationEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isRotationEnabled() const +{ + return d_ptr->m_rotationEnabled; +} + +/*! + * \property Q3DInputHandler::zoomEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph zooming. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setZoomEnabled(bool enable) +{ + if (d_ptr->m_zoomEnabled != enable) { + d_ptr->m_zoomEnabled = enable; + emit zoomEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isZoomEnabled() const +{ + return d_ptr->m_zoomEnabled; +} + +/*! + * \property Q3DInputHandler::selectionEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows selection from the graph. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setSelectionEnabled(bool enable) +{ + if (d_ptr->m_selectionEnabled != enable) { + d_ptr->m_selectionEnabled = enable; + emit selectionEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isSelectionEnabled() const +{ + return d_ptr->m_selectionEnabled; +} + +/*! + * \property Q3DInputHandler::zoomAtTargetEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if zooming should change the camera target so that the zoomed point + * of the graph stays at the same location after the zoom. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setZoomAtTargetEnabled(bool enable) +{ + if (d_ptr->m_zoomAtTargetEnabled != enable) { + d_ptr->m_zoomAtTargetEnabled = enable; + emit zoomAtTargetEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isZoomAtTargetEnabled() const +{ + return d_ptr->m_zoomAtTargetEnabled; } Q3DInputHandlerPrivate::Q3DInputHandlerPrivate(Q3DInputHandler *q) : q_ptr(q), - m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone) + m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone), + m_rotationEnabled(true), + m_zoomEnabled(true), + m_selectionEnabled(true), + m_zoomAtTargetEnabled(true), + m_zoomAtTargetPending(false), + m_controller(0), + m_requestedZoomLevel(0.0f), + m_driftMultiplier(0.0f) { + QObject::connect(q, &QAbstract3DInputHandler::sceneChanged, + this, &Q3DInputHandlerPrivate::handleSceneChange); } Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate() { } +void Q3DInputHandlerPrivate::handleSceneChange(Q3DScene *scene) +{ + if (scene) { + if (m_controller) { + QObject::disconnect(m_controller, &Abstract3DController::queriedGraphPositionChanged, + this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); + } + + m_controller = qobject_cast<Abstract3DController *>(scene->parent()); + + if (m_controller) { + QObject::connect(m_controller, &Abstract3DController::queriedGraphPositionChanged, + this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); + } + } +} + +void Q3DInputHandlerPrivate::handleQueriedGraphPositionChange() +{ + if (m_zoomAtTargetPending) { + // Check if the zoom point is on graph + QVector3D newTarget = m_controller->queriedGraphPosition(); + float currentZoom = m_requestedZoomLevel; + float previousZoom = q_ptr->scene()->activeCamera()->zoomLevel(); + q_ptr->scene()->activeCamera()->setZoomLevel(currentZoom); + float diffAdj = 0.0f; + + // If zooming in/out outside the graph, or zooming out after certain point, + // move towards the center. + if ((qAbs(newTarget.x()) > 1.0f + || qAbs(newTarget.y()) > 1.0f + || qAbs(newTarget.z()) > 1.0f) + || (previousZoom > currentZoom && currentZoom <= driftTowardCenterLevel)) { + newTarget = zeroVector; + // Add some extra correction so that we actually reach the center eventually + diffAdj = m_driftMultiplier; + if (previousZoom > currentZoom) + diffAdj *= 2.0f; // Correct towards center little more when zooming out + } + + float zoomFraction = 1.0f - (previousZoom / currentZoom); + + // Adjust camera towards the zoom point, attempting to keep the cursor at same graph point + QVector3D oldTarget = q_ptr->scene()->activeCamera()->target(); + QVector3D origDiff = newTarget - oldTarget; + QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj); + if (diff.length() > origDiff.length()) + diff = origDiff; + q_ptr->scene()->activeCamera()->setTarget(oldTarget + diff); + + if (q_ptr->scene()->selectionQueryPosition() == Q3DScene::invalidSelectionPoint()) + m_zoomAtTargetPending = false; + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/input/q3dinputhandler.h b/src/datavisualization/input/q3dinputhandler.h index 118bd829..5423b4ea 100644 --- a/src/datavisualization/input/q3dinputhandler.h +++ b/src/datavisualization/input/q3dinputhandler.h @@ -28,17 +28,36 @@ class Q3DInputHandlerPrivate; class QT_DATAVISUALIZATION_EXPORT Q3DInputHandler : public QAbstract3DInputHandler { Q_OBJECT + Q_PROPERTY(bool rotationEnabled READ isRotationEnabled WRITE setRotationEnabled NOTIFY rotationEnabledChanged) + Q_PROPERTY(bool zoomEnabled READ isZoomEnabled WRITE setZoomEnabled NOTIFY zoomEnabledChanged) + Q_PROPERTY(bool selectionEnabled READ isSelectionEnabled WRITE setSelectionEnabled NOTIFY selectionEnabledChanged) + Q_PROPERTY(bool zoomAtTargetEnabled READ isZoomAtTargetEnabled WRITE setZoomAtTargetEnabled NOTIFY zoomAtTargetEnabledChanged) public: explicit Q3DInputHandler(QObject *parent = 0); virtual ~Q3DInputHandler(); + void setRotationEnabled(bool enable); + bool isRotationEnabled() const; + void setZoomEnabled(bool enable); + bool isZoomEnabled() const; + void setSelectionEnabled(bool enable); + bool isSelectionEnabled() const; + void setZoomAtTargetEnabled(bool enable); + bool isZoomAtTargetEnabled() const; + // Input event listeners virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos); virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos); virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos); virtual void wheelEvent(QWheelEvent *event); +signals: + void rotationEnabledChanged(bool enable); + void zoomEnabledChanged(bool enable); + void selectionEnabledChanged(bool enable); + void zoomAtTargetEnabledChanged(bool enable); + private: Q_DISABLE_COPY(Q3DInputHandler) diff --git a/src/datavisualization/input/q3dinputhandler_p.h b/src/datavisualization/input/q3dinputhandler_p.h index a9b27307..79b1c8dd 100644 --- a/src/datavisualization/input/q3dinputhandler_p.h +++ b/src/datavisualization/input/q3dinputhandler_p.h @@ -34,15 +34,36 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -class Q3DInputHandlerPrivate +class Abstract3DController; + +class Q3DInputHandlerPrivate : public QObject { + Q_OBJECT public: Q3DInputHandlerPrivate(Q3DInputHandler *q); ~Q3DInputHandlerPrivate(); -public: +public slots: + void handleSceneChange(Q3DScene *scene); + void handleQueriedGraphPositionChange(); + +private: Q3DInputHandler *q_ptr; +protected: QAbstract3DInputHandlerPrivate::InputState m_inputState; + + bool m_rotationEnabled; + bool m_zoomEnabled; + bool m_selectionEnabled; + bool m_zoomAtTargetEnabled; + bool m_zoomAtTargetPending; + + Abstract3DController *m_controller; // Not owned + + float m_requestedZoomLevel; + float m_driftMultiplier; + + friend class Q3DInputHandler; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/input/qtouch3dinputhandler.cpp b/src/datavisualization/input/qtouch3dinputhandler.cpp index 30f31d4a..357a6f3e 100644 --- a/src/datavisualization/input/qtouch3dinputhandler.cpp +++ b/src/datavisualization/input/qtouch3dinputhandler.cpp @@ -22,17 +22,16 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const float maxTapAndHoldJitter = 20.0f; -const int maxPinchJitter = 10; +static const float maxTapAndHoldJitter = 20.0f; +static const int maxPinchJitter = 10; #if defined (Q_OS_ANDROID) || defined(Q_OS_IOS) -const int maxSelectionJitter = 10; +static const int maxSelectionJitter = 10; #else -const int maxSelectionJitter = 5; +static const int maxSelectionJitter = 5; #endif -const int tapAndHoldTime = 250; -const float rotationSpeed = 200.0f; -const int minZoomLevel = 10; -const int maxZoomLevel = 500; +static const int tapAndHoldTime = 250; +static const float rotationSpeed = 200.0f; +static const float touchZoomDrift = 0.02f; /*! * \class QTouch3DInputHandler @@ -60,12 +59,29 @@ const int maxZoomLevel = 500; * \li Same as tap. * \row * \li Pinch - * \li Zoom in/out within default range (10...500%). + * \li Zoom in/out within the allowable zoom range set for Q3DCamera. * \row * \li Tap on the primary view when the secondary view is visible * \li Closes the secondary view. * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. * \endtable + * + * Rotation, zoom, and selection can each be individually disabled using + * corresponding Q3DInputHandler properties. + */ + +/*! + * \qmltype TouchInputHandler3D + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates QTouch3DInputHandler + * \inherits InputHandler3D + * \brief Basic touch display based input handler. + * + * TouchInputHandler3D is the basic input handler for touch screen devices. + * + * See QTouch3DInputHandler documentation for more details. */ /*! @@ -97,36 +113,46 @@ void QTouch3DInputHandler::touchEvent(QTouchEvent *event) if (!scene()->isSlicingActive() && points.count() == 2) { d_ptr->m_holdTimer->stop(); QPointF distance = points.at(0).pos() - points.at(1).pos(); - d_ptr->handlePinchZoom(distance.manhattanLength()); + QPoint midPoint = ((points.at(0).pos() + points.at(1).pos()) / 2.0).toPoint(); + d_ptr->handlePinchZoom(distance.manhattanLength(), midPoint); } else if (points.count() == 1) { QPointF pointerPos = points.at(0).pos(); if (event->type() == QEvent::TouchBegin) { // Flush input state d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; if (scene()->isSlicingActive()) { - if (scene()->isPointInPrimarySubView(pointerPos.toPoint())) - setInputView(InputViewOnPrimary); - else if (scene()->isPointInSecondarySubView(pointerPos.toPoint())) - setInputView(InputViewOnSecondary); - else - setInputView(InputViewNone); + if (isSelectionEnabled()) { + if (scene()->isPointInPrimarySubView(pointerPos.toPoint())) + setInputView(InputViewOnPrimary); + else if (scene()->isPointInSecondarySubView(pointerPos.toPoint())) + setInputView(InputViewOnSecondary); + else + setInputView(InputViewNone); + } } else { // Handle possible tap-and-hold selection - d_ptr->m_startHoldPos = pointerPos; - d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos; - d_ptr->m_holdTimer->start(); - setInputView(InputViewOnPrimary); + if (isSelectionEnabled()) { + d_ptr->m_startHoldPos = pointerPos; + d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos; + d_ptr->m_holdTimer->start(); + setInputView(InputViewOnPrimary); + } // Start rotating - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; - setInputPosition(pointerPos.toPoint()); + if (isRotationEnabled()) { + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; + setInputPosition(pointerPos.toPoint()); + setInputView(InputViewOnPrimary); + } } } else if (event->type() == QEvent::TouchEnd) { setInputView(InputViewNone); d_ptr->m_holdTimer->stop(); // Handle possible selection if (!scene()->isSlicingActive() - && QAbstract3DInputHandlerPrivate::InputStatePinching != d_ptr->m_inputState) + && QAbstract3DInputHandlerPrivate::InputStatePinching + != d_ptr->m_inputState) { d_ptr->handleSelection(pointerPos); + } } else if (event->type() == QEvent::TouchUpdate) { if (!scene()->isSlicingActive()) { d_ptr->m_touchHoldPos = pointerPos; @@ -140,7 +166,8 @@ void QTouch3DInputHandler::touchEvent(QTouchEvent *event) } QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q) - : q_ptr(q), + : Q3DInputHandlerPrivate(q), + q_ptr(q), m_holdTimer(0), m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone) { @@ -156,54 +183,71 @@ QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate() delete m_holdTimer; } -void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance) +void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos) { - int newDistance = distance; - int prevDist = q_ptr->prevDistance(); - if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter) - return; - m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching; - Q3DCamera *camera = q_ptr->scene()->activeCamera(); - int zoomLevel = camera->zoomLevel(); - float zoomRate = qSqrt(qSqrt(zoomLevel)); - if (newDistance > prevDist) - zoomLevel += zoomRate; - else - zoomLevel -= zoomRate; - if (zoomLevel > maxZoomLevel) - zoomLevel = maxZoomLevel; - else if (zoomLevel < minZoomLevel) - zoomLevel = minZoomLevel; - camera->setZoomLevel(zoomLevel); - q_ptr->setPrevDistance(newDistance); + if (q_ptr->isZoomEnabled()) { + int newDistance = distance; + int prevDist = q_ptr->prevDistance(); + if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter) + return; + m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching; + Q3DCamera *camera = q_ptr->scene()->activeCamera(); + int zoomLevel = int(camera->zoomLevel()); + const int minZoomLevel = int(camera->minZoomLevel()); + const int maxZoomLevel = int(camera->maxZoomLevel()); + float zoomRate = qSqrt(qSqrt(zoomLevel)); + if (newDistance > prevDist) + zoomLevel += zoomRate; + else + zoomLevel -= zoomRate; + zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); + + if (q_ptr->isZoomAtTargetEnabled()) { + q_ptr->scene()->setGraphPositionQuery(pos); + m_zoomAtTargetPending = true; + // If zoom at target is enabled, we don't want to zoom yet, as that causes + // jitter. Instead, we zoom next frame, when we apply the camera position. + m_requestedZoomLevel = zoomLevel; + m_driftMultiplier = touchZoomDrift; + } else { + camera->setZoomLevel(zoomLevel); + } + + q_ptr->setPrevDistance(newDistance); + } } void QTouch3DInputHandlerPrivate::handleTapAndHold() { - QPointF distance = m_startHoldPos - m_touchHoldPos; - if (distance.manhattanLength() < maxTapAndHoldJitter) { - q_ptr->setInputPosition(m_touchHoldPos.toPoint()); - q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint()); - m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + if (q_ptr->isSelectionEnabled()) { + QPointF distance = m_startHoldPos - m_touchHoldPos; + if (distance.manhattanLength() < maxTapAndHoldJitter) { + q_ptr->setInputPosition(m_touchHoldPos.toPoint()); + q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint()); + m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + } } } void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position) { - QPointF distance = m_startHoldPos - position; - if (distance.manhattanLength() < maxSelectionJitter) { - m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; - q_ptr->scene()->setSelectionQueryPosition(position.toPoint()); - } else { - m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; - q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone); + if (q_ptr->isSelectionEnabled()) { + QPointF distance = m_startHoldPos - position; + if (distance.manhattanLength() < maxSelectionJitter) { + m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + q_ptr->scene()->setSelectionQueryPosition(position.toPoint()); + } else { + m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; + q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone); + } + q_ptr->setPreviousInputPos(position.toPoint()); } - q_ptr->setPreviousInputPos(position.toPoint()); } void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position) { - if (QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) { + if (q_ptr->isRotationEnabled() + && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) { Q3DScene *scene = q_ptr->scene(); Q3DCamera *camera = scene->activeCamera(); float xRotation = camera->xRotation(); diff --git a/src/datavisualization/input/qtouch3dinputhandler_p.h b/src/datavisualization/input/qtouch3dinputhandler_p.h index 613b5f28..b01904ca 100644 --- a/src/datavisualization/input/qtouch3dinputhandler_p.h +++ b/src/datavisualization/input/qtouch3dinputhandler_p.h @@ -19,7 +19,7 @@ #ifndef QTOUCH3DINPUTHANDLER_P_H #define QTOUCH3DINPUTHANDLER_P_H -#include "qabstract3dinputhandler_p.h" +#include "q3dinputhandler_p.h" #include "qtouch3dinputhandler.h" class QTimer; @@ -28,7 +28,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION class QAbstract3DInputHandler; -class QTouch3DInputHandlerPrivate : public QObject +class QTouch3DInputHandlerPrivate : public Q3DInputHandlerPrivate { Q_OBJECT @@ -36,13 +36,14 @@ public: QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q); ~QTouch3DInputHandlerPrivate(); - void handlePinchZoom(float distance); + void handlePinchZoom(float distance, const QPoint &pos); void handleTapAndHold(); void handleSelection(const QPointF &position); void handleRotation(const QPointF &position); -public: +private: QTouch3DInputHandler *q_ptr; +public: QTimer *m_holdTimer; QAbstract3DInputHandlerPrivate::InputState m_inputState; QPointF m_startHoldPos; diff --git a/src/datavisualization/theme/q3dtheme.cpp b/src/datavisualization/theme/q3dtheme.cpp index 97ff8f81..a1dfe8b3 100644 --- a/src/datavisualization/theme/q3dtheme.cpp +++ b/src/datavisualization/theme/q3dtheme.cpp @@ -323,7 +323,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty real Theme3D::lightStrength * - * Specular light strength for the whole graph. Value must be between 0.0 and 1.0. + * Specular light strength for the whole graph. Value must be between 0.0 and 10.0. */ /*! @@ -335,7 +335,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty real Theme3D::highlightLightStrength * - * Specular light strength for highlighted objects. Value must be between 0.0 and 1.0. + * Specular light strength for highlighted objects. Value must be between 0.0 and 10.0. */ /*! @@ -679,7 +679,7 @@ QLinearGradient Q3DTheme::multiHighlightGradient() const /*! * \property Q3DTheme::lightStrength * - * Specular light strength for the whole graph. Value must be 0.0f and 10.0f. + * Specular light strength for the whole graph. Value must be between 0.0f and 10.0f. */ void Q3DTheme::setLightStrength(float strength) { diff --git a/src/datavisualization/theme/theme.pri b/src/datavisualization/theme/theme.pri index 1de3035a..cf7b434c 100644 --- a/src/datavisualization/theme/theme.pri +++ b/src/datavisualization/theme/theme.pri @@ -6,3 +6,5 @@ HEADERS += \ SOURCES += \ $$PWD/q3dtheme.cpp \ $$PWD/thememanager.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/utils/abstractobjecthelper.cpp b/src/datavisualization/utils/abstractobjecthelper.cpp index c350d096..40b3a45e 100644 --- a/src/datavisualization/utils/abstractobjecthelper.cpp +++ b/src/datavisualization/utils/abstractobjecthelper.cpp @@ -28,6 +28,7 @@ AbstractObjectHelper::AbstractObjectHelper() m_indexCount(0), m_meshDataLoaded(false) { + initializeOpenGLFunctions(); } AbstractObjectHelper::~AbstractObjectHelper() diff --git a/src/datavisualization/utils/abstractobjecthelper_p.h b/src/datavisualization/utils/abstractobjecthelper_p.h index c1618909..99f85dab 100644 --- a/src/datavisualization/utils/abstractobjecthelper_p.h +++ b/src/datavisualization/utils/abstractobjecthelper_p.h @@ -38,11 +38,11 @@ class AbstractObjectHelper: protected QOpenGLFunctions protected: AbstractObjectHelper(); public: - ~AbstractObjectHelper(); + virtual ~AbstractObjectHelper(); GLuint vertexBuf(); GLuint normalBuf(); - GLuint uvBuf(); + virtual GLuint uvBuf(); GLuint elementBuf(); GLuint indexCount(); GLuint indicesType(); diff --git a/src/datavisualization/utils/objecthelper.cpp b/src/datavisualization/utils/objecthelper.cpp index a66e0f7e..4240d6f5 100644 --- a/src/datavisualization/utils/objecthelper.cpp +++ b/src/datavisualization/utils/objecthelper.cpp @@ -111,7 +111,6 @@ ObjectHelper *ObjectHelper::getObjectHelper(const Abstract3DRenderer *cacheId, void ObjectHelper::load() { - initializeOpenGLFunctions(); if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_vertexbuffer); @@ -122,6 +121,10 @@ void ObjectHelper::load() m_indexedVertices.clear(); m_indexedUVs.clear(); m_indexedNormals.clear(); + m_vertexbuffer = 0; + m_uvbuffer = 0; + m_normalbuffer = 0; + m_elementbuffer = 0; } QVector<QVector3D> vertices; QVector<QVector2D> uvs; diff --git a/src/datavisualization/utils/objecthelper_p.h b/src/datavisualization/utils/objecthelper_p.h index c84f53bd..b00e5d8d 100644 --- a/src/datavisualization/utils/objecthelper_p.h +++ b/src/datavisualization/utils/objecthelper_p.h @@ -41,7 +41,7 @@ class ObjectHelper : public AbstractObjectHelper private: ObjectHelper(const QString &objectFile); public: - ~ObjectHelper(); + virtual ~ObjectHelper(); static void resetObjectHelper(const Abstract3DRenderer *cacheId, ObjectHelper *&obj, const QString &meshFile); diff --git a/src/datavisualization/utils/qutils.h b/src/datavisualization/utils/qutils.h index 43375a9c..d4acfc99 100644 --- a/src/datavisualization/utils/qutils.h +++ b/src/datavisualization/utils/qutils.h @@ -28,17 +28,21 @@ inline static QSurfaceFormat qDefaultSurfaceFormat(bool antialias = true) QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setStencilBufferSize(8); surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); -#if !defined(QT_OPENGL_ES_2) - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGL); -#else + surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); +#if defined(QT_OPENGL_ES_2) // Antialias not supported for ES antialias = false; - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); + surfaceFormat.setRedBufferSize(8); + surfaceFormat.setBlueBufferSize(8); + surfaceFormat.setGreenBufferSize(8); #endif if (antialias) surfaceFormat.setSamples(8); + else + surfaceFormat.setSamples(0); return surfaceFormat; } diff --git a/src/datavisualization/utils/scatterobjectbufferhelper.cpp b/src/datavisualization/utils/scatterobjectbufferhelper.cpp index d68b9df4..44c84ae0 100644 --- a/src/datavisualization/utils/scatterobjectbufferhelper.cpp +++ b/src/datavisualization/utils/scatterobjectbufferhelper.cpp @@ -20,12 +20,14 @@ #include "objecthelper_p.h" #include <QtGui/QVector2D> #include <QtGui/QMatrix4x4> +#include <QtCore/qmath.h> QT_BEGIN_NAMESPACE_DATAVISUALIZATION const GLfloat itemScaler = 3.0f; ScatterObjectBufferHelper::ScatterObjectBufferHelper() + : m_scaleY(0.0f) { m_indicesType = GL_UNSIGNED_INT; } @@ -36,34 +38,40 @@ ScatterObjectBufferHelper::~ScatterObjectBufferHelper() void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal dotScale) { - initializeOpenGLFunctions(); - m_meshDataLoaded = false; m_indexCount = 0; ObjectHelper *dotObj = cache->object(); - ScatterRenderItemArray &renderArray = cache->renderArray(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); const uint renderArraySize = renderArray.size(); + if (renderArraySize == 0) return; // No use to go forward - uint itemCount = renderArraySize; + + uint itemCount = 0; QQuaternion seriesRotation(cache->meshRotation()); + if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_vertexbuffer); glDeleteBuffers(1, &m_uvbuffer); glDeleteBuffers(1, &m_normalbuffer); glDeleteBuffers(1, &m_elementbuffer); + m_vertexbuffer = 0; + m_uvbuffer = 0; + m_normalbuffer = 0; + m_elementbuffer = 0; } + // Index vertices const QVector<unsigned short> indices = dotObj->indices(); const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); const QVector<QVector2D> indexed_uvs = dotObj->indexedUVs(); const QVector<QVector3D> indexed_normals = dotObj->indexedNormals(); - int indicesCount = indices.count(); - int verticeCount = indexed_vertices.count(); - int uvsCount = indexed_uvs.count(); - int normalsCount = indexed_normals.count(); + const int indicesCount = indices.count(); + const int verticeCount = indexed_vertices.count(); + const int uvsCount = indexed_uvs.count(); + const int normalsCount = indexed_normals.count(); float itemSize = cache->itemSize() / itemScaler; if (itemSize == 0.0f) @@ -89,47 +97,58 @@ void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal buffered_indices.resize(indicesCount * renderArraySize); buffered_vertices.resize(verticeCount * renderArraySize); - buffered_uvs.resize(uvsCount * renderArraySize); buffered_normals.resize(normalsCount * renderArraySize); - uint pos = 0; + buffered_uvs.resize(uvsCount * renderArraySize); + + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + createRangeGradientUVs(cache, buffered_uvs); + else if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) + createObjectGradientUVs(cache, buffered_uvs, indexed_vertices); + + QVector2D dummyUV(0.0f, 0.0f); + + cache->bufferIndices().resize(renderArraySize); for (uint i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; - if (!item.isVisible()) { - itemCount--; + const ScatterRenderItem &item = renderArray.at(i); + if (!item.isVisible()) continue; - } + else + cache->bufferIndices()[i] = itemCount; - int offset = pos * verticeCount; + int offset = itemCount * verticeCount; if (item.rotation().isIdentity()) { - for (int j = 0; j < verticeCount; j++) + for (int j = 0; j < verticeCount; j++) { buffered_vertices[j + offset] = scaled_vertices[j] + item.translation(); + buffered_normals[j + offset] = indexed_normals[j]; + } } else { QMatrix4x4 matrix; - matrix.rotate(seriesRotation * item.rotation()); - modelMatrix = matrix.transposed(); - modelMatrix.scale(modelScaler); + QQuaternion totalRotation = seriesRotation * item.rotation(); + matrix.rotate(totalRotation); + matrix.scale(modelScaler); + QMatrix4x4 itModelMatrix = matrix.inverted(); + modelMatrix = matrix.transposed(); // Because of row-column major difference - for (int j = 0; j < verticeCount; j++) + for (int j = 0; j < verticeCount; j++) { buffered_vertices[j + offset] = indexed_vertices[j] * modelMatrix + item.translation(); + buffered_normals[j + offset] = indexed_normals[j] * itModelMatrix; + } } - offset = pos * normalsCount; - for (int j = 0; j < normalsCount; j++) - buffered_normals[j + offset] = indexed_normals[j]; - - offset = pos * uvsCount; - for (int j = 0; j < uvsCount; j++) - buffered_uvs[j + offset] = indexed_uvs[j]; + if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) { + offset = itemCount * uvsCount; + for (int j = 0; j < uvsCount; j++) + buffered_uvs[j + offset] = dummyUV; + } - int offsetVertice = i * verticeCount; - offset = pos * indicesCount; - for (int j = 0; j < indicesCount; j++) { + int offsetVertice = itemCount * verticeCount; + offset = itemCount * indicesCount; + for (int j = 0; j < indicesCount; j++) buffered_indices[j + offset] = GLuint(indices[j] + offsetVertice); - } - pos++; + itemCount++; } m_indexCount = indicesCount * itemCount; @@ -164,15 +183,128 @@ void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal } } -void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal dotScale) +void ScatterObjectBufferHelper::updateUVs(ScatterSeriesRenderCache *cache) +{ + ObjectHelper *dotObj = cache->object(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + + if (!updateSize) + return; + + QVector<QVector2D> buffered_uvs; + buffered_uvs.resize(uvsCount * updateSize); + + uint itemCount = 0; + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) { + itemCount = createRangeGradientUVs(cache, buffered_uvs); + } else if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) { + const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); + itemCount = createObjectGradientUVs(cache, buffered_uvs, indexed_vertices); + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + int itemSize = uvsCount * sizeof(QVector2D); + if (cache->updateIndices().size()) { + int pos = 0; + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + if (renderArray.at(index).isVisible()) { + int dataPos = cache->bufferIndices().at(index); + glBufferSubData(GL_ARRAY_BUFFER, itemSize * dataPos, itemSize, + &buffered_uvs.at(uvsCount * pos++)); + } + } + } else { + glBufferData(GL_ARRAY_BUFFER, itemSize * itemCount, &buffered_uvs.at(0), GL_STATIC_DRAW); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +uint ScatterObjectBufferHelper::createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs) { - initializeOpenGLFunctions(); + ObjectHelper *dotObj = cache->object(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + const float yAdjustment = 0.1f; + const float flippedYAdjustment = 0.9f; + + QVector2D uv; + uv.setX(0.0f); + uint pos = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + if (!item.isVisible()) + continue; + + float y = ((item.translation().y() + m_scaleY) * 0.5f) / m_scaleY; + // Avoid values near gradient texel boundary, as this causes artifacts + // with some graphics cards. + const float floorY = float(qFloor(y * gradientTextureHeight)); + const float diff = (y * gradientTextureHeight) - floorY; + if (diff < yAdjustment) + y += yAdjustment / gradientTextureHeight; + else if (diff > flippedYAdjustment) + y -= yAdjustment / gradientTextureHeight; + uv.setY(y); + + int offset = pos * uvsCount; + for (int j = 0; j < uvsCount; j++) + buffered_uvs[j + offset] = uv; + + pos++; + } + + return pos; +} + +uint ScatterObjectBufferHelper::createObjectGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs, + const QVector<QVector3D> &indexed_vertices) +{ ObjectHelper *dotObj = cache->object(); - ScatterRenderItemArray &renderArray = cache->renderArray(); - const int renderArraySize = renderArray.size(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const uint renderArraySize = renderArray.size(); + + QVector2D uv; + uv.setX(0.0f); + uint pos = 0; + for (uint i = 0; i < renderArraySize; i++) { + const ScatterRenderItem &item = renderArray.at(i); + if (!item.isVisible()) + continue; + + int offset = pos * uvsCount; + for (int j = 0; j < uvsCount; j++) { + uv.setY((indexed_vertices.at(j).y() + 1.0f) / 2.0f); + buffered_uvs[j + offset] = uv; + } + + pos++; + } + + return pos; +} + +void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal dotScale) +{ + ObjectHelper *dotObj = cache->object(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); QQuaternion seriesRotation(cache->meshRotation()); + if (!updateSize) + return; + // Index vertices const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); int verticeCount = indexed_vertices.count(); @@ -195,14 +327,16 @@ void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal do scaled_vertices[i] = indexed_vertices[i] * modelMatrix; QVector<QVector3D> buffered_vertices; + buffered_vertices.resize(verticeCount * updateSize); - buffered_vertices.resize(verticeCount * renderArraySize); - for (int i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; + int itemCount = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); if (!item.isVisible()) continue; - const int offset = i * verticeCount; + const int offset = itemCount * verticeCount; if (item.rotation().isIdentity()) { for (int j = 0; j < verticeCount; j++) buffered_vertices[j + offset] = scaled_vertices[j] + item.translation(); @@ -216,15 +350,28 @@ void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal do buffered_vertices[j + offset] = indexed_vertices[j] * modelMatrix + item.translation(); } + itemCount++; } glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer); - glBufferData(GL_ARRAY_BUFFER, buffered_vertices.size() * sizeof(QVector3D), - &buffered_vertices.at(0), - GL_DYNAMIC_DRAW); - + int sizeOfItem = verticeCount * sizeof(QVector3D); + if (updateAll) { + if (itemCount) { + glBufferData(GL_ARRAY_BUFFER, itemCount * sizeOfItem, + &buffered_vertices.at(0), GL_STATIC_DRAW); + } + } else { + itemCount = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + if (renderArray.at(index).isVisible()) { + glBufferSubData(GL_ARRAY_BUFFER, cache->bufferIndices().at(index) * sizeOfItem, + sizeOfItem, &buffered_vertices.at(itemCount * verticeCount)); + itemCount++; + } + } + } glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } diff --git a/src/datavisualization/utils/scatterobjectbufferhelper_p.h b/src/datavisualization/utils/scatterobjectbufferhelper_p.h index 952c3d7d..08a42900 100644 --- a/src/datavisualization/utils/scatterobjectbufferhelper_p.h +++ b/src/datavisualization/utils/scatterobjectbufferhelper_p.h @@ -39,10 +39,21 @@ class ScatterObjectBufferHelper : public AbstractObjectHelper { public: ScatterObjectBufferHelper(); - ~ScatterObjectBufferHelper(); + virtual ~ScatterObjectBufferHelper(); void fullLoad(ScatterSeriesRenderCache *cache, qreal dotScale); void update(ScatterSeriesRenderCache *cache, qreal dotScale); + void updateUVs(ScatterSeriesRenderCache *cache); + void setScaleY(float scale) { m_scaleY = scale; } + +private: + uint createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs); + uint createObjectGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs, + const QVector<QVector3D> &indexed_vertices); + + float m_scaleY; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/scatterpointbufferhelper.cpp b/src/datavisualization/utils/scatterpointbufferhelper.cpp index b14d84ad..22e76f92 100644 --- a/src/datavisualization/utils/scatterpointbufferhelper.cpp +++ b/src/datavisualization/utils/scatterpointbufferhelper.cpp @@ -17,6 +17,7 @@ ****************************************************************************/ #include "scatterpointbufferhelper_p.h" +#include <QtGui/QVector2D> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -24,8 +25,7 @@ const QVector3D hiddenPos(-1000.0f, -1000.0f, -1000.0f); ScatterPointBufferHelper::ScatterPointBufferHelper() : m_pointbuffer(0), - m_oldRemoveIndex(0), - m_oldRemove(false) + m_oldRemoveIndex(-1) { m_indicesType = GL_UNSIGNED_INT; } @@ -47,7 +47,8 @@ void ScatterPointBufferHelper::pushPoint(uint pointIndex) { glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); - if (m_oldRemove && m_oldRemoveIndex < pointIndex) { + // Pop the previous point if it is still pushed + if (m_oldRemoveIndex >= 0) { glBufferSubData(GL_ARRAY_BUFFER, m_oldRemoveIndex * sizeof(QVector3D), sizeof(QVector3D), &m_bufferedPoints.at(m_oldRemoveIndex)); } @@ -59,26 +60,22 @@ void ScatterPointBufferHelper::pushPoint(uint pointIndex) glBindBuffer(GL_ARRAY_BUFFER, 0); m_oldRemoveIndex = pointIndex; - m_oldRemove = true; } void ScatterPointBufferHelper::popPoint() { - if (m_oldRemove) { + if (m_oldRemoveIndex >= 0) { glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); glBufferSubData(GL_ARRAY_BUFFER, m_oldRemoveIndex * sizeof(QVector3D), sizeof(QVector3D), &m_bufferedPoints.at(m_oldRemoveIndex)); glBindBuffer(GL_ARRAY_BUFFER, 0); } - m_oldRemoveIndex = 0; - m_oldRemove = false; + m_oldRemoveIndex = -1; } void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) { - initializeOpenGLFunctions(); - ScatterRenderItemArray &renderArray = cache->renderArray(); const int renderArraySize = renderArray.size(); m_indexCount = 0; @@ -86,13 +83,16 @@ void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_pointbuffer); + glDeleteBuffers(1, &m_uvbuffer); m_bufferedPoints.clear(); + m_pointbuffer = 0; + m_uvbuffer = 0; } bool itemsVisible = false; m_bufferedPoints.resize(renderArraySize); for (int i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; + const ScatterRenderItem &item = renderArray.at(i); if (!item.isVisible()) { m_bufferedPoints[i] = hiddenPos; } else { @@ -101,19 +101,108 @@ void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) } } + QVector<QVector2D> buffered_uvs; if (itemsVisible) m_indexCount = renderArraySize; if (m_indexCount > 0) { + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + createRangeGradientUVs(cache, buffered_uvs); + glGenBuffers(1, &m_pointbuffer); glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); glBufferData(GL_ARRAY_BUFFER, m_bufferedPoints.size() * sizeof(QVector3D), &m_bufferedPoints.at(0), GL_DYNAMIC_DRAW); + + if (buffered_uvs.size()) { + glGenBuffers(1, &m_uvbuffer); + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + glBufferData(GL_ARRAY_BUFFER, buffered_uvs.size() * sizeof(QVector2D), + &buffered_uvs.at(0), GL_STATIC_DRAW); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } } +void ScatterPointBufferHelper::update(ScatterSeriesRenderCache *cache) +{ + // It may be that the buffer hasn't yet been initialized, in case the entire series was + // hidden items. No need to update in that case. + if (m_indexCount > 0) { + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const int updateSize = cache->updateIndices().size(); + + glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + if (!item.isVisible()) + m_bufferedPoints[index] = hiddenPos; + else + m_bufferedPoints[index] = item.translation(); + + if (index != m_oldRemoveIndex) { + glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(QVector3D), + sizeof(QVector3D), &m_bufferedPoints.at(index)); + } + } + glBindBuffer(GL_ARRAY_BUFFER, 0); + } +} + +void ScatterPointBufferHelper::updateUVs(ScatterSeriesRenderCache *cache) +{ + // It may be that the buffer hasn't yet been initialized, in case the entire series was + // hidden items. No need to update in that case. + if (m_indexCount > 0) { + QVector<QVector2D> buffered_uvs; + createRangeGradientUVs(cache, buffered_uvs); + + if (buffered_uvs.size()) { + if (!m_uvbuffer) + glGenBuffers(1, &m_uvbuffer); + + int updateSize = cache->updateIndices().size(); + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + if (updateSize) { + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(QVector2D), + sizeof(QVector2D), &buffered_uvs.at(i)); + + } + } else { + glBufferData(GL_ARRAY_BUFFER, buffered_uvs.size() * sizeof(QVector2D), + &buffered_uvs.at(0), GL_STATIC_DRAW); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + } +} + +void ScatterPointBufferHelper::createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs) +{ + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + buffered_uvs.resize(updateSize); + + QVector2D uv; + uv.setX(0.0f); + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + + float y = ((item.translation().y() + m_scaleY) * 0.5f) / m_scaleY; + uv.setY(y); + buffered_uvs[i] = uv; + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/scatterpointbufferhelper_p.h b/src/datavisualization/utils/scatterpointbufferhelper_p.h index b3adcfa8..8b34542d 100644 --- a/src/datavisualization/utils/scatterpointbufferhelper_p.h +++ b/src/datavisualization/utils/scatterpointbufferhelper_p.h @@ -39,21 +39,28 @@ class ScatterPointBufferHelper : public AbstractObjectHelper { public: ScatterPointBufferHelper(); - ~ScatterPointBufferHelper(); + virtual ~ScatterPointBufferHelper(); GLuint pointBuf(); void pushPoint(uint pointIndex); void popPoint(); void load(ScatterSeriesRenderCache *cache); + void update(ScatterSeriesRenderCache *cache); + void setScaleY(float scale) { m_scaleY = scale; } + void updateUVs(ScatterSeriesRenderCache *cache); public: GLuint m_pointbuffer; private: + void createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs); + +private: QVector<QVector3D> m_bufferedPoints; - uint m_oldRemoveIndex; - bool m_oldRemove; + int m_oldRemoveIndex; + float m_scaleY; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/shaderhelper.cpp b/src/datavisualization/utils/shaderhelper.cpp index 7fb237c6..bbd7fe0e 100644 --- a/src/datavisualization/utils/shaderhelper.cpp +++ b/src/datavisualization/utils/shaderhelper.cpp @@ -40,7 +40,37 @@ ShaderHelper::ShaderHelper(QObject *parent, m_vertexShaderFile(vertexShader), m_fragmentShaderFile(fragmentShader), m_textureFile(texture), - m_depthTextureFile(depthTexture) + m_depthTextureFile(depthTexture), + m_positionAttr(0), + m_uvAttr(0), + m_normalAttr(0), + m_colorUniform(0), + m_viewMatrixUniform(0), + m_modelMatrixUniform(0), + m_invTransModelMatrixUniform(0), + m_depthMatrixUniform(0), + m_mvpMatrixUniform(0), + m_lightPositionUniform(0), + m_lightStrengthUniform(0), + m_ambientStrengthUniform(0), + m_shadowQualityUniform(0), + m_textureUniform(0), + m_shadowUniform(0), + m_gradientMinUniform(0), + m_gradientHeightUniform(0), + m_lightColorUniform(0), + m_volumeSliceIndicesUniform(0), + m_colorIndexUniform(0), + m_cameraPositionRelativeToModelUniform(0), + m_color8BitUniform(0), + m_textureDimensionsUniform(0), + m_sampleCountUniform(0), + m_alphaMultiplierUniform(0), + m_preserveOpacityUniform(0), + m_minBoundsUniform(0), + m_maxBoundsUniform(0), + m_sliceFrameWidthUniform(0), + m_initialized(false) { } @@ -93,6 +123,17 @@ void ShaderHelper::initialize() m_gradientMinUniform = m_program->uniformLocation("gradMin"); m_gradientHeightUniform = m_program->uniformLocation("gradHeight"); m_lightColorUniform = m_program->uniformLocation("lightColor"); + m_volumeSliceIndicesUniform = m_program->uniformLocation("volumeSliceIndices"); + m_colorIndexUniform = m_program->uniformLocation("colorIndex"); + m_cameraPositionRelativeToModelUniform = m_program->uniformLocation("cameraPositionRelativeToModel"); + m_color8BitUniform = m_program->uniformLocation("color8Bit"); + m_textureDimensionsUniform = m_program->uniformLocation("textureDimensions"); + m_sampleCountUniform = m_program->uniformLocation("sampleCount"); + m_alphaMultiplierUniform = m_program->uniformLocation("alphaMultiplier"); + m_preserveOpacityUniform = m_program->uniformLocation("preserveOpacity"); + m_minBoundsUniform = m_program->uniformLocation("minBounds"); + m_maxBoundsUniform = m_program->uniformLocation("maxBounds"); + m_sliceFrameWidthUniform = m_program->uniformLocation("sliceFrameWidth"); m_initialized = true; } @@ -125,151 +166,239 @@ void ShaderHelper::release() m_program->release(); } -void ShaderHelper::setUniformValue(GLuint uniform, const QVector3D &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector2D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, const QVector4D &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector3D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, const QMatrix4x4 &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector4D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, GLfloat value) +void ShaderHelper::setUniformValue(GLint uniform, const QMatrix4x4 &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, GLint value) +void ShaderHelper::setUniformValue(GLint uniform, GLfloat value) { m_program->setUniformValue(uniform, value); } -GLuint ShaderHelper::MVP() +void ShaderHelper::setUniformValue(GLint uniform, GLint value) +{ + m_program->setUniformValue(uniform, value); +} + +void ShaderHelper::setUniformValueArray(GLint uniform, const QVector4D *values, int count) +{ + m_program->setUniformValueArray(uniform, values, count); +} + +GLint ShaderHelper::MVP() { if (!m_initialized) qFatal("Shader not initialized"); return m_mvpMatrixUniform; } -GLuint ShaderHelper::view() +GLint ShaderHelper::view() { if (!m_initialized) qFatal("Shader not initialized"); return m_viewMatrixUniform; } -GLuint ShaderHelper::model() +GLint ShaderHelper::model() { if (!m_initialized) qFatal("Shader not initialized"); return m_modelMatrixUniform; } -GLuint ShaderHelper::nModel() +GLint ShaderHelper::nModel() { if (!m_initialized) qFatal("Shader not initialized"); return m_invTransModelMatrixUniform; } -GLuint ShaderHelper::depth() +GLint ShaderHelper::depth() { if (!m_initialized) qFatal("Shader not initialized"); return m_depthMatrixUniform; } -GLuint ShaderHelper::lightP() +GLint ShaderHelper::lightP() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightPositionUniform; } -GLuint ShaderHelper::lightS() +GLint ShaderHelper::lightS() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightStrengthUniform; } -GLuint ShaderHelper::ambientS() +GLint ShaderHelper::ambientS() { if (!m_initialized) qFatal("Shader not initialized"); return m_ambientStrengthUniform; } -GLuint ShaderHelper::shadowQ() +GLint ShaderHelper::shadowQ() { if (!m_initialized) qFatal("Shader not initialized"); return m_shadowQualityUniform; } -GLuint ShaderHelper::color() +GLint ShaderHelper::color() { if (!m_initialized) qFatal("Shader not initialized"); return m_colorUniform; } -GLuint ShaderHelper::texture() +GLint ShaderHelper::texture() { if (!m_initialized) qFatal("Shader not initialized"); return m_textureUniform; } -GLuint ShaderHelper::shadow() +GLint ShaderHelper::shadow() { if (!m_initialized) qFatal("Shader not initialized"); return m_shadowUniform; } -GLuint ShaderHelper::gradientMin() +GLint ShaderHelper::gradientMin() { if (!m_initialized) qFatal("Shader not initialized"); return m_gradientMinUniform; } -GLuint ShaderHelper::gradientHeight() +GLint ShaderHelper::gradientHeight() { if (!m_initialized) qFatal("Shader not initialized"); return m_gradientHeightUniform; } -GLuint ShaderHelper::lightColor() +GLint ShaderHelper::lightColor() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightColorUniform; } -GLuint ShaderHelper::posAtt() +GLint ShaderHelper::volumeSliceIndices() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_volumeSliceIndicesUniform; +} + +GLint ShaderHelper::colorIndex() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_colorIndexUniform; +} + +GLint ShaderHelper::cameraPositionRelativeToModel() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_cameraPositionRelativeToModelUniform; +} + +GLint ShaderHelper::color8Bit() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_color8BitUniform; +} + +GLint ShaderHelper::textureDimensions() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_textureDimensionsUniform; +} + +GLint ShaderHelper::sampleCount() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_sampleCountUniform; +} + +GLint ShaderHelper::alphaMultiplier() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_alphaMultiplierUniform; +} + +GLint ShaderHelper::preserveOpacity() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_preserveOpacityUniform; +} + +GLint ShaderHelper::maxBounds() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_maxBoundsUniform; +} + +GLint ShaderHelper::minBounds() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_minBoundsUniform; +} + +GLint ShaderHelper::sliceFrameWidth() +{ + + if (!m_initialized) + qFatal("Shader not initialized"); + return m_sliceFrameWidthUniform; +} + +GLint ShaderHelper::posAtt() { if (!m_initialized) qFatal("Shader not initialized"); return m_positionAttr; } -GLuint ShaderHelper::uvAtt() +GLint ShaderHelper::uvAtt() { if (!m_initialized) qFatal("Shader not initialized"); return m_uvAttr; } -GLuint ShaderHelper::normalAtt() +GLint ShaderHelper::normalAtt() { if (!m_initialized) qFatal("Shader not initialized"); diff --git a/src/datavisualization/utils/shaderhelper_p.h b/src/datavisualization/utils/shaderhelper_p.h index fdef0dff..812cba18 100644 --- a/src/datavisualization/utils/shaderhelper_p.h +++ b/src/datavisualization/utils/shaderhelper_p.h @@ -52,31 +52,44 @@ class ShaderHelper bool testCompile(); void bind(); void release(); - void setUniformValue(GLuint uniform, const QVector3D &value); - void setUniformValue(GLuint uniform, const QVector4D &value); - void setUniformValue(GLuint uniform, const QMatrix4x4 &value); - void setUniformValue(GLuint uniform, GLfloat value); - void setUniformValue(GLuint uniform, GLint value); - - GLuint MVP(); - GLuint view(); - GLuint model(); - GLuint nModel(); - GLuint depth(); - GLuint lightP(); - GLuint lightS(); - GLuint ambientS(); - GLuint shadowQ(); - GLuint color(); - GLuint texture(); - GLuint shadow(); - GLuint gradientMin(); - GLuint gradientHeight(); - GLuint lightColor(); - - GLuint posAtt(); - GLuint uvAtt(); - GLuint normalAtt(); + void setUniformValue(GLint uniform, const QVector2D &value); + void setUniformValue(GLint uniform, const QVector3D &value); + void setUniformValue(GLint uniform, const QVector4D &value); + void setUniformValue(GLint uniform, const QMatrix4x4 &value); + void setUniformValue(GLint uniform, GLfloat value); + void setUniformValue(GLint uniform, GLint value); + void setUniformValueArray(GLint uniform, const QVector4D *values, int count); + + GLint MVP(); + GLint view(); + GLint model(); + GLint nModel(); + GLint depth(); + GLint lightP(); + GLint lightS(); + GLint ambientS(); + GLint shadowQ(); + GLint color(); + GLint texture(); + GLint shadow(); + GLint gradientMin(); + GLint gradientHeight(); + GLint lightColor(); + GLint volumeSliceIndices(); + GLint colorIndex(); + GLint cameraPositionRelativeToModel(); + GLint color8Bit(); + GLint textureDimensions(); + GLint sampleCount(); + GLint alphaMultiplier(); + GLint preserveOpacity(); + GLint maxBounds(); + GLint minBounds(); + GLint sliceFrameWidth(); + + GLint posAtt(); + GLint uvAtt(); + GLint normalAtt(); private: QObject *m_caller; @@ -88,25 +101,36 @@ class ShaderHelper QString m_textureFile; QString m_depthTextureFile; - GLuint m_positionAttr; - GLuint m_uvAttr; - GLuint m_normalAttr; - - GLuint m_colorUniform; - GLuint m_viewMatrixUniform; - GLuint m_modelMatrixUniform; - GLuint m_invTransModelMatrixUniform; - GLuint m_depthMatrixUniform; - GLuint m_mvpMatrixUniform; - GLuint m_lightPositionUniform; - GLuint m_lightStrengthUniform; - GLuint m_ambientStrengthUniform; - GLuint m_shadowQualityUniform; - GLuint m_textureUniform; - GLuint m_shadowUniform; - GLuint m_gradientMinUniform; - GLuint m_gradientHeightUniform; - GLuint m_lightColorUniform; + GLint m_positionAttr; + GLint m_uvAttr; + GLint m_normalAttr; + + GLint m_colorUniform; + GLint m_viewMatrixUniform; + GLint m_modelMatrixUniform; + GLint m_invTransModelMatrixUniform; + GLint m_depthMatrixUniform; + GLint m_mvpMatrixUniform; + GLint m_lightPositionUniform; + GLint m_lightStrengthUniform; + GLint m_ambientStrengthUniform; + GLint m_shadowQualityUniform; + GLint m_textureUniform; + GLint m_shadowUniform; + GLint m_gradientMinUniform; + GLint m_gradientHeightUniform; + GLint m_lightColorUniform; + GLint m_volumeSliceIndicesUniform; + GLint m_colorIndexUniform; + GLint m_cameraPositionRelativeToModelUniform; + GLint m_color8BitUniform; + GLint m_textureDimensionsUniform; + GLint m_sampleCountUniform; + GLint m_alphaMultiplierUniform; + GLint m_preserveOpacityUniform; + GLint m_minBoundsUniform; + GLint m_maxBoundsUniform; + GLint m_sliceFrameWidthUniform; GLboolean m_initialized; }; diff --git a/src/datavisualization/utils/surfaceobject.cpp b/src/datavisualization/utils/surfaceobject.cpp index d999ba90..b93030b1 100644 --- a/src/datavisualization/utils/surfaceobject.cpp +++ b/src/datavisualization/utils/surfaceobject.cpp @@ -30,26 +30,31 @@ SurfaceObject::SurfaceObject(Surface3DRenderer *renderer) m_gridIndexCount(0), m_axisCacheX(renderer->m_axisCacheX), m_axisCacheY(renderer->m_axisCacheY), - m_axisCacheZ(renderer->m_axisCacheZ) - + m_axisCacheZ(renderer->m_axisCacheZ), + m_renderer(renderer), + m_returnTextureBuffer(false), + m_dataDimension(0), + m_oldDataDimension(-1) { m_indicesType = GL_UNSIGNED_INT; - initializeOpenGLFunctions(); glGenBuffers(1, &m_vertexbuffer); glGenBuffers(1, &m_normalbuffer); glGenBuffers(1, &m_uvbuffer); glGenBuffers(1, &m_elementbuffer); glGenBuffers(1, &m_gridElementbuffer); + glGenBuffers(1, &m_uvTextureBuffer); } SurfaceObject::~SurfaceObject() { - if (QOpenGLContext::currentContext()) + if (QOpenGLContext::currentContext()) { glDeleteBuffers(1, &m_gridElementbuffer); + glDeleteBuffers(1, &m_uvTextureBuffer); + } } void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ) + bool changeGeometry, bool polar, bool flipXZ) { m_columns = space.width(); m_rows = space.height(); @@ -59,6 +64,12 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR m_surfaceType = SurfaceSmooth; + checkDirections(dataArray); + bool indicesDirty = false; + if (m_dataDimension != m_oldDataDimension) + indicesDirty = true; + m_oldDataDimension = m_dataDimension; + // Create/populate vertix table if (changeGeometry) m_vertices.resize(totalSize); @@ -68,17 +79,14 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR uvs.resize(totalSize); int totalIndex = 0; - AxisRenderCache &xCache = flipXZ ? m_axisCacheZ : m_axisCacheX; - AxisRenderCache &zCache = flipXZ ? m_axisCacheX : m_axisCacheZ; + // Init min and max to ridiculous values + m_minY = 10000000.0; + m_maxY = -10000000.0f; for (int i = 0; i < m_rows; i++) { const QSurfaceDataRow &p = *dataArray.at(i); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = p.at(j); - float normalizedX = xCache.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = zCache.positionAt(data.z()); - m_vertices[totalIndex] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(p.at(j), m_vertices[totalIndex], polar, flipXZ); if (changeGeometry) uvs[totalIndex] = QVector2D(GLfloat(j) * uvX, GLfloat(i) * uvY); totalIndex++; @@ -95,161 +103,305 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR // Create normals int rowLimit = m_rows - 1; int colLimit = m_columns - 1; - int rowColLimit = rowLimit * m_columns; - int totalLimit = totalSize - 1; if (changeGeometry) m_normals.resize(totalSize); totalIndex = 0; - const bool flipNormal = checkFlipNormal(dataArray); - for (int row = 0; row < rowColLimit; row += m_columns) { - for (int j = 0; j < colLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(row + m_columns + j), - flipNormal); - } - int p = row + colLimit; - m_normals[totalIndex++] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); - } - for (int j = rowColLimit; j < totalLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(j), - m_vertices.at(j - m_columns), - m_vertices.at(j + 1), - flipNormal); + + if ((m_dataDimension == BothAscending) || (m_dataDimension == XDescending)) { + for (int row = 0; row < rowLimit; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); + createSmoothNormalUpperLine(totalIndex); + } else { // BothDescending || ZDescending + createSmoothNormalUpperLine(totalIndex); + for (int row = 1; row < m_rows; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); } - m_normals[totalIndex++] = normal(m_vertices.at(totalLimit), - m_vertices.at(totalLimit - 1), - m_vertices.at(totalLimit - m_columns), - flipNormal); // Create indices table - if (changeGeometry) + if (changeGeometry || indicesDirty) createSmoothIndices(0, 0, colLimit, rowLimit); // Create line element indices if (changeGeometry) createSmoothGridlineIndices(0, 0, colLimit, rowLimit); - createBuffers(m_vertices, uvs, m_normals, 0, changeGeometry); + createBuffers(m_vertices, uvs, m_normals, 0); } -void SurfaceObject::updateSmoothRow(const QSurfaceDataArray &dataArray, int rowIndex) +void SurfaceObject::createSmoothNormalBodyLine(int &totalIndex, int column) +{ + int colLimit = m_columns - 1; + + if (m_dataDimension == BothAscending) { + int end = colLimit + column; + for (int j = column; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + 1), + m_vertices.at(j + m_columns)); + } + m_normals[totalIndex++] = normal(m_vertices.at(end), + m_vertices.at(end + m_columns), + m_vertices.at(end - 1)); + } else if (m_dataDimension == XDescending) { + m_normals[totalIndex++] = normal(m_vertices.at(column), + m_vertices.at(column + m_columns), + m_vertices.at(column + 1)); + int end = column + m_columns; + for (int j = column + 1; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - 1), + m_vertices.at(j + m_columns)); + } + } else if (m_dataDimension == ZDescending) { + int end = colLimit + column; + for (int j = column; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + 1), + m_vertices.at(j - m_columns)); + } + m_normals[totalIndex++] = normal(m_vertices.at(end), + m_vertices.at(end - m_columns), + m_vertices.at(end - 1)); + } else { // BothDescending + m_normals[totalIndex++] = normal(m_vertices.at(column), + m_vertices.at(column - m_columns), + m_vertices.at(column + 1)); + int end = column + m_columns; + for (int j = column + 1; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - 1), + m_vertices.at(j - m_columns)); + } + } +} + +void SurfaceObject::createSmoothNormalUpperLine(int &totalIndex) +{ + if (m_dataDimension == BothAscending) { + int lineEnd = m_rows * m_columns - 1; + for (int j = (m_rows - 1) * m_columns; j < lineEnd; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - m_columns), + m_vertices.at(j + 1)); + } + m_normals[totalIndex++] = normal(m_vertices.at(lineEnd), + m_vertices.at(lineEnd - 1), + m_vertices.at(lineEnd - m_columns)); + } else if (m_dataDimension == XDescending) { + int lineStart = (m_rows - 1) * m_columns; + int lineEnd = m_rows * m_columns; + m_normals[totalIndex++] = normal(m_vertices.at(lineStart), + m_vertices.at(lineStart + 1), + m_vertices.at(lineStart - m_columns)); + for (int j = lineStart + 1; j < lineEnd; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - m_columns), + m_vertices.at(j - 1)); + } + } else if (m_dataDimension == ZDescending) { + int colLimit = m_columns - 1; + for (int j = 0; j < colLimit; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + m_columns), + m_vertices.at(j + 1)); + } + m_normals[totalIndex++] = normal(m_vertices.at(colLimit), + m_vertices.at(colLimit - 1), + m_vertices.at(colLimit + m_columns)); + } else { // BothDescending + m_normals[totalIndex++] = normal(m_vertices.at(0), + m_vertices.at(1), + m_vertices.at(m_columns)); + for (int j = 1; j < m_columns; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + m_columns), + m_vertices.at(j - 1)); + } + } +} + +QVector3D SurfaceObject::createSmoothNormalBodyLineItem(int x, int y) +{ + int p = y * m_columns + x; + if (m_dataDimension == BothAscending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p + m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p - 1)); + } + } else if (m_dataDimension == XDescending) { + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p + m_columns)); + } + } else if (m_dataDimension == ZDescending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p - m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p - 1)); + } + } else { // BothDescending + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p - m_columns)); + } + } +} + +QVector3D SurfaceObject::createSmoothNormalUpperLineItem(int x, int y) +{ + int p = y * m_columns + x; + if (m_dataDimension == BothAscending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p - m_columns)); + } + } else if (m_dataDimension == XDescending) { + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p - m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p - 1)); + } + } else if (m_dataDimension == ZDescending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p + m_columns)); + } + } else { // BothDescending + if (x == 0) { + return normal(m_vertices.at(0), m_vertices.at(1), + m_vertices.at(m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p - 1)); + } + } +} + +void SurfaceObject::smoothUVs(const QSurfaceDataArray &dataArray, + const QSurfaceDataArray &modelArray) +{ + int columns = dataArray.at(0)->size(); + int rows = dataArray.size(); + float xRangeNormalizer = dataArray.at(0)->at(columns - 1).x() - dataArray.at(0)->at(0).x(); + float zRangeNormalizer = dataArray.at(rows - 1)->at(0).z() - dataArray.at(0)->at(0).z(); + float xMin = dataArray.at(0)->at(0).x(); + float zMin = dataArray.at(0)->at(0).z(); + const bool zDescending = m_dataDimension.testFlag(SurfaceObject::ZDescending); + const bool xDescending = m_dataDimension.testFlag(SurfaceObject::XDescending); + + QVector<QVector2D> uvs; + uvs.resize(m_rows * m_columns); + int index = 0; + for (int i = 0; i < m_rows; i++) { + float y = (modelArray.at(i)->at(0).z() - zMin) / zRangeNormalizer; + if (zDescending) + y = 1.0f - y; + const QSurfaceDataRow &p = *modelArray.at(i); + for (int j = 0; j < m_columns; j++) { + float x = (p.at(j).x() - xMin) / xRangeNormalizer; + if (xDescending) + x = 1.0f - x; + uvs[index] = QVector2D(x, y); + index++; + } + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvTextureBuffer); + glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), + &uvs.at(0), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + m_returnTextureBuffer = true; +} + +void SurfaceObject::updateSmoothRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar) { // Update vertices int p = rowIndex * m_columns; const QSurfaceDataRow &dataRow = *dataArray.at(rowIndex); - for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = dataRow.at(j); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p++] = QVector3D(normalizedX, normalizedY, normalizedZ); - } + for (int j = 0; j < m_columns; j++) + getNormalizedVertex(dataRow.at(j), m_vertices[p++], polar, false); // Create normals - int colLimit = m_columns - 1; + bool upwards = (m_dataDimension == BothAscending) || (m_dataDimension == XDescending); int startRow = rowIndex; - if (startRow > 0) + if ((startRow > 0) && upwards) startRow--; + int endRow = rowIndex; + if (!upwards && (rowIndex < m_rows - 1)) + endRow++; + if ((endRow == m_rows - 1) && upwards) + endRow--; int totalIndex = startRow * m_columns; - int rowLimit = (rowIndex + 1) * m_columns; - if (rowIndex == m_rows - 1) - rowLimit = rowIndex * m_columns; // The rowIndex is top most row, special handling - const bool flipNormal = checkFlipNormal(dataArray); - for (int row = totalIndex; row < rowLimit; row += m_columns) { - for (int j = 0; j < colLimit; j++) { - // One right and one up - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(row + m_columns + j), - flipNormal); - } - int p = row + colLimit; - // One up and one left - m_normals[totalIndex++] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); + if ((startRow == 0) && !upwards) { + createSmoothNormalUpperLine(totalIndex); + startRow++; } - if (rowIndex == m_rows - 1) { - // Top most line, nothing above, must have different handling. - // Take from one down and one right. Read till second-to-last - rowLimit = (rowIndex + 1) * m_columns - 1; - for (int j = rowIndex * m_columns; j < rowLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(j), - m_vertices.at(j - m_columns), - m_vertices.at(j + 1), - flipNormal); - } - // Top left corner. Take from one left and one down - m_normals[totalIndex++] = normal(m_vertices.at(rowLimit), - m_vertices.at(rowLimit - 1), - m_vertices.at(rowLimit - m_columns), - flipNormal); - } + for (int row = startRow; row <= endRow; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); + + if ((rowIndex == m_rows - 1) && upwards) + createSmoothNormalUpperLine(totalIndex); } -void SurfaceObject::updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column) +void SurfaceObject::updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column, + bool polar) { // Update a vertice - const QSurfaceDataItem &data = dataArray.at(row)->at(column); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[row * m_columns + column] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(dataArray.at(row)->at(column), + m_vertices[row * m_columns + column], polar, false); // Create normals + bool upwards = (m_dataDimension == BothAscending) || (m_dataDimension == XDescending); + bool rightwards = (m_dataDimension == BothAscending) || (m_dataDimension == ZDescending); int startRow = row; - if (startRow > 0) - startRow--; // Change the normal for previous row also + if ((startRow > 0) && upwards) + startRow--; + int endRow = row; + if (!upwards && (row < m_rows - 1)) + endRow++; + if ((endRow == m_rows - 1) && upwards) + endRow--; int startCol = column; - if (startCol > 0) + if ((startCol > 0) && rightwards) startCol--; - int rightCol = m_columns - 1; - int topRow = m_rows - 1; + int endCol = column; + if ((endCol < m_columns - 1) && !rightwards) + endCol++; - const bool flipNormal = checkFlipNormal(dataArray); - for (int i = startRow; i <= row; i++) { - for (int j = startCol; j <= column; j++) { + for (int i = startRow; i <= endRow; i++) { + for (int j = startCol; j <= endCol; j++) { int p = i * m_columns + j; - if (i < topRow) { - if (j < rightCol) { - // One right and one up - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + 1), - m_vertices.at(p + m_columns), - flipNormal); - } else { - // Last item, nothing on the right. One up and one left - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); - } - } else { - // Top most line, nothing above, must have different handling. - if (j < rightCol) { - // Take from one down and one right. Read till second-to-last - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p - m_columns), - m_vertices.at(p + 1), - flipNormal); - } else { - // Top left corner. Take from one left and one down - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p - 1), - m_vertices.at(p - m_columns), - flipNormal); - } - } - } + if ((i == 0) && !upwards) + m_normals[p] = createSmoothNormalUpperLineItem(j, i); + else if ((i == m_rows - 1) && upwards) + m_normals[p] = createSmoothNormalUpperLineItem(j, i); + else + m_normals[p] = createSmoothNormalBodyLineItem(j, i); + } } } @@ -271,15 +423,38 @@ void SurfaceObject::createSmoothIndices(int x, int y, int endX, int endY) int rowEnd = endY * m_columns; for (int row = y * m_columns; row < rowEnd; row += m_columns) { for (int j = x; j < endX; j++) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = row + m_columns + j; - indices[p++] = row + j; - - // Right triangle - indices[p++] = row + m_columns + j + 1; - indices[p++] = row + m_columns + j; - indices[p++] = row + j + 1; + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + // Left triangle + indices[p++] = row + j + 1; + indices[p++] = row + m_columns + j; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + m_columns + j; + indices[p++] = row + j + 1; + } else if (m_dataDimension == XDescending) { + // Right triangle + indices[p++] = row + m_columns + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j; + + // Left triangle + indices[p++] = row + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j + 1; + } else { + // Left triangle + indices[p++] = row + m_columns + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j + 1; + + } } } @@ -331,7 +506,7 @@ void SurfaceObject::createSmoothGridlineIndices(int x, int y, int endX, int endY } void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ) + bool changeGeometry, bool polar, bool flipXZ) { m_columns = space.width(); m_rows = space.height(); @@ -339,6 +514,12 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s GLfloat uvX = 1.0f / GLfloat(m_columns - 1); GLfloat uvY = 1.0f / GLfloat(m_rows - 1); + checkDirections(dataArray); + bool indicesDirty = false; + if (m_dataDimension != m_oldDataDimension) + indicesDirty = true; + m_oldDataDimension = m_dataDimension; + m_surfaceType = SurfaceFlat; // Create vertix table @@ -355,17 +536,14 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s int doubleColumns = m_columns * 2 - 2; int rowColLimit = rowLimit * doubleColumns; - AxisRenderCache &xCache = flipXZ ? m_axisCacheZ : m_axisCacheX; - AxisRenderCache &zCache = flipXZ ? m_axisCacheX : m_axisCacheZ; + // Init min and max to ridiculous values + m_minY = 10000000.0; + m_maxY = -10000000.0f; for (int i = 0; i < m_rows; i++) { const QSurfaceDataRow &row = *dataArray.at(i); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = row.at(j); - float normalizedX = xCache.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = zCache.positionAt(data.z()); - m_vertices[totalIndex] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(row.at(j), m_vertices[totalIndex], polar, flipXZ); if (changeGeometry) uvs[totalIndex] = QVector2D(GLfloat(j) * uvX, GLfloat(i) * uvY); @@ -389,42 +567,23 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s // Create normals & indices table GLint *indices = 0; - int p = 0; - if (changeGeometry) { + if (changeGeometry || indicesDirty) { int normalCount = 2 * colLimit * rowLimit; m_indexCount = 3 * normalCount; indices = new GLint[m_indexCount]; m_normals.resize(normalCount); } + int p = 0; totalIndex = 0; - const bool flipNormal = checkFlipNormal(dataArray); for (int row = 0, upperRow = doubleColumns; row < rowColLimit; row += doubleColumns, upperRow += doubleColumns) { for (int j = 0; j < doubleColumns; j += 2) { - // Normal for the left triangle - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - // Normal for the right triangle - m_normals[totalIndex++] = normal(m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - if (changeGeometry) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j; + createNormals(totalIndex, row, upperRow, j); - // Right triangle - indices[p++] = upperRow + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j + 1; + if (changeGeometry || indicesDirty) { + createCoarseIndices(indices, p, row, upperRow, j); } } } @@ -433,12 +592,54 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s if (changeGeometry) createCoarseGridlineIndices(0, 0, colLimit, rowLimit); - createBuffers(m_vertices, uvs, m_normals, indices, changeGeometry); + createBuffers(m_vertices, uvs, m_normals, indices); delete[] indices; } -void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex) +void SurfaceObject::coarseUVs(const QSurfaceDataArray &dataArray, + const QSurfaceDataArray &modelArray) +{ + int columns = dataArray.at(0)->size(); + int rows = dataArray.size(); + float xRangeNormalizer = dataArray.at(0)->at(columns - 1).x() - dataArray.at(0)->at(0).x(); + float zRangeNormalizer = dataArray.at(rows - 1)->at(0).z() - dataArray.at(0)->at(0).z(); + float xMin = dataArray.at(0)->at(0).x(); + float zMin = dataArray.at(0)->at(0).z(); + const bool zDescending = m_dataDimension.testFlag(SurfaceObject::ZDescending); + const bool xDescending = m_dataDimension.testFlag(SurfaceObject::XDescending); + + QVector<QVector2D> uvs; + uvs.resize(m_rows * m_columns * 2); + int index = 0; + int colLimit = m_columns - 1; + for (int i = 0; i < m_rows; i++) { + float y = (modelArray.at(i)->at(0).z() - zMin) / zRangeNormalizer; + if (zDescending) + y = 1.0f - y; + const QSurfaceDataRow &p = *modelArray.at(i); + for (int j = 0; j < m_columns; j++) { + float x = (p.at(j).x() - xMin) / xRangeNormalizer; + if (xDescending) + x = 1.0f - x; + uvs[index] = QVector2D(x, y); + index++; + if (j > 0 && j < colLimit) { + uvs[index] = uvs[index - 1]; + index++; + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvTextureBuffer); + glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), + &uvs.at(0), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + m_returnTextureBuffer = true; +} + +void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar) { int colLimit = m_columns - 1; int doubleColumns = m_columns * 2 - 2; @@ -447,12 +648,7 @@ void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowI const QSurfaceDataRow &dataRow = *dataArray.at(rowIndex); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = dataRow.at(j); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p++] = QVector3D(normalizedX, normalizedY, normalizedZ); - + getNormalizedVertex(dataRow.at(j), m_vertices[p++], polar, false); if (j > 0 && j < colLimit) { m_vertices[p] = m_vertices[p - 1]; p++; @@ -466,39 +662,23 @@ void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowI int rowLimit = (rowIndex + 1) * doubleColumns; if (rowIndex == m_rows - 1) rowLimit = rowIndex * doubleColumns; //Topmost row, no normals - const bool flipNormal = checkFlipNormal(dataArray); for (int row = p, upperRow = p + doubleColumns; row < rowLimit; row += doubleColumns, upperRow += doubleColumns) { - for (int j = 0; j < doubleColumns; j += 2) { - // Normal for the left triangle - m_normals[p++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - // Normal for the right triangle - m_normals[p++] = normal(m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - } + for (int j = 0; j < doubleColumns; j += 2) + createNormals(p, row, upperRow, j); } } -void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column) +void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column, + bool polar) { int colLimit = m_columns - 1; int doubleColumns = m_columns * 2 - 2; // Update a vertice int p = row * doubleColumns + column * 2 - (column > 0); - const QSurfaceDataItem &data = dataArray.at(row)->at(column); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p] = QVector3D(normalizedX, normalizedY, normalizedZ); - p++; + getNormalizedVertex(dataArray.at(row)->at(column), m_vertices[p++], polar, false); if (column > 0 && column < colLimit) m_vertices[p] = m_vertices[p - 1]; @@ -515,27 +695,15 @@ void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row if (column == m_columns - 1) column--; - const bool flipNormal = checkFlipNormal(dataArray); for (int i = startRow; i <= row; i++) { for (int j = startCol; j <= column; j++) { p = i * doubleColumns + j * 2; - // Normal for the left triangle - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + 1), - m_vertices.at(p + doubleColumns), - flipNormal); - p++; - - // Normal for the right triangle - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + doubleColumns), - m_vertices.at(p + doubleColumns - 1), - flipNormal); + createNormals(p, i * doubleColumns, (i + 1) * doubleColumns, j * 2); } } } -void SurfaceObject::createCoarseIndices(int x, int y, int columns, int rows) +void SurfaceObject::createCoarseSubSection(int x, int y, int columns, int rows) { if (columns > m_columns) columns = m_columns; @@ -557,17 +725,8 @@ void SurfaceObject::createCoarseIndices(int x, int y, int columns, int rows) for (int row = y * doubleColumns, upperRow = (y + 1) * doubleColumns; row < rowColLimit; row += doubleColumns, upperRow += doubleColumns) { - for (int j = 2 * x; j < doubleColumnsLimit; j += 2) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j; - - // Right triangle - indices[p++] = upperRow + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j + 1; - } + for (int j = 2 * x; j < doubleColumnsLimit; j += 2) + createCoarseIndices(indices, p, row, upperRow, j); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); @@ -631,12 +790,11 @@ void SurfaceObject::createCoarseGridlineIndices(int x, int y, int endX, int endY void SurfaceObject::uploadBuffers() { QVector<QVector2D> uvs; // Empty dummy - createBuffers(m_vertices, uvs, m_normals, 0, false); + createBuffers(m_vertices, uvs, m_normals, 0); } void SurfaceObject::createBuffers(const QVector<QVector3D> &vertices, const QVector<QVector2D> &uvs, - const QVector<QVector3D> &normals, const GLint *indices, - bool changeGeometry) + const QVector<QVector3D> &normals, const GLint *indices) { // Move to buffers glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer); @@ -647,30 +805,62 @@ void SurfaceObject::createBuffers(const QVector<QVector3D> &vertices, const QVec glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(QVector3D), &normals.at(0), GL_DYNAMIC_DRAW); - if (changeGeometry) { + if (uvs.size()) { glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), &uvs.at(0), GL_STATIC_DRAW); + } - if (indices) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount * sizeof(GLint), - indices, GL_STATIC_DRAW); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + if (indices) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount * sizeof(GLint), + indices, GL_STATIC_DRAW); } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } -bool SurfaceObject::checkFlipNormal(const QSurfaceDataArray &array) +void SurfaceObject::checkDirections(const QSurfaceDataArray &array) +{ + m_dataDimension = BothAscending; + + if (array.at(0)->at(0).x() > array.at(0)->at(array.at(0)->size() - 1).x()) + m_dataDimension |= XDescending; + if (m_axisCacheX.reversed()) + m_dataDimension ^= XDescending; + + if (array.at(0)->at(0).z() > array.at(array.size() - 1)->at(0).z()) + m_dataDimension |= ZDescending; + if (m_axisCacheZ.reversed()) + m_dataDimension ^= ZDescending; +} + +void SurfaceObject::getNormalizedVertex(const QSurfaceDataItem &data, QVector3D &vertex, + bool polar, bool flipXZ) { - const bool ascendingX = array.at(0)->at(0).x() < array.at(0)->at(array.at(0)->size() - 1).x(); - const bool ascendingZ = array.at(0)->at(0).z() < array.at(array.size() - 1)->at(0).z(); - return ascendingX != ascendingZ; + float normalizedX; + float normalizedZ; + if (polar) { + // Slice don't use polar, so don't care about flip + m_renderer->calculatePolarXZ(data.position(), normalizedX, normalizedZ); + } else { + if (flipXZ) { + normalizedX = m_axisCacheZ.positionAt(data.x()); + normalizedZ = m_axisCacheX.positionAt(data.z()); + } else { + normalizedX = m_axisCacheX.positionAt(data.x()); + normalizedZ = m_axisCacheZ.positionAt(data.z()); + } + } + float normalizedY = m_axisCacheY.positionAt(data.y()); + m_minY = qMin(normalizedY, m_minY); + m_maxY = qMax(normalizedY, m_maxY); + vertex.setX(normalizedX); + vertex.setY(normalizedY); + vertex.setZ(normalizedZ); } GLuint SurfaceObject::gridElementBuf() @@ -680,6 +870,17 @@ GLuint SurfaceObject::gridElementBuf() return m_gridElementbuffer; } +GLuint SurfaceObject::uvBuf() +{ + if (!m_meshDataLoaded) + qFatal("No loaded object"); + + if (m_returnTextureBuffer) + return m_uvTextureBuffer; + else + return m_uvbuffer; +} + GLuint SurfaceObject::gridIndexCount() { return m_gridIndexCount; @@ -707,14 +908,74 @@ void SurfaceObject::clear() m_normals.clear(); } -QVector3D SurfaceObject::normal(const QVector3D &a, const QVector3D &b, const QVector3D &c, - bool flipNormal) +void SurfaceObject::createCoarseIndices(GLint *indices, int &p, int row, int upperRow, int j) +{ + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + // Left triangle + indices[p++] = row + j + 1; + indices[p++] = upperRow + j; + indices[p++] = row + j; + + // Right triangle + indices[p++] = upperRow + j + 1; + indices[p++] = upperRow + j; + indices[p++] = row + j + 1; + } else if (m_dataDimension == XDescending) { + indices[p++] = upperRow + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j; + + indices[p++] = row + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j + 1; + } else { + // Left triangle + indices[p++] = upperRow + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j + 1; + } +} + +void SurfaceObject::createNormals(int &p, int row, int upperRow, int j) +{ + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(row + j + 1), + m_vertices.at(upperRow + j)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(upperRow + j + 1), + m_vertices.at(upperRow + j)); + } else if (m_dataDimension == XDescending) { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(upperRow + j), + m_vertices.at(upperRow + j + 1)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(row + j), + m_vertices.at(upperRow + j + 1)); + } else { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(upperRow + j), + m_vertices.at(upperRow + j + 1)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(row + j), + m_vertices.at(upperRow + j + 1)); + } +} + +QVector3D SurfaceObject::normal(const QVector3D &a, const QVector3D &b, const QVector3D &c) { QVector3D v1 = b - a; QVector3D v2 = c - a; QVector3D normal = QVector3D::crossProduct(v1, v2); - if (flipNormal) - normal *= -1; + return normal; } diff --git a/src/datavisualization/utils/surfaceobject_p.h b/src/datavisualization/utils/surfaceobject_p.h index 9c18dcb2..e7b61310 100644 --- a/src/datavisualization/utils/surfaceobject_p.h +++ b/src/datavisualization/utils/surfaceobject_p.h @@ -49,34 +49,55 @@ public: Undefined }; + enum DataDimension { + BothAscending = 0, + XDescending = 1, + ZDescending = 2, + BothDescending = XDescending | ZDescending + }; + Q_DECLARE_FLAGS(DataDimensions, DataDimension) + public: SurfaceObject(Surface3DRenderer *renderer); - ~SurfaceObject(); + virtual ~SurfaceObject(); void setUpData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ = false); + bool changeGeometry, bool polar, bool flipXZ = false); void setUpSmoothData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ = false); - void updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex); - void updateSmoothRow(const QSurfaceDataArray &dataArray, int startRow); - void updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column); - void updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column); + bool changeGeometry, bool polar, bool flipXZ = false); + void smoothUVs(const QSurfaceDataArray &dataArray, const QSurfaceDataArray &modelArray); + void coarseUVs(const QSurfaceDataArray &dataArray, const QSurfaceDataArray &modelArray); + void updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar); + void updateSmoothRow(const QSurfaceDataArray &dataArray, int startRow, bool polar); + void updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column, bool polar); + void updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column, bool polar); void createSmoothIndices(int x, int y, int endX, int endY); - void createCoarseIndices(int x, int y, int columns, int rows); + void createCoarseSubSection(int x, int y, int columns, int rows); void createSmoothGridlineIndices(int x, int y, int endX, int endY); void createCoarseGridlineIndices(int x, int y, int endX, int endY); void uploadBuffers(); GLuint gridElementBuf(); + GLuint uvBuf(); GLuint gridIndexCount(); QVector3D vertexAt(int column, int row); void clear(); + float minYValue() const { return m_minY; } + float maxYValue() const { return m_maxY; } + inline void activateSurfaceTexture(bool value) { m_returnTextureBuffer = value; } private: - QVector3D normal(const QVector3D &a, const QVector3D &b, const QVector3D &c, bool flipNormal); + void createCoarseIndices(GLint *indices, int &p, int row, int upperRow, int j); + void createNormals(int &p, int row, int upperRow, int j); + void createSmoothNormalBodyLine(int &totalIndex, int column); + void createSmoothNormalUpperLine(int &totalIndex); + QVector3D createSmoothNormalBodyLineItem(int x, int y); + QVector3D createSmoothNormalUpperLineItem(int x, int y); + QVector3D normal(const QVector3D &a, const QVector3D &b, const QVector3D &c); void createBuffers(const QVector<QVector3D> &vertices, const QVector<QVector2D> &uvs, - const QVector<QVector3D> &normals, const GLint *indices, - bool changeGeometry); - bool checkFlipNormal(const QSurfaceDataArray &array); + const QVector<QVector3D> &normals, const GLint *indices); + void checkDirections(const QSurfaceDataArray &array); + inline void getNormalizedVertex(const QSurfaceDataItem &data, QVector3D &vertex, bool polar, + bool flipXZ); private: SurfaceType m_surfaceType; @@ -90,6 +111,13 @@ private: AxisRenderCache &m_axisCacheX; AxisRenderCache &m_axisCacheY; AxisRenderCache &m_axisCacheZ; + Surface3DRenderer *m_renderer; + float m_minY; + float m_maxY; + GLuint m_uvTextureBuffer; + bool m_returnTextureBuffer; + SurfaceObject::DataDimensions m_dataDimension; + SurfaceObject::DataDimensions m_oldDataDimension; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/texturehelper.cpp b/src/datavisualization/utils/texturehelper.cpp index 2a2a89dd..3944fb0c 100644 --- a/src/datavisualization/utils/texturehelper.cpp +++ b/src/datavisualization/utils/texturehelper.cpp @@ -21,12 +21,29 @@ #include <QtGui/QImage> #include <QtGui/QPainter> +#include <QtCore/QTime> QT_BEGIN_NAMESPACE_DATAVISUALIZATION +// Defined in shaderhelper.cpp +extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg); + TextureHelper::TextureHelper() { initializeOpenGLFunctions(); +#if !defined(QT_OPENGL_ES_2) + if (!Utils::isOpenGLES()) { + // Discard warnings about deprecated functions + QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs); + + m_openGlFunctions_2_1 = + QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>(); + m_openGlFunctions_2_1->initializeOpenGLFunctions(); + + // Restore original message handler + qInstallMessageHandler(handler); + } +#endif } TextureHelper::~TextureHelper() @@ -41,17 +58,16 @@ GLuint TextureHelper::create2DTexture(const QImage &image, bool useTrilinearFilt QImage texImage = image; -#if defined(QT_OPENGL_ES_2) - GLuint temp; - GLuint imageWidth = Utils::getNearestPowerOfTwo(image.width(), temp); - GLuint imageHeight = Utils::getNearestPowerOfTwo(image.height(), temp); - if (smoothScale) { - texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } else { - texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio); + if (!Utils::isOpenGLES()) { + GLuint imageWidth = Utils::getNearestPowerOfTwo(image.width()); + GLuint imageHeight = Utils::getNearestPowerOfTwo(image.height()); + if (smoothScale) { + texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } else { + texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio); + } } -#endif GLuint textureId; glGenTextures(1, &textureId); @@ -73,6 +89,53 @@ GLuint TextureHelper::create2DTexture(const QImage &image, bool useTrilinearFilt if (clampY) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); + + return textureId; +} + +GLuint TextureHelper::create3DTexture(const QVector<uchar> *data, int width, int height, int depth, + QImage::Format dataFormat) +{ + if (Utils::isOpenGLES() || !width || !height || !depth) + return 0; + + GLuint textureId = 0; +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(dataFormat) + Q_UNUSED(data) +#else + glEnable(GL_TEXTURE_3D); + + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_3D, textureId); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + GLenum status = glGetError(); + // glGetError docs advise to call glGetError in loop to clear all error flags + while (status) + status = glGetError(); + + GLint internalFormat = 4; + GLint format = GL_BGRA; + if (dataFormat == QImage::Format_Indexed8) { + internalFormat = 1; + format = GL_RED; + // Align width to 32bits + width = width + width % 4; + } + m_openGlFunctions_2_1->glTexImage3D(GL_TEXTURE_3D, 0, internalFormat, width, height, depth, 0, + format, GL_UNSIGNED_BYTE, data->constData()); + status = glGetError(); + if (status) + qWarning() << __FUNCTION__ << "3D texture creation failed:" << status; + + glBindTexture(GL_TEXTURE_3D, 0); + glDisable(GL_TEXTURE_3D); +#endif return textureId; } @@ -113,14 +176,27 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf glBindTexture(GL_TEXTURE_2D, 0); // Create render buffer - if (!depthBuffer) - glGenRenderbuffers(1, &depthBuffer); + if (depthBuffer) + glDeleteRenderbuffers(1, &depthBuffer); + + glGenRenderbuffers(1, &depthBuffer); glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); -#if !defined(QT_OPENGL_ES_2) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.width(), size.height()); -#else - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height()); -#endif + GLenum status = glGetError(); + // glGetError docs advise to call glGetError in loop to clear all error flags + while (status) + status = glGetError(); + if (Utils::isOpenGLES()) + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height()); + else + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.width(), size.height()); + + status = glGetError(); + if (status) { + qCritical() << "Selection texture render buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + return 0; + } glBindRenderbuffer(GL_RENDERBUFFER, 0); // Create frame buffer @@ -134,10 +210,11 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); // Verify that the frame buffer is complete - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { - qCritical() << "Frame buffer creation failed" << status; - return 0; + qCritical() << "Selection texture frame buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + textureid = 0; } // Restore the default framebuffer @@ -146,6 +223,31 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf return textureid; } +GLuint TextureHelper::createCursorPositionTexture(const QSize &size, GLuint &frameBuffer) +{ + GLuint textureid; + glGenTextures(1, &textureid); + glBindTexture(GL_TEXTURE_2D, textureid); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_RGBA, + GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &frameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + textureid, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qCritical() << "Cursor position mapper frame buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + textureid = 0; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return textureid; +} + GLuint TextureHelper::createUniformTexture(const QColor &color) { QImage image(QSize(int(uniformTextureWidth), int(uniformTextureHeight)), @@ -170,76 +272,64 @@ GLuint TextureHelper::createGradientTexture(const QLinearGradient &gradient) return create2DTexture(image, false, true, false, true); } -#if !defined(QT_OPENGL_ES_2) GLuint TextureHelper::createDepthTexture(const QSize &size, GLuint textureSize) { - GLuint depthtextureid; - - // Create depth texture for the shadow mapping - glGenTextures(1, &depthtextureid); - glBindTexture(GL_TEXTURE_2D, depthtextureid); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, - size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - + GLuint depthtextureid = 0; +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(size) + Q_UNUSED(textureSize) +#else + if (!Utils::isOpenGLES()) { + // Create depth texture for the shadow mapping + glGenTextures(1, &depthtextureid); + glBindTexture(GL_TEXTURE_2D, depthtextureid); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, + size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + } +#endif return depthtextureid; } -#endif -#if !defined(QT_OPENGL_ES_2) GLuint TextureHelper::createDepthTextureFrameBuffer(const QSize &size, GLuint &frameBuffer, GLuint textureSize) { GLuint depthtextureid = createDepthTexture(size, textureSize); +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(frameBuffer) +#else + if (!Utils::isOpenGLES()) { + // Create frame buffer + if (!frameBuffer) + glGenFramebuffers(1, &frameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); + + // Attach texture to depth attachment + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthtextureid, 0); + + m_openGlFunctions_2_1->glDrawBuffer(GL_NONE); + m_openGlFunctions_2_1->glReadBuffer(GL_NONE); + + // Verify that the frame buffer is complete + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qCritical() << "Depth texture frame buffer creation failed" << status; + glDeleteTextures(1, &depthtextureid); + depthtextureid = 0; + } - // Create frame buffer - if (!frameBuffer) - glGenFramebuffers(1, &frameBuffer); - glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); - - // Attach texture to depth attachment - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthtextureid, 0); - - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - - // Verify that the frame buffer is complete - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - qCritical() << "Frame buffer creation failed" << status; - return 0; + // Restore the default framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); } - - // Restore the default framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - return depthtextureid; -} #endif - -#if !defined(QT_OPENGL_ES_2) -void TextureHelper::fillDepthTexture(GLuint texture,const QSize &size, GLuint textureSize, - GLfloat value) -{ - int nItems = size.width() * textureSize * size.height() * textureSize; - GLfloat *bits = new GLfloat[nItems]; - for (int i = 0; i < nItems; i++) - bits[i] = value; - - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, - size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, bits); - glBindTexture(GL_TEXTURE_2D, 0); - - delete[] bits; + return depthtextureid; } -#endif void TextureHelper::deleteTexture(GLuint *texture) { diff --git a/src/datavisualization/utils/texturehelper_p.h b/src/datavisualization/utils/texturehelper_p.h index aec137a4..6c0aa3de 100644 --- a/src/datavisualization/utils/texturehelper_p.h +++ b/src/datavisualization/utils/texturehelper_p.h @@ -32,6 +32,10 @@ #include "datavisualizationglobal_p.h" #include <QtGui/QRgb> #include <QtGui/QLinearGradient> +#if !defined(QT_OPENGL_ES_2) +// 3D Textures are not supported by ES set +# include <QtGui/QOpenGLFunctions_2_1> +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -44,17 +48,17 @@ class TextureHelper : protected QOpenGLFunctions // Ownership of created texture is transferred to caller GLuint create2DTexture(const QImage &image, bool useTrilinearFiltering = false, bool convert = true, bool smoothScale = true, bool clampY = false); + GLuint create3DTexture(const QVector<uchar> *data, int width, int height, int depth, + QImage::Format dataFormat); GLuint createCubeMapTexture(const QImage &image, bool useTrilinearFiltering = false); // Returns selection texture and inserts generated framebuffers to framebuffer parameters GLuint createSelectionTexture(const QSize &size, GLuint &frameBuffer, GLuint &depthBuffer); + GLuint createCursorPositionTexture(const QSize &size, GLuint &frameBuffer); GLuint createUniformTexture(const QColor &color); GLuint createGradientTexture(const QLinearGradient &gradient); -#if !defined(QT_OPENGL_ES_2) GLuint createDepthTexture(const QSize &size, GLuint textureSize); // Returns depth texture and inserts generated framebuffer to parameter GLuint createDepthTextureFrameBuffer(const QSize &size, GLuint &frameBuffer, GLuint textureSize); - void fillDepthTexture(GLuint texture, const QSize &size, GLuint textureSize, GLfloat value); -#endif void deleteTexture(GLuint *texture); private: @@ -62,9 +66,13 @@ class TextureHelper : protected QOpenGLFunctions void convertToGLFormatHelper(QImage &dstImage, const QImage &srcImage, GLenum texture_format); QRgb qt_gl_convertToGLFormatHelper(QRgb src_pixel, GLenum texture_format); +#if !defined(QT_OPENGL_ES_2) + QOpenGLFunctions_2_1 *m_openGlFunctions_2_1; // Not owned +#endif friend class Bars3DRenderer; friend class Surface3DRenderer; friend class Scatter3DRenderer; + friend class Abstract3DRenderer; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/utils.cpp b/src/datavisualization/utils/utils.cpp index 2368d86a..cb64eb8f 100644 --- a/src/datavisualization/utils/utils.cpp +++ b/src/datavisualization/utils/utils.cpp @@ -17,6 +17,7 @@ ****************************************************************************/ #include "utils_p.h" +#include "qutils.h" #include <QtGui/QPainter> @@ -25,11 +26,12 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION #define NUM_IN_POWER(y, x) for (;y<x;y<<=1) #define MIN_POWER 2 -GLuint Utils::getNearestPowerOfTwo(GLuint value, GLuint &padding) +static GLint maxTextureSize = 0; // Safe, as all instances have the same texture size + +GLuint Utils::getNearestPowerOfTwo(GLuint value) { GLuint powOfTwoValue = MIN_POWER; NUM_IN_POWER(powOfTwoValue, value); - padding = powOfTwoValue - value; return powOfTwoValue; } @@ -54,35 +56,76 @@ QImage Utils::printTextToImage(const QFont &font, const QString &text, const QCo const QColor &txtColor, bool labelBackground, bool borders, int maxLabelWidth) { + if (maxTextureSize == 0) { + QOpenGLContext::currentContext()->functions()->glGetIntegerv( + GL_MAX_TEXTURE_SIZE, &maxTextureSize); + } GLuint paddingWidth = 20; GLuint paddingHeight = 20; + GLuint prePadding = 20; + GLint targetWidth = maxTextureSize; + // Calculate text dimensions QFont valueFont = font; valueFont.setPointSize(textureFontSize); QFontMetrics valueFM(valueFont); int valueStrWidth = valueFM.width(text); - if (maxLabelWidth && labelBackground) + + // ES2 needs to use maxLabelWidth always (when given) because of the power of 2 -issue. + if (maxLabelWidth && (labelBackground || Utils::isOpenGLES())) valueStrWidth = maxLabelWidth; int valueStrHeight = valueFM.height(); valueStrWidth += paddingWidth / 2; // Fix clipping problem with skewed fonts (italic or italic-style) QSize labelSize; + qreal fontRatio = 1.0; -#if defined(QT_OPENGL_ES_2) - // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly. - // Add some padding before converting to power of two to avoid too tight fit - GLuint prePadding = 5; - // ES2 needs to use this always (when given) because of the power of 2 -issue. - if (maxLabelWidth) - valueStrWidth = maxLabelWidth + paddingWidth / 2; - labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding); - labelSize.setWidth(getNearestPowerOfTwo(labelSize.width(), paddingWidth)); - labelSize.setHeight(getNearestPowerOfTwo(labelSize.height(), paddingHeight)); -#else - if (!labelBackground) - labelSize = QSize(valueStrWidth, valueStrHeight); - else - labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2); -#endif + if (Utils::isOpenGLES()) { + // Test if text with slighly smaller font would fit into one step smaller texture + // ie. if the text is just exceeded the smaller texture boundary, it would + // make a label with large empty space + uint testWidth = getNearestPowerOfTwo(valueStrWidth + prePadding) >> 1; + int diffToFit = (valueStrWidth + prePadding) - testWidth; + int maxSqueeze = int((valueStrWidth + prePadding) * 0.1f); + if (diffToFit < maxSqueeze && maxTextureSize > GLint(testWidth)) + targetWidth = testWidth; + } + + bool sizeOk = false; + int currentFontSize = textureFontSize; + do { + if (Utils::isOpenGLES()) { + // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly. + // Add some padding before converting to power of two to avoid too tight fit + labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding); + labelSize.setWidth(getNearestPowerOfTwo(labelSize.width())); + labelSize.setHeight(getNearestPowerOfTwo(labelSize.height())); + } else { + if (!labelBackground) + labelSize = QSize(valueStrWidth, valueStrHeight); + else + labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2); + } + + if (!maxTextureSize || (labelSize.width() <= maxTextureSize + && (labelSize.width() <= targetWidth || !Utils::isOpenGLES()))) { + // Make sure the label is not too wide + sizeOk = true; + } else if (--currentFontSize == 4) { + qCritical() << "Label" << text << "is too long to be generated."; + return QImage(); + } else { + fontRatio = (qreal)currentFontSize / (qreal)textureFontSize; + // Reduce font size and try again + valueFont.setPointSize(currentFontSize); + QFontMetrics currentValueFM(valueFont); + if (maxLabelWidth && (labelBackground || Utils::isOpenGLES())) + valueStrWidth = maxLabelWidth * fontRatio; + else + valueStrWidth = currentValueFM.width(text); + valueStrHeight = currentValueFM.height(); + valueStrWidth += paddingWidth / 2; + } + } while (!sizeOk); // Create image QImage image = QImage(labelSize, QImage::Format_ARGB32); @@ -96,27 +139,30 @@ QImage Utils::printTextToImage(const QFont &font, const QString &text, const QCo painter.setFont(valueFont); if (!labelBackground) { painter.setPen(txtColor); -#if defined(QT_OPENGL_ES_2) - painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, - (labelSize.height() - valueStrHeight) / 2.0f, - valueStrWidth, valueStrHeight, - Qt::AlignCenter | Qt::AlignVCenter, - text); -#else - painter.drawText(0, 0, - valueStrWidth, valueStrHeight, - Qt::AlignCenter | Qt::AlignVCenter, - text); -#endif + if (Utils::isOpenGLES()) { + painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, + (labelSize.height() - valueStrHeight) / 2.0f, + valueStrWidth, valueStrHeight, + Qt::AlignCenter | Qt::AlignVCenter, + text); + } else { + painter.drawText(0, 0, + valueStrWidth, valueStrHeight, + Qt::AlignCenter | Qt::AlignVCenter, + text); + } } else { painter.setBrush(QBrush(bgrColor)); + qreal radius = 10.0 * fontRatio; if (borders) { - painter.setPen(QPen(QBrush(txtColor), 5, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); - painter.drawRoundedRect(5, 5, labelSize.width() - 10, labelSize.height() - 10, - 10.0, 10.0); + painter.setPen(QPen(QBrush(txtColor), 5.0 * fontRatio, + Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); + painter.drawRoundedRect(5, 5, + labelSize.width() - 10, labelSize.height() - 10, + radius, radius); } else { painter.setPen(bgrColor); - painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), 10.0, 10.0); + painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), radius, radius); } painter.setPen(txtColor); painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, @@ -133,62 +179,74 @@ QVector4D Utils::getSelection(QPoint mousepos, int height) // This is the only one that works with OpenGL ES 2.0, so we're forced to use it // Item count will be limited to 256*256*256 GLubyte pixel[4] = {255, 255, 255, 255}; - glReadPixels(mousepos.x(), height - mousepos.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - (void *)pixel); + QOpenGLContext::currentContext()->functions()->glReadPixels(mousepos.x(), height - mousepos.y(), + 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, + (void *)pixel); QVector4D selectedColor(pixel[0], pixel[1], pixel[2], pixel[3]); return selectedColor; } -QImage Utils::getGradientImage(const QLinearGradient &gradient) +QImage Utils::getGradientImage(QLinearGradient &gradient) { - QImage image(QSize(1, 101), QImage::Format_RGB32); + QImage image(QSize(gradientTextureWidth, gradientTextureHeight), QImage::Format_RGB32); + gradient.setFinalStop(qreal(gradientTextureWidth), qreal(gradientTextureHeight)); + gradient.setStart(0.0, 0.0); + QPainter pmp(&image); pmp.setBrush(QBrush(gradient)); pmp.setPen(Qt::NoPen); - pmp.drawRect(0, 0, 1, 101); + pmp.drawRect(0, 0, int(gradientTextureWidth), int(gradientTextureHeight)); return image; } -Utils::ParamType Utils::mapFormatCharToParamType(const QChar &formatChar) +Utils::ParamType Utils::preParseFormat(const QString &format, QString &preStr, QString &postStr, + int &precision, char &formatSpec) { - ParamType retVal = ParamTypeUnknown; - if (formatChar == QLatin1Char('d') - || formatChar == QLatin1Char('i') - || formatChar == QLatin1Char('c')) { - retVal = ParamTypeInt; - } else if (formatChar == QLatin1Char('u') - || formatChar == QLatin1Char('o') - || formatChar == QLatin1Char('x') - || formatChar == QLatin1Char('X')) { - retVal = ParamTypeUInt; - } else if (formatChar == QLatin1Char('f') - || formatChar == QLatin1Char('F') - || formatChar == QLatin1Char('e') - || formatChar == QLatin1Char('E') - || formatChar == QLatin1Char('g') - || formatChar == QLatin1Char('G')) { - retVal = ParamTypeReal; + static QRegExp formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$")); + static QRegExp precisionMatcher(QStringLiteral("\\.(\\d+)")); + + Utils::ParamType retVal; + + if (formatMatcher.indexIn(format, 0) != -1) { + preStr = formatMatcher.cap(1); + // Six and 'g' are defaults in Qt API + precision = 6; + if (!formatMatcher.cap(2).isEmpty()) { + if (precisionMatcher.indexIn(formatMatcher.cap(2), 0) != -1) + precision = precisionMatcher.cap(1).toInt(); + } + if (formatMatcher.cap(3).isEmpty()) + formatSpec = 'g'; + else + formatSpec = formatMatcher.cap(3).at(0).toLatin1(); + postStr = formatMatcher.cap(4); + retVal = mapFormatCharToParamType(formatSpec); + } else { + retVal = ParamTypeUnknown; + // The out parameters are irrelevant in unknown case } return retVal; } -Utils::ParamType Utils::findFormatParamType(const QString &format) +Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec) { - static QRegExp formatMatcher(QStringLiteral("%[\\-\\+#\\s\\d\\.lhjztL]*([dicuoxfegXFEG])")); - - if (formatMatcher.indexIn(format, 0) != -1) { - QString capStr = formatMatcher.cap(1); - if (capStr.isEmpty()) - return ParamTypeUnknown; - else - return mapFormatCharToParamType(capStr.at(0)); + ParamType retVal = ParamTypeUnknown; + if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') { + retVal = ParamTypeInt; + } else if (formatSpec == 'u' || formatSpec == 'o' + || formatSpec == 'x'|| formatSpec == 'X') { + retVal = ParamTypeUInt; + } else if (formatSpec == 'f' || formatSpec == 'F' + || formatSpec == 'e' || formatSpec == 'E' + || formatSpec == 'g' || formatSpec == 'G') { + retVal = ParamTypeReal; } - return ParamTypeUnknown; + return retVal; } -QString Utils::formatLabel(const QByteArray &format, ParamType paramType, qreal value) +QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value) { switch (paramType) { case ParamTypeInt: @@ -198,7 +256,24 @@ QString Utils::formatLabel(const QByteArray &format, ParamType paramType, qreal case ParamTypeReal: return QString().sprintf(format, value); default: - return QString::fromUtf8(format); // To detect errors + // Return format string to detect errors. Bars selection label logic also depends on this. + return QString::fromUtf8(format); + } +} + +QString Utils::formatLabelLocalized(Utils::ParamType paramType, qreal value, + const QLocale &locale, const QString &preStr, const QString &postStr, + int precision, char formatSpec, const QByteArray &format) +{ + switch (paramType) { + case ParamTypeInt: + case ParamTypeUInt: + return preStr + locale.toString(qint64(value)) + postStr; + case ParamTypeReal: + return preStr + locale.toString(value, formatSpec, precision) + postStr; + default: + // Return format string to detect errors. Bars selection label logic also depends on this. + return QString::fromUtf8(format); } } @@ -238,4 +313,41 @@ QQuaternion Utils::calculateRotation(const QVector3D &xyzRotations) return totalRotation; } +bool Utils::isOpenGLES() +{ +#if defined(QT_OPENGL_ES_2) + return true; +#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) + return false; +#else + static bool resolved = false; + static bool isES = false; + if (!resolved) { + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QWindow *dummySurface = 0; + if (!ctx) { + QSurfaceFormat surfaceFormat = qDefaultSurfaceFormat(); + dummySurface = new QWindow(); + dummySurface->setSurfaceType(QWindow::OpenGLSurface); + dummySurface->setFormat(surfaceFormat); + dummySurface->create(); + ctx = new QOpenGLContext; + ctx->setFormat(surfaceFormat); + ctx->create(); + ctx->makeCurrent(dummySurface); + } + + isES = ctx->isOpenGLES(); + resolved = true; + + if (dummySurface) { + ctx->doneCurrent(); + delete ctx; + delete dummySurface; + } + } + return isES; +#endif +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/utils.pri b/src/datavisualization/utils/utils.pri index 30bfb156..904407b8 100644 --- a/src/datavisualization/utils/utils.pri +++ b/src/datavisualization/utils/utils.pri @@ -22,3 +22,5 @@ SOURCES += $$PWD/meshloader.cpp \ $$PWD/surfaceobject.cpp \ $$PWD/scatterobjectbufferhelper.cpp \ $$PWD/scatterpointbufferhelper.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/utils/utils_p.h b/src/datavisualization/utils/utils_p.h index d7187c16..1a46c731 100644 --- a/src/datavisualization/utils/utils_p.h +++ b/src/datavisualization/utils/utils_p.h @@ -45,7 +45,7 @@ public: ParamTypeReal }; - static GLuint getNearestPowerOfTwo(GLuint value, GLuint &padding); + static GLuint getNearestPowerOfTwo(GLuint value); static QVector4D vectorFromColor(const QColor &color); static QColor colorFromVector(const QVector3D &colorVector); static QColor colorFromVector(const QVector4D &colorVector); @@ -57,17 +57,22 @@ public: bool borders = false, int maxLabelWidth = 0); static QVector4D getSelection(QPoint mousepos, int height); - static QImage getGradientImage(const QLinearGradient &gradient); + static QImage getGradientImage(QLinearGradient &gradient); - static ParamType findFormatParamType(const QString &format); - static QString formatLabel(const QByteArray &format, ParamType paramType, qreal value); + static ParamType preParseFormat(const QString &format, QString &preStr, QString &postStr, + int &precision, char &formatSpec); + static QString formatLabelSprintf(const QByteArray &format, ParamType paramType, qreal value); + static QString formatLabelLocalized(ParamType paramType, qreal value, + const QLocale &locale, const QString &preStr, const QString &postStr, + int precision, char formatSpec, const QByteArray &format); static QString defaultLabelFormat(); static float wrapValue(float value, float min, float max); static QQuaternion calculateRotation(const QVector3D &xyzRotations); + static bool isOpenGLES(); private: - static ParamType mapFormatCharToParamType(const QChar &formatChar); + static ParamType mapFormatCharToParamType(char formatSpec); }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualizationqml2/abstractdeclarative.cpp b/src/datavisualizationqml2/abstractdeclarative.cpp index f7ccbf21..51c254ec 100644 --- a/src/datavisualizationqml2/abstractdeclarative.cpp +++ b/src/datavisualizationqml2/abstractdeclarative.cpp @@ -19,11 +19,13 @@ #include "abstractdeclarative_p.h" #include "declarativetheme_p.h" #include "declarativerendernode_p.h" - #include <QtGui/QGuiApplication> #if defined(Q_OS_IOS) #include <QtCore/QTimer> #endif +#if defined(Q_OS_OSX) +#include <qpa/qplatformnativeinterface.h> +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -36,11 +38,7 @@ AbstractDeclarative::AbstractDeclarative(QQuickItem *parent) : m_controller(0), m_contextWindow(0), m_renderMode(RenderIndirect), - #if defined(QT_OPENGL_ES_2) m_samples(0), - #else - m_samples(4), - #endif m_windowSamples(0), m_initialisedSize(0, 0), #ifdef USE_SHARED_CONTEXT @@ -53,7 +51,6 @@ AbstractDeclarative::AbstractDeclarative(QQuickItem *parent) : m_contextThread(0) { connect(this, &QQuickItem::windowChanged, this, &AbstractDeclarative::handleWindowChanged); - setAntialiasing(m_samples > 0); // Set contents to false in case we are in qml designer to make component look nice m_runningInDesigner = QGuiApplication::applicationDisplayName() == "Qml2Puppet"; @@ -62,24 +59,7 @@ AbstractDeclarative::AbstractDeclarative(QQuickItem *parent) : AbstractDeclarative::~AbstractDeclarative() { -#ifdef USE_SHARED_CONTEXT - // Context can be in another thread, don't delete it directly in that case - if (m_contextThread && m_contextThread != m_mainThread) { - if (m_context) - m_context->deleteLater(); - m_context = 0; - } else { - delete m_context; - } -#else - if (m_contextThread && m_contextThread != m_mainThread) { - if (m_stateStore) - m_stateStore->deleteLater(); - m_stateStore = 0; - } else { - delete m_stateStore; - } -#endif + destroyContext(); disconnect(this, 0, this, 0); checkWindowList(0); @@ -293,6 +273,10 @@ void AbstractDeclarative::setSharedController(Abstract3DController *controller) Q_ASSERT(controller); m_controller = controller; + if (!m_controller->isOpenGLES()) + m_samples = 4; + setAntialiasing(m_samples > 0); + // Reset default theme, as the default C++ theme is Q3DTheme, not DeclarativeTheme3D. DeclarativeTheme3D *defaultTheme = new DeclarativeTheme3D; defaultTheme->d_ptr->setDefaultTheme(true); @@ -329,6 +313,22 @@ void AbstractDeclarative::setSharedController(Abstract3DController *controller) &AbstractDeclarative::aspectRatioChanged); QObject::connect(m_controller.data(), &Abstract3DController::optimizationHintsChanged, this, &AbstractDeclarative::handleOptimizationHintChange); + QObject::connect(m_controller.data(), &Abstract3DController::polarChanged, this, + &AbstractDeclarative::polarChanged); + QObject::connect(m_controller.data(), &Abstract3DController::radialLabelOffsetChanged, this, + &AbstractDeclarative::radialLabelOffsetChanged); + QObject::connect(m_controller.data(), &Abstract3DController::horizontalAspectRatioChanged, this, + &AbstractDeclarative::horizontalAspectRatioChanged); + QObject::connect(m_controller.data(), &Abstract3DController::reflectionChanged, this, + &AbstractDeclarative::reflectionChanged); + QObject::connect(m_controller.data(), &Abstract3DController::reflectivityChanged, this, + &AbstractDeclarative::reflectivityChanged); + QObject::connect(m_controller.data(), &Abstract3DController::localeChanged, this, + &AbstractDeclarative::localeChanged); + QObject::connect(m_controller.data(), &Abstract3DController::queriedGraphPositionChanged, this, + &AbstractDeclarative::queriedGraphPositionChanged); + QObject::connect(m_controller.data(), &Abstract3DController::marginChanged, this, + &AbstractDeclarative::marginChanged); } void AbstractDeclarative::activateOpenGLContext(QQuickWindow *window) @@ -347,7 +347,6 @@ void AbstractDeclarative::activateOpenGLContext(QQuickWindow *window) m_contextThread = QThread::currentThread(); m_contextWindow = window; m_qtContext = currentContext; - m_context = new QOpenGLContext(); m_context->setFormat(m_qtContext->format()); m_context->setShareContext(m_qtContext); @@ -355,6 +354,10 @@ void AbstractDeclarative::activateOpenGLContext(QQuickWindow *window) m_context->makeCurrent(window); m_controller->initializeOpenGL(); + + // Make sure context gets deleted. + QObject::connect(m_contextThread, &QThread::finished, this, + &AbstractDeclarative::destroyContext, Qt::DirectConnection); } else { m_context->makeCurrent(window); } @@ -376,6 +379,10 @@ void AbstractDeclarative::activateOpenGLContext(QQuickWindow *window) m_stateStore->storeGLState(); m_controller->initializeOpenGL(); + + // Make sure state store gets deleted. + QObject::connect(m_contextThread, &QThread::finished, this, + &AbstractDeclarative::destroyContext, Qt::DirectConnection); } else { m_stateStore->storeGLState(); } @@ -416,17 +423,15 @@ void AbstractDeclarative::setMsaaSamples(int samples) if (m_renderMode != RenderIndirect) { qWarning("Multisampling cannot be adjusted in this render mode"); } else { -#if defined(QT_OPENGL_ES_2) - if (samples > 0) - qWarning("Multisampling is not supported in OpenGL ES2"); -#else - if (m_samples != samples) { + if (m_controller->isOpenGLES()) { + if (samples > 0) + qWarning("Multisampling is not supported in OpenGL ES2"); + } else if (m_samples != samples) { m_samples = samples; setAntialiasing(m_samples > 0); emit msaaSamplesChanged(samples); update(); } -#endif } } @@ -437,6 +442,18 @@ void AbstractDeclarative::handleWindowChanged(QQuickWindow *window) if (!window) return; +#if defined(Q_OS_OSX) + bool previousVisibility = window->isVisible(); + // Enable touch events for Mac touchpads + window->setVisible(true); + typedef void * (*EnableTouch)(QWindow*, bool); + EnableTouch enableTouch = + (EnableTouch)QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow"); + if (enableTouch) + enableTouch(window, true); + window->setVisible(previousVisibility); +#endif + connect(window, &QObject::destroyed, this, &AbstractDeclarative::windowDestroyed); int oldWindowSamples = m_windowSamples; @@ -556,24 +573,25 @@ void AbstractDeclarative::render() // Clear the background once per window as that is not done by default QQuickWindow *win = window(); activateOpenGLContext(win); + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); if (m_renderMode == RenderDirectToBackground && !clearList.contains(win)) { clearList.append(win); QColor clearColor = win->color(); - glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + funcs->glClearColor(clearColor.redF(), clearColor.greenF(), clearColor.blueF(), 1.0f); + funcs->glClear(GL_COLOR_BUFFER_BIT); } if (isVisible()) { - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glDisable(GL_BLEND); + funcs->glDepthMask(GL_TRUE); + funcs->glEnable(GL_DEPTH_TEST); + funcs->glDepthFunc(GL_LESS); + funcs->glEnable(GL_CULL_FACE); + funcs->glCullFace(GL_BACK); + funcs->glDisable(GL_BLEND); m_controller->render(); - glEnable(GL_BLEND); + funcs->glEnable(GL_BLEND); } doneOpenGLContext(win); } @@ -704,7 +722,7 @@ AbstractDeclarative::ElementType AbstractDeclarative::selectedElement() const void AbstractDeclarative::setAspectRatio(qreal ratio) { - m_controller->setAspectRatio(float(ratio)); + m_controller->setAspectRatio(ratio); } qreal AbstractDeclarative::aspectRatio() const @@ -724,6 +742,81 @@ AbstractDeclarative::OptimizationHints AbstractDeclarative::optimizationHints() return OptimizationHints(intmode); } +void AbstractDeclarative::setPolar(bool enable) +{ + m_controller->setPolar(enable); +} + +bool AbstractDeclarative::isPolar() const +{ + return m_controller->isPolar(); +} + +void AbstractDeclarative::setRadialLabelOffset(float offset) +{ + m_controller->setRadialLabelOffset(offset); +} + +float AbstractDeclarative::radialLabelOffset() const +{ + return m_controller->radialLabelOffset(); +} + +void AbstractDeclarative::setHorizontalAspectRatio(qreal ratio) +{ + m_controller->setHorizontalAspectRatio(ratio); +} + +qreal AbstractDeclarative::horizontalAspectRatio() const +{ + return m_controller->horizontalAspectRatio(); +} + +void AbstractDeclarative::setReflection(bool enable) +{ + m_controller->setReflection(enable); +} + +bool AbstractDeclarative::isReflection() const +{ + return m_controller->reflection(); +} + +void AbstractDeclarative::setReflectivity(qreal reflectivity) +{ + m_controller->setReflectivity(reflectivity); +} + +qreal AbstractDeclarative::reflectivity() const +{ + return m_controller->reflectivity(); +} + +void AbstractDeclarative::setLocale(const QLocale &locale) +{ + m_controller->setLocale(locale); +} + +QLocale AbstractDeclarative::locale() const +{ + return m_controller->locale(); +} + +QVector3D AbstractDeclarative::queriedGraphPosition() const +{ + return m_controller->queriedGraphPosition(); +} + +void AbstractDeclarative::setMargin(qreal margin) +{ + m_controller->setMargin(margin); +} + +qreal AbstractDeclarative::margin() const +{ + return m_controller->margin(); +} + void AbstractDeclarative::windowDestroyed(QObject *obj) { // Remove destroyed window from window lists @@ -736,4 +829,31 @@ void AbstractDeclarative::windowDestroyed(QObject *obj) windowClearList.remove(win); } +void AbstractDeclarative::destroyContext() +{ +#ifdef USE_SHARED_CONTEXT + // Context can be in another thread, don't delete it directly in that case + if (m_contextThread && m_contextThread != m_mainThread) { + if (m_context) + m_context->deleteLater(); + } else { + delete m_context; + } + m_context = 0; +#else + if (m_contextThread && m_contextThread != m_mainThread) { + if (m_stateStore) + m_stateStore->deleteLater(); + } else { + delete m_stateStore; + } + m_stateStore = 0; +#endif + if (m_contextThread) { + QObject::disconnect(m_contextThread, &QThread::finished, this, + &AbstractDeclarative::destroyContext); + m_contextThread = 0; + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualizationqml2/abstractdeclarative_p.h b/src/datavisualizationqml2/abstractdeclarative_p.h index dfcd9537..0c32a4a5 100644 --- a/src/datavisualizationqml2/abstractdeclarative_p.h +++ b/src/datavisualizationqml2/abstractdeclarative_p.h @@ -72,6 +72,14 @@ class AbstractDeclarative : public QQuickItem Q_PROPERTY(ElementType selectedElement READ selectedElement NOTIFY selectedElementChanged REVISION 1) Q_PROPERTY(qreal aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged REVISION 1) Q_PROPERTY(OptimizationHints optimizationHints READ optimizationHints WRITE setOptimizationHints NOTIFY optimizationHintsChanged REVISION 1) + Q_PROPERTY(bool polar READ isPolar WRITE setPolar NOTIFY polarChanged REVISION 2) + Q_PROPERTY(float radialLabelOffset READ radialLabelOffset WRITE setRadialLabelOffset NOTIFY radialLabelOffsetChanged REVISION 2) + Q_PROPERTY(qreal horizontalAspectRatio READ horizontalAspectRatio WRITE setHorizontalAspectRatio NOTIFY horizontalAspectRatioChanged REVISION 2) + Q_PROPERTY(bool reflection READ isReflection WRITE setReflection NOTIFY reflectionChanged REVISION 2) + Q_PROPERTY(qreal reflectivity READ reflectivity WRITE setReflectivity NOTIFY reflectivityChanged REVISION 2) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged REVISION 2) + Q_PROPERTY(QVector3D queriedGraphPosition READ queriedGraphPosition NOTIFY queriedGraphPositionChanged REVISION 2) + Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged REVISION 2) public: enum SelectionFlag { @@ -193,11 +201,35 @@ public: void setOptimizationHints(OptimizationHints hints); OptimizationHints optimizationHints() const; + void setPolar(bool enable); + bool isPolar() const; + + void setRadialLabelOffset(float offset); + float radialLabelOffset() const; + + void setHorizontalAspectRatio(qreal ratio); + qreal horizontalAspectRatio() const; + + void setReflection(bool enable); + bool isReflection() const; + + void setReflectivity(qreal reflectivity); + qreal reflectivity() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + QVector3D queriedGraphPosition() const; + + void setMargin(qreal margin); + qreal margin() const; + public slots: virtual void handleAxisXChanged(QAbstract3DAxis *axis) = 0; virtual void handleAxisYChanged(QAbstract3DAxis *axis) = 0; virtual void handleAxisZChanged(QAbstract3DAxis *axis) = 0; void windowDestroyed(QObject *obj); + void destroyContext(); protected: virtual void mouseDoubleClickEvent(QMouseEvent *event); @@ -230,6 +262,14 @@ signals: Q_REVISION(1) void orthoProjectionChanged(bool enabled); Q_REVISION(1) void aspectRatioChanged(qreal ratio); Q_REVISION(1) void optimizationHintsChanged(AbstractDeclarative::OptimizationHints hints); + Q_REVISION(2) void polarChanged(bool enabled); + Q_REVISION(2) void radialLabelOffsetChanged(float offset); + Q_REVISION(2) void horizontalAspectRatioChanged(qreal ratio); + Q_REVISION(2) void reflectionChanged(bool enabled); + Q_REVISION(2) void reflectivityChanged(qreal reflectivity); + Q_REVISION(2) void localeChanged(const QLocale &locale); + Q_REVISION(2) void queriedGraphPositionChanged(const QVector3D &data); + Q_REVISION(2) void marginChanged(qreal margin); private: QPointer<Abstract3DController> m_controller; diff --git a/src/datavisualizationqml2/datavisualizationqml2.pro b/src/datavisualizationqml2/datavisualizationqml2.pro index 7c65d69e..87376e70 100644 --- a/src/datavisualizationqml2/datavisualizationqml2.pro +++ b/src/datavisualizationqml2/datavisualizationqml2.pro @@ -1,5 +1,6 @@ TARGET = datavisualizationqml2 QT += qml quick datavisualization +osx: QT += gui-private TARGETPATH = QtDataVisualization IMPORT_VERSION = $$MODULE_VERSION diff --git a/src/datavisualizationqml2/datavisualizationqml2_plugin.cpp b/src/datavisualizationqml2/datavisualizationqml2_plugin.cpp index 09780dc5..5aaebf03 100644 --- a/src/datavisualizationqml2/datavisualizationqml2_plugin.cpp +++ b/src/datavisualizationqml2/datavisualizationqml2_plugin.cpp @@ -93,6 +93,7 @@ void QtDataVisualizationQml2Plugin::registerTypes(const char *uri) QLatin1String("Trying to create uncreatable: Abstract3DSeries.")); qmlRegisterUncreatableType<AbstractDeclarative, 1>(uri, 1, 1, "AbstractGraph3D", QLatin1String("Trying to create uncreatable: AbstractGraph3D.")); + qmlRegisterType<QValue3DAxis, 1>(uri, 1, 1, "ValueAxis3D"); qmlRegisterType<QItemModelBarDataProxy, 1>(uri, 1, 1, "ItemModelBarDataProxy"); qmlRegisterType<QItemModelSurfaceDataProxy, 1>(uri, 1, 1, "ItemModelSurfaceDataProxy"); @@ -106,6 +107,23 @@ void QtDataVisualizationQml2Plugin::registerTypes(const char *uri) // New metatypes qRegisterMetaType<QAbstract3DGraph::ElementType>("QAbstract3DGraph::ElementType"); + + // QtDataVisualization 1.2 + + // New revisions + qmlRegisterUncreatableType<AbstractDeclarative, 2>(uri, 1, 2, "AbstractGraph3D", + QLatin1String("Trying to create uncreatable: AbstractGraph3D.")); + qmlRegisterUncreatableType<Declarative3DScene, 1>(uri, 1, 2, "Scene3D", + QLatin1String("Trying to create uncreatable: Scene3D.")); + qmlRegisterType<DeclarativeSurface, 1>(uri, 1, 2, "Surface3D"); + qmlRegisterType<Q3DCamera, 1>(uri, 1, 2, "Camera3D"); + qmlRegisterType<QCustom3DItem, 1>(uri, 1, 2, "Custom3DItem"); + qmlRegisterType<DeclarativeBars, 1>(uri, 1, 2, "Bars3D"); + + // New types + qmlRegisterType<Q3DInputHandler>(uri, 1, 2, "InputHandler3D"); + qmlRegisterType<QTouch3DInputHandler>(uri, 1, 2, "TouchInputHandler3D"); + qmlRegisterType<QCustom3DVolume>(uri, 1, 2, "Custom3DVolume"); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualizationqml2/datavisualizationqml2_plugin.h b/src/datavisualizationqml2/datavisualizationqml2_plugin.h index 21ef85b8..8ece1c15 100644 --- a/src/datavisualizationqml2/datavisualizationqml2_plugin.h +++ b/src/datavisualizationqml2/datavisualizationqml2_plugin.h @@ -42,11 +42,13 @@ #include "declarativeseries_p.h" #include "q3dtheme.h" #include "declarativetheme_p.h" -#include "qabstract3dinputhandler.h" +#include "q3dinputhandler.h" +#include "qtouch3dinputhandler.h" #include "declarativecolor_p.h" #include "declarativescene_p.h" #include "qcustom3ditem.h" #include "qcustom3dlabel.h" +#include "qcustom3dvolume.h" #include <QtQml/QQmlExtensionPlugin> @@ -97,9 +99,12 @@ QML_DECLARE_TYPE(Q3DTheme) QML_DECLARE_TYPE(DeclarativeTheme3D) QML_DECLARE_TYPE(QAbstract3DInputHandler) +QML_DECLARE_TYPE(Q3DInputHandler) +QML_DECLARE_TYPE(QTouch3DInputHandler) QML_DECLARE_TYPE(QCustom3DItem) QML_DECLARE_TYPE(QCustom3DLabel) +QML_DECLARE_TYPE(QCustom3DVolume) QT_BEGIN_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualizationqml2/declarativebars.cpp b/src/datavisualizationqml2/declarativebars.cpp index 9670a7db..61e6aafb 100644 --- a/src/datavisualizationqml2/declarativebars.cpp +++ b/src/datavisualizationqml2/declarativebars.cpp @@ -129,6 +129,19 @@ QBar3DSeries *DeclarativeBars::selectedSeries() const return m_barsController->selectedSeries(); } +void DeclarativeBars::setFloorLevel(float level) +{ + if (level != floorLevel()) { + m_barsController->setFloorLevel(level); + emit floorLevelChanged(level); + } +} + +float DeclarativeBars::floorLevel() const +{ + return m_barsController->floorLevel(); +} + QQmlListProperty<QBar3DSeries> DeclarativeBars::seriesList() { return QQmlListProperty<QBar3DSeries>(this, this, diff --git a/src/datavisualizationqml2/declarativebars_p.h b/src/datavisualizationqml2/declarativebars_p.h index 52690813..05d66cdc 100644 --- a/src/datavisualizationqml2/declarativebars_p.h +++ b/src/datavisualizationqml2/declarativebars_p.h @@ -48,6 +48,7 @@ class DeclarativeBars : public AbstractDeclarative Q_PROPERTY(QQmlListProperty<QBar3DSeries> seriesList READ seriesList) Q_PROPERTY(QBar3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) Q_PROPERTY(QBar3DSeries *primarySeries READ primarySeries WRITE setPrimarySeries NOTIFY primarySeriesChanged) + Q_PROPERTY(float floorLevel READ floorLevel WRITE setFloorLevel NOTIFY floorLevelChanged REVISION 1) Q_CLASSINFO("DefaultProperty", "seriesList") public: @@ -85,6 +86,9 @@ public: QBar3DSeries *primarySeries() const; QBar3DSeries *selectedSeries() const; + void setFloorLevel(float level); + float floorLevel() const; + public slots: void handleAxisXChanged(QAbstract3DAxis *axis); void handleAxisYChanged(QAbstract3DAxis *axis); @@ -101,6 +105,7 @@ signals: void meshFileNameChanged(QString filename); void primarySeriesChanged(QBar3DSeries *series); void selectedSeriesChanged(QBar3DSeries *series); + Q_REVISION(1) void floorLevelChanged(float level); private: Bars3DController *m_barsController; diff --git a/src/datavisualizationqml2/declarativesurface.cpp b/src/datavisualizationqml2/declarativesurface.cpp index 3075d207..ec520459 100644 --- a/src/datavisualizationqml2/declarativesurface.cpp +++ b/src/datavisualizationqml2/declarativesurface.cpp @@ -32,6 +32,8 @@ DeclarativeSurface::DeclarativeSurface(QQuickItem *parent) QObject::connect(m_surfaceController, &Surface3DController::selectedSeriesChanged, this, &DeclarativeSurface::selectedSeriesChanged); + QObject::connect(m_surfaceController, &Surface3DController::flipHorizontalGridChanged, + this, &DeclarativeSurface::flipHorizontalGridChanged); } DeclarativeSurface::~DeclarativeSurface() @@ -74,6 +76,16 @@ QSurface3DSeries *DeclarativeSurface::selectedSeries() const return m_surfaceController->selectedSeries(); } +void DeclarativeSurface::setFlipHorizontalGrid(bool flip) +{ + m_surfaceController->setFlipHorizontalGrid(flip); +} + +bool DeclarativeSurface::flipHorizontalGrid() const +{ + return m_surfaceController->flipHorizontalGrid(); +} + QQmlListProperty<QSurface3DSeries> DeclarativeSurface::seriesList() { return QQmlListProperty<QSurface3DSeries>(this, this, diff --git a/src/datavisualizationqml2/declarativesurface_p.h b/src/datavisualizationqml2/declarativesurface_p.h index 6fe800ba..ff6e4d70 100644 --- a/src/datavisualizationqml2/declarativesurface_p.h +++ b/src/datavisualizationqml2/declarativesurface_p.h @@ -44,6 +44,7 @@ class DeclarativeSurface : public AbstractDeclarative Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged) Q_PROPERTY(QSurface3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) Q_PROPERTY(QQmlListProperty<QSurface3DSeries> seriesList READ seriesList) + Q_PROPERTY(bool flipHorizontalGrid READ flipHorizontalGrid WRITE setFlipHorizontalGrid NOTIFY flipHorizontalGridChanged REVISION 1) Q_CLASSINFO("DefaultProperty", "seriesList") public: @@ -66,6 +67,8 @@ public: Q_INVOKABLE void removeSeries(QSurface3DSeries *series); QSurface3DSeries *selectedSeries() const; + void setFlipHorizontalGrid(bool flip); + bool flipHorizontalGrid() const; public slots: void handleAxisXChanged(QAbstract3DAxis *axis); @@ -77,6 +80,7 @@ signals: void axisYChanged(QValue3DAxis *axis); void axisZChanged(QValue3DAxis *axis); void selectedSeriesChanged(QSurface3DSeries *series); + Q_REVISION(1) void flipHorizontalGridChanged(bool flip); private: Surface3DController *m_surfaceController; diff --git a/src/datavisualizationqml2/designer/Bars3DSpecifics.qml b/src/datavisualizationqml2/designer/Bars3DSpecifics.qml index cb5fb4a0..bd32d383 100644 --- a/src/datavisualizationqml2/designer/Bars3DSpecifics.qml +++ b/src/datavisualizationqml2/designer/Bars3DSpecifics.qml @@ -288,6 +288,92 @@ Column { Layout.fillWidth: true } } + Label { + text: qsTr("aspectRatio") + toolTip: qsTr("Aspect Ratio") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.aspectRatio + minimumValue: 0.01 + maximumValue: 100.0 + stepSize: 0.01 + decimals: 2 + Layout.fillWidth: true + } + } + Label { + text: qsTr("floorLevel") + toolTip: qsTr("Floor Level") + Layout.fillWidth: true + } + SecondColumnLayout { + LineEdit { + backendValue: backendValues.floorLevel + inputMethodHints: Qt.ImhFormattedNumbersOnly + Layout.fillWidth: true + } + } + Label { + text: qsTr("horizontalAspectRatio") + toolTip: qsTr("Horizontal Aspect Ratio") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.horizontalAspectRatio + minimumValue: 0.0 + maximumValue: 100.0 + stepSize: 0.01 + decimals: 2 + Layout.fillWidth: true + } + } + Label { + text: qsTr("reflection") + toolTip: qsTr("Reflection") + Layout.fillWidth: true + } + SecondColumnLayout { + CheckBox { + id: reflectionCheckbox + backendValue: backendValues.reflection + Layout.fillWidth: true + } + } + Label { + text: qsTr("reflectivity") + toolTip: qsTr("Reflectivity") + Layout.fillWidth: true + visible: reflectionCheckbox.checked + } + SecondColumnLayout { + visible: reflectionCheckbox.checked + SpinBox { + backendValue: backendValues.reflectivity + minimumValue: 0.0 + maximumValue: 1.0 + stepSize: 0.01 + decimals: 1 + Layout.fillWidth: true + } + } + Label { + text: qsTr("margin") + toolTip: qsTr("Graph Margin") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.margin + minimumValue: -1.0 + maximumValue: 100.0 + stepSize: 0.1 + decimals: 1 + Layout.fillWidth: true + } + } // Kept for debugging Label { } diff --git a/src/datavisualizationqml2/designer/Scatter3DSpecifics.qml b/src/datavisualizationqml2/designer/Scatter3DSpecifics.qml index 1e2556ec..131f71fd 100644 --- a/src/datavisualizationqml2/designer/Scatter3DSpecifics.qml +++ b/src/datavisualizationqml2/designer/Scatter3DSpecifics.qml @@ -121,6 +121,78 @@ Column { Layout.fillWidth: true } } + Label { + text: qsTr("optimizationHints") + toolTip: qsTr("Optimization Hints") + Layout.fillWidth: true + } + SecondColumnLayout { + ComboBox { + backendValue: backendValues.optimizationHints + model: ["OptimizationDefault", "OptimizationStatic"] + Layout.fillWidth: true + scope: "AbstractGraph3D" + } + } + Label { + text: qsTr("polar") + toolTip: qsTr("Use Polar Coordinates") + Layout.fillWidth: true + } + SecondColumnLayout { + CheckBox { + id: polarCheckbox + backendValue: backendValues.polar + Layout.fillWidth: true + } + } + Label { + text: qsTr("radialLabelOffset") + toolTip: qsTr("Radial Label Offset") + Layout.fillWidth: true + visible: polarCheckbox.checked + } + SecondColumnLayout { + visible: polarCheckbox.checked + SpinBox { + backendValue: backendValues.radialLabelOffset + minimumValue: 0.0 + maximumValue: 1.0 + stepSize: 0.01 + decimals: 1 + Layout.fillWidth: true + } + } + Label { + text: qsTr("horizontalAspectRatio") + toolTip: qsTr("Horizontal Aspect Ratio") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.horizontalAspectRatio + minimumValue: 0.0 + maximumValue: 100.0 + stepSize: 0.01 + decimals: 2 + Layout.fillWidth: true + } + } + Label { + text: qsTr("margin") + toolTip: qsTr("Graph Margin") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.margin + minimumValue: -1.0 + maximumValue: 100.0 + stepSize: 0.1 + decimals: 1 + Layout.fillWidth: true + } + } } } } diff --git a/src/datavisualizationqml2/designer/Surface3DSpecifics.qml b/src/datavisualizationqml2/designer/Surface3DSpecifics.qml index 65a65d37..a834f677 100644 --- a/src/datavisualizationqml2/designer/Surface3DSpecifics.qml +++ b/src/datavisualizationqml2/designer/Surface3DSpecifics.qml @@ -241,6 +241,76 @@ Column { Layout.fillWidth: true } } + Label { + text: qsTr("flipHorizontalGrid") + toolTip: qsTr("Flip Horizontal Grid") + Layout.fillWidth: true + } + SecondColumnLayout { + CheckBox { + backendValue: backendValues.flipHorizontalGrid + Layout.fillWidth: true + } + } + Label { + text: qsTr("polar") + toolTip: qsTr("Use Polar Coordinates") + Layout.fillWidth: true + } + SecondColumnLayout { + CheckBox { + id: polarCheckbox + backendValue: backendValues.polar + Layout.fillWidth: true + } + } + Label { + text: qsTr("radialLabelOffset") + toolTip: qsTr("Radial Label Offset") + Layout.fillWidth: true + visible: polarCheckbox.checked + } + SecondColumnLayout { + visible: polarCheckbox.checked + SpinBox { + backendValue: backendValues.radialLabelOffset + minimumValue: 0.0 + maximumValue: 1.0 + stepSize: 0.01 + decimals: 1 + Layout.fillWidth: true + } + } + Label { + text: qsTr("horizontalAspectRatio") + toolTip: qsTr("Horizontal Aspect Ratio") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.horizontalAspectRatio + minimumValue: 0.0 + maximumValue: 100.0 + stepSize: 0.01 + decimals: 2 + Layout.fillWidth: true + } + } + Label { + text: qsTr("margin") + toolTip: qsTr("Graph Margin") + Layout.fillWidth: true + } + SecondColumnLayout { + SpinBox { + backendValue: backendValues.margin + minimumValue: -1.0 + maximumValue: 100.0 + stepSize: 0.1 + decimals: 1 + Layout.fillWidth: true + } + } // Kept for debugging Label { } diff --git a/src/datavisualizationqml2/designer/default/Bars3D.qml b/src/datavisualizationqml2/designer/default/Bars3D.qml index 10fefe53..c85c0e94 100644 --- a/src/datavisualizationqml2/designer/default/Bars3D.qml +++ b/src/datavisualizationqml2/designer/default/Bars3D.qml @@ -17,7 +17,7 @@ ****************************************************************************/ import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Bars3D { width: 300 diff --git a/src/datavisualizationqml2/designer/default/Scatter3D.qml b/src/datavisualizationqml2/designer/default/Scatter3D.qml index b08d4e24..0bd6cac2 100644 --- a/src/datavisualizationqml2/designer/default/Scatter3D.qml +++ b/src/datavisualizationqml2/designer/default/Scatter3D.qml @@ -17,7 +17,7 @@ ****************************************************************************/ import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Scatter3D { width: 300 diff --git a/src/datavisualizationqml2/designer/default/Surface3D.qml b/src/datavisualizationqml2/designer/default/Surface3D.qml index 77ee476e..f9de62a1 100644 --- a/src/datavisualizationqml2/designer/default/Surface3D.qml +++ b/src/datavisualizationqml2/designer/default/Surface3D.qml @@ -17,7 +17,7 @@ ****************************************************************************/ import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Surface3D { width: 300 diff --git a/src/datavisualizationqml2/plugins.qmltypes b/src/datavisualizationqml2/plugins.qmltypes index 6a580536..956100ed 100644 --- a/src/datavisualizationqml2/plugins.qmltypes +++ b/src/datavisualizationqml2/plugins.qmltypes @@ -4,7 +4,7 @@ import QtQuick.tooling 1.1 // It is used for QML tooling purposes only. // // This file was auto-generated by: -// 'qmlplugindump -nonrelocatable QtDataVisualization 1.1' +// 'qmlplugindump.exe -nonrelocatable QtDataVisualization 1.2' Module { Component { @@ -13,10 +13,11 @@ Module { prototype: "QQuickItem" exports: [ "QtDataVisualization/AbstractGraph3D 1.0", - "QtDataVisualization/AbstractGraph3D 1.1" + "QtDataVisualization/AbstractGraph3D 1.1", + "QtDataVisualization/AbstractGraph3D 1.2" ] isCreatable: false - exportMetaObjectRevisions: [0, 1] + exportMetaObjectRevisions: [0, 1, 2] Enum { name: "SelectionFlag" values: { @@ -113,6 +114,14 @@ Module { Property { name: "selectedElement"; revision: 1; type: "ElementType"; isReadonly: true } Property { name: "aspectRatio"; revision: 1; type: "double" } Property { name: "optimizationHints"; revision: 1; type: "OptimizationHints" } + Property { name: "polar"; revision: 2; type: "bool" } + Property { name: "radialLabelOffset"; revision: 2; type: "float" } + Property { name: "horizontalAspectRatio"; revision: 2; type: "double" } + Property { name: "reflection"; revision: 2; type: "bool" } + Property { name: "reflectivity"; revision: 2; type: "double" } + Property { name: "locale"; revision: 2; type: "QLocale" } + Property { name: "queriedGraphPosition"; revision: 2; type: "QVector3D"; isReadonly: true } + Property { name: "margin"; revision: 2; type: "double" } Signal { name: "selectionModeChanged" Parameter { name: "mode"; type: "AbstractDeclarative::SelectionFlags" } @@ -175,6 +184,46 @@ Module { revision: 1 Parameter { name: "hints"; type: "AbstractDeclarative::OptimizationHints" } } + Signal { + name: "polarChanged" + revision: 2 + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "radialLabelOffsetChanged" + revision: 2 + Parameter { name: "offset"; type: "float" } + } + Signal { + name: "horizontalAspectRatioChanged" + revision: 2 + Parameter { name: "ratio"; type: "double" } + } + Signal { + name: "reflectionChanged" + revision: 2 + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "reflectivityChanged" + revision: 2 + Parameter { name: "reflectivity"; type: "double" } + } + Signal { + name: "localeChanged" + revision: 2 + Parameter { name: "locale"; type: "QLocale" } + } + Signal { + name: "queriedGraphPositionChanged" + revision: 2 + Parameter { name: "data"; type: "QVector3D" } + } + Signal { + name: "marginChanged" + revision: 2 + Parameter { name: "margin"; type: "double" } + } Method { name: "handleAxisXChanged" Parameter { name: "axis"; type: "QAbstract3DAxis"; isPointer: true } @@ -191,6 +240,7 @@ Module { name: "windowDestroyed" Parameter { name: "obj"; type: "QObject"; isPointer: true } } + Method { name: "destroyContext" } Method { name: "clearSelection" } Method { name: "addCustomItem" @@ -247,9 +297,12 @@ Module { Component { name: "QtDataVisualization::Declarative3DScene" prototype: "QtDataVisualization::Q3DScene" - exports: ["QtDataVisualization/Scene3D 1.0"] + exports: [ + "QtDataVisualization/Scene3D 1.0", + "QtDataVisualization/Scene3D 1.2" + ] isCreatable: false - exportMetaObjectRevisions: [0] + exportMetaObjectRevisions: [0, 1] Property { name: "selectionQueryPosition"; type: "QPointF" } Property { name: "invalidSelectionPoint"; type: "QPoint"; isReadonly: true } Signal { @@ -293,18 +346,22 @@ Module { name: "QtDataVisualization::DeclarativeBars" defaultProperty: "seriesList" prototype: "QtDataVisualization::AbstractDeclarative" - exports: ["QtDataVisualization/Bars3D 1.0"] - exportMetaObjectRevisions: [0] + exports: [ + "QtDataVisualization/Bars3D 1.0", + "QtDataVisualization/Bars3D 1.2" + ] + exportMetaObjectRevisions: [0, 1] Property { name: "rowAxis"; type: "QCategory3DAxis"; isPointer: true } Property { name: "valueAxis"; type: "QValue3DAxis"; isPointer: true } Property { name: "columnAxis"; type: "QCategory3DAxis"; isPointer: true } Property { name: "multiSeriesUniform"; type: "bool" } - Property { name: "barThickness"; type: "double" } + Property { name: "barThickness"; type: "float" } Property { name: "barSpacing"; type: "QSizeF" } Property { name: "barSpacingRelative"; type: "bool" } Property { name: "seriesList"; type: "QBar3DSeries"; isList: true; isReadonly: true } Property { name: "selectedSeries"; type: "QBar3DSeries"; isReadonly: true; isPointer: true } Property { name: "primarySeries"; type: "QBar3DSeries"; isPointer: true } + Property { name: "floorLevel"; revision: 1; type: "float" } Signal { name: "rowAxisChanged" Parameter { name: "axis"; type: "QCategory3DAxis"; isPointer: true } @@ -323,7 +380,7 @@ Module { } Signal { name: "barThicknessChanged" - Parameter { name: "thicknessRatio"; type: "double" } + Parameter { name: "thicknessRatio"; type: "float" } } Signal { name: "barSpacingChanged" @@ -345,6 +402,11 @@ Module { name: "selectedSeriesChanged" Parameter { name: "series"; type: "QBar3DSeries"; isPointer: true } } + Signal { + name: "floorLevelChanged" + revision: 1 + Parameter { name: "level"; type: "float" } + } Method { name: "handleAxisXChanged" Parameter { name: "axis"; type: "QAbstract3DAxis"; isPointer: true } @@ -461,13 +523,17 @@ Module { name: "QtDataVisualization::DeclarativeSurface" defaultProperty: "seriesList" prototype: "QtDataVisualization::AbstractDeclarative" - exports: ["QtDataVisualization/Surface3D 1.0"] - exportMetaObjectRevisions: [0] + exports: [ + "QtDataVisualization/Surface3D 1.0", + "QtDataVisualization/Surface3D 1.2" + ] + exportMetaObjectRevisions: [0, 1] Property { name: "axisX"; type: "QValue3DAxis"; isPointer: true } Property { name: "axisY"; type: "QValue3DAxis"; isPointer: true } Property { name: "axisZ"; type: "QValue3DAxis"; isPointer: true } Property { name: "selectedSeries"; type: "QSurface3DSeries"; isReadonly: true; isPointer: true } Property { name: "seriesList"; type: "QSurface3DSeries"; isList: true; isReadonly: true } + Property { name: "flipHorizontalGrid"; revision: 1; type: "bool" } Signal { name: "axisXChanged" Parameter { name: "axis"; type: "QValue3DAxis"; isPointer: true } @@ -484,6 +550,11 @@ Module { name: "selectedSeriesChanged" Parameter { name: "series"; type: "QSurface3DSeries"; isPointer: true } } + Signal { + name: "flipHorizontalGridChanged" + revision: 1 + Parameter { name: "flip"; type: "bool" } + } Method { name: "handleAxisXChanged" Parameter { name: "axis"; type: "QAbstract3DAxis"; isPointer: true } @@ -560,8 +631,11 @@ Module { Component { name: "QtDataVisualization::Q3DCamera" prototype: "QtDataVisualization::Q3DObject" - exports: ["QtDataVisualization/Camera3D 1.0"] - exportMetaObjectRevisions: [0] + exports: [ + "QtDataVisualization/Camera3D 1.0", + "QtDataVisualization/Camera3D 1.2" + ] + exportMetaObjectRevisions: [0, 1] Enum { name: "CameraPreset" values: { @@ -592,23 +666,26 @@ Module { "CameraPresetDirectlyBelow": 23 } } - Property { name: "xRotation"; type: "double" } - Property { name: "yRotation"; type: "double" } - Property { name: "zoomLevel"; type: "double" } + Property { name: "xRotation"; type: "float" } + Property { name: "yRotation"; type: "float" } + Property { name: "zoomLevel"; type: "float" } Property { name: "cameraPreset"; type: "CameraPreset" } Property { name: "wrapXRotation"; type: "bool" } Property { name: "wrapYRotation"; type: "bool" } + Property { name: "target"; revision: 1; type: "QVector3D" } + Property { name: "minZoomLevel"; revision: 1; type: "float" } + Property { name: "maxZoomLevel"; revision: 1; type: "float" } Signal { name: "xRotationChanged" - Parameter { name: "rotation"; type: "double" } + Parameter { name: "rotation"; type: "float" } } Signal { name: "yRotationChanged" - Parameter { name: "rotation"; type: "double" } + Parameter { name: "rotation"; type: "float" } } Signal { name: "zoomLevelChanged" - Parameter { name: "zoomLevel"; type: "double" } + Parameter { name: "zoomLevel"; type: "float" } } Signal { name: "cameraPresetChanged" @@ -622,10 +699,47 @@ Module { name: "wrapYRotationChanged" Parameter { name: "isEnabled"; type: "bool" } } + Signal { + name: "targetChanged" + revision: 1 + Parameter { name: "target"; type: "QVector3D" } + } + Signal { + name: "minZoomLevelChanged" + revision: 1 + Parameter { name: "zoomLevel"; type: "float" } + } + Signal { + name: "maxZoomLevelChanged" + revision: 1 + Parameter { name: "zoomLevel"; type: "float" } + } } Component { name: "QtDataVisualization::Q3DInputHandler" prototype: "QtDataVisualization::QAbstract3DInputHandler" + exports: ["QtDataVisualization/InputHandler3D 1.2"] + exportMetaObjectRevisions: [0] + Property { name: "rotationEnabled"; type: "bool" } + Property { name: "zoomEnabled"; type: "bool" } + Property { name: "selectionEnabled"; type: "bool" } + Property { name: "zoomAtTargetEnabled"; type: "bool" } + Signal { + name: "rotationEnabledChanged" + Parameter { name: "enable"; type: "bool" } + } + Signal { + name: "zoomEnabledChanged" + Parameter { name: "enable"; type: "bool" } + } + Signal { + name: "selectionEnabledChanged" + Parameter { name: "enable"; type: "bool" } + } + Signal { + name: "zoomAtTargetEnabledChanged" + Parameter { name: "enable"; type: "bool" } + } } Component { name: "QtDataVisualization::Q3DLight" @@ -654,7 +768,8 @@ Module { Property { name: "slicingActive"; type: "bool" } Property { name: "activeCamera"; type: "Q3DCamera"; isPointer: true } Property { name: "activeLight"; type: "Q3DLight"; isPointer: true } - Property { name: "devicePixelRatio"; type: "double" } + Property { name: "devicePixelRatio"; type: "float" } + Property { name: "graphPositionQuery"; revision: 1; type: "QPoint" } Signal { name: "viewportChanged" Parameter { name: "viewport"; type: "QRect" } @@ -685,12 +800,17 @@ Module { } Signal { name: "devicePixelRatioChanged" - Parameter { name: "pixelRatio"; type: "double" } + Parameter { name: "pixelRatio"; type: "float" } } Signal { name: "selectionQueryPositionChanged" Parameter { name: "position"; type: "QPoint" } } + Signal { + name: "graphPositionQueryChanged" + revision: 1 + Parameter { name: "position"; type: "QPoint" } + } } Component { name: "QtDataVisualization::Q3DTheme" @@ -733,9 +853,9 @@ Module { Property { name: "baseGradients"; type: "QList<QLinearGradient>" } Property { name: "singleHighlightGradient"; type: "QLinearGradient" } Property { name: "multiHighlightGradient"; type: "QLinearGradient" } - Property { name: "lightStrength"; type: "double" } - Property { name: "ambientLightStrength"; type: "double" } - Property { name: "highlightLightStrength"; type: "double" } + Property { name: "lightStrength"; type: "float" } + Property { name: "ambientLightStrength"; type: "float" } + Property { name: "highlightLightStrength"; type: "float" } Property { name: "labelBorderEnabled"; type: "bool" } Property { name: "font"; type: "QFont" } Property { name: "backgroundEnabled"; type: "bool" } @@ -796,15 +916,15 @@ Module { } Signal { name: "lightStrengthChanged" - Parameter { name: "strength"; type: "double" } + Parameter { name: "strength"; type: "float" } } Signal { name: "ambientLightStrengthChanged" - Parameter { name: "strength"; type: "double" } + Parameter { name: "strength"; type: "float" } } Signal { name: "highlightLightStrengthChanged" - Parameter { name: "strength"; type: "double" } + Parameter { name: "strength"; type: "float" } } Signal { name: "labelBorderEnabledChanged" @@ -861,10 +981,10 @@ Module { Property { name: "labels"; type: "QStringList" } Property { name: "orientation"; type: "AxisOrientation"; isReadonly: true } Property { name: "type"; type: "AxisType"; isReadonly: true } - Property { name: "min"; type: "double" } - Property { name: "max"; type: "double" } + Property { name: "min"; type: "float" } + Property { name: "max"; type: "float" } Property { name: "autoAdjustRange"; type: "bool" } - Property { name: "labelAutoRotation"; revision: 1; type: "double" } + Property { name: "labelAutoRotation"; revision: 1; type: "float" } Property { name: "titleVisible"; revision: 1; type: "bool" } Property { name: "titleFixed"; revision: 1; type: "bool" } Signal { @@ -877,16 +997,16 @@ Module { } Signal { name: "minChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } Signal { name: "maxChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } Signal { name: "rangeChanged" - Parameter { name: "min"; type: "double" } - Parameter { name: "max"; type: "double" } + Parameter { name: "min"; type: "float" } + Parameter { name: "max"; type: "float" } } Signal { name: "autoAdjustRangeChanged" @@ -895,7 +1015,7 @@ Module { Signal { name: "labelAutoRotationChanged" revision: 1 - Parameter { name: "angle"; type: "double" } + Parameter { name: "angle"; type: "float" } } Signal { name: "titleVisibilityChanged" @@ -1059,7 +1179,7 @@ Module { Method { name: "setMeshAxisAndAngle" Parameter { name: "axis"; type: "QVector3D" } - Parameter { name: "angle"; type: "double" } + Parameter { name: "angle"; type: "float" } } } Component { @@ -1087,7 +1207,7 @@ Module { exportMetaObjectRevisions: [0] Property { name: "dataProxy"; type: "QBarDataProxy"; isPointer: true } Property { name: "selectedBar"; type: "QPoint" } - Property { name: "meshAngle"; type: "double" } + Property { name: "meshAngle"; type: "float" } Signal { name: "dataProxyChanged" Parameter { name: "proxy"; type: "QBarDataProxy"; isPointer: true } @@ -1098,7 +1218,7 @@ Module { } Signal { name: "meshAngleChanged" - Parameter { name: "angle"; type: "double" } + Parameter { name: "angle"; type: "float" } } } Component { @@ -1156,8 +1276,11 @@ Module { Component { name: "QtDataVisualization::QCustom3DItem" prototype: "QObject" - exports: ["QtDataVisualization/Custom3DItem 1.1"] - exportMetaObjectRevisions: [0] + exports: [ + "QtDataVisualization/Custom3DItem 1.1", + "QtDataVisualization/Custom3DItem 1.2" + ] + exportMetaObjectRevisions: [0, 1] Property { name: "meshFile"; type: "string" } Property { name: "textureFile"; type: "string" } Property { name: "position"; type: "QVector3D" } @@ -1166,6 +1289,7 @@ Module { Property { name: "rotation"; type: "QQuaternion" } Property { name: "visible"; type: "bool" } Property { name: "shadowCasting"; type: "bool" } + Property { name: "scalingAbsolute"; revision: 1; type: "bool" } Signal { name: "meshFileChanged" Parameter { name: "meshFile"; type: "string" } @@ -1198,10 +1322,15 @@ Module { name: "shadowCastingChanged" Parameter { name: "shadowCasting"; type: "bool" } } + Signal { + name: "scalingAbsoluteChanged" + revision: 1 + Parameter { name: "scalingAbsolute"; type: "bool" } + } Method { name: "setRotationAxisAndAngle" Parameter { name: "axis"; type: "QVector3D" } - Parameter { name: "angle"; type: "double" } + Parameter { name: "angle"; type: "float" } } } Component { @@ -1246,16 +1375,107 @@ Module { } } Component { + name: "QtDataVisualization::QCustom3DVolume" + prototype: "QtDataVisualization::QCustom3DItem" + exports: ["QtDataVisualization/Custom3DVolume 1.2"] + exportMetaObjectRevisions: [0] + Property { name: "textureWidth"; type: "int" } + Property { name: "textureHeight"; type: "int" } + Property { name: "textureDepth"; type: "int" } + Property { name: "sliceIndexX"; type: "int" } + Property { name: "sliceIndexY"; type: "int" } + Property { name: "sliceIndexZ"; type: "int" } + Property { name: "colorTable"; type: "QVector<QRgb>" } + Property { name: "textureData"; type: "QVector<uchar>"; isPointer: true } + Property { name: "alphaMultiplier"; type: "float" } + Property { name: "preserveOpacity"; type: "bool" } + Property { name: "useHighDefShader"; type: "bool" } + Property { name: "drawSlices"; type: "bool" } + Property { name: "drawSliceFrames"; type: "bool" } + Property { name: "sliceFrameColor"; type: "QColor" } + Property { name: "sliceFrameWidths"; type: "QVector3D" } + Property { name: "sliceFrameGaps"; type: "QVector3D" } + Property { name: "sliceFrameThicknesses"; type: "QVector3D" } + Signal { + name: "textureWidthChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "textureHeightChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "textureDepthChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "sliceIndexXChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "sliceIndexYChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "sliceIndexZChanged" + Parameter { name: "value"; type: "int" } + } + Signal { + name: "textureDataChanged" + Parameter { name: "data"; type: "QVector<uchar>"; isPointer: true } + } + Signal { + name: "textureFormatChanged" + Parameter { name: "format"; type: "QImage::Format" } + } + Signal { + name: "alphaMultiplierChanged" + Parameter { name: "mult"; type: "float" } + } + Signal { + name: "preserveOpacityChanged" + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "useHighDefShaderChanged" + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "drawSlicesChanged" + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "drawSliceFramesChanged" + Parameter { name: "enabled"; type: "bool" } + } + Signal { + name: "sliceFrameColorChanged" + Parameter { name: "color"; type: "QColor" } + } + Signal { + name: "sliceFrameWidthsChanged" + Parameter { name: "values"; type: "QVector3D" } + } + Signal { + name: "sliceFrameGapsChanged" + Parameter { name: "values"; type: "QVector3D" } + } + Signal { + name: "sliceFrameThicknessesChanged" + Parameter { name: "values"; type: "QVector3D" } + } + } + Component { name: "QtDataVisualization::QHeightMapSurfaceDataProxy" prototype: "QtDataVisualization::QSurfaceDataProxy" exports: ["QtDataVisualization/HeightMapSurfaceDataProxy 1.0"] exportMetaObjectRevisions: [0] Property { name: "heightMap"; type: "QImage" } Property { name: "heightMapFile"; type: "string" } - Property { name: "minXValue"; type: "double" } - Property { name: "maxXValue"; type: "double" } - Property { name: "minZValue"; type: "double" } - Property { name: "maxZValue"; type: "double" } + Property { name: "minXValue"; type: "float" } + Property { name: "maxXValue"; type: "float" } + Property { name: "minZValue"; type: "float" } + Property { name: "maxZValue"; type: "float" } Signal { name: "heightMapChanged" Parameter { name: "image"; type: "QImage" } @@ -1266,19 +1486,19 @@ Module { } Signal { name: "minXValueChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } Signal { name: "maxXValueChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } Signal { name: "minZValueChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } Signal { name: "maxZValueChanged" - Parameter { name: "value"; type: "double" } + Parameter { name: "value"; type: "float" } } } Component { @@ -1657,7 +1877,7 @@ Module { exportMetaObjectRevisions: [0] Property { name: "dataProxy"; type: "QScatterDataProxy"; isPointer: true } Property { name: "selectedItem"; type: "int" } - Property { name: "itemSize"; type: "double" } + Property { name: "itemSize"; type: "float" } Signal { name: "dataProxyChanged" Parameter { name: "proxy"; type: "QScatterDataProxy"; isPointer: true } @@ -1668,7 +1888,7 @@ Module { } Signal { name: "itemSizeChanged" - Parameter { name: "size"; type: "double" } + Parameter { name: "size"; type: "float" } } } Component { @@ -1736,6 +1956,8 @@ Module { Property { name: "flatShadingEnabled"; type: "bool" } Property { name: "flatShadingSupported"; type: "bool"; isReadonly: true } Property { name: "drawMode"; type: "DrawFlags" } + Property { name: "texture"; type: "QImage" } + Property { name: "textureFile"; type: "string" } Signal { name: "dataProxyChanged" Parameter { name: "proxy"; type: "QSurfaceDataProxy"; isPointer: true } @@ -1756,6 +1978,14 @@ Module { name: "drawModeChanged" Parameter { name: "mode"; type: "QSurface3DSeries::DrawFlags" } } + Signal { + name: "textureChanged" + Parameter { name: "image"; type: "QImage" } + } + Signal { + name: "textureFileChanged" + Parameter { name: "filename"; type: "string" } + } } Component { name: "QtDataVisualization::QSurfaceDataProxy" @@ -1808,6 +2038,8 @@ Module { Component { name: "QtDataVisualization::QTouch3DInputHandler" prototype: "QtDataVisualization::Q3DInputHandler" + exports: ["QtDataVisualization/TouchInputHandler3D 1.2"] + exportMetaObjectRevisions: [0] } Component { name: "QtDataVisualization::QValue3DAxis" diff --git a/src/src.pro b/src/src.pro index 33e3c009..3547745d 100644 --- a/src/src.pro +++ b/src/src.pro @@ -1,4 +1,5 @@ TEMPLATE = subdirs CONFIG += ordered -SUBDIRS += datavisualization \ - datavisualizationqml2 +SUBDIRS += datavisualization + +qtHaveModule(quick): SUBDIRS += datavisualizationqml2 diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro new file mode 100644 index 00000000..df2943a4 --- /dev/null +++ b/tests/auto/auto.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +SUBDIRS += cpptest + +qtHaveModule(quick): SUBDIRS += qmltest + +installed_cmake.depends = cmake diff --git a/tests/auto/cpptest/cpptest.pro b/tests/auto/cpptest/cpptest.pro new file mode 100644 index 00000000..abd8f38e --- /dev/null +++ b/tests/auto/cpptest/cpptest.pro @@ -0,0 +1,26 @@ +TEMPLATE = subdirs +SUBDIRS = q3dbars \ + q3dbars-proxy \ + q3dbars-modelproxy \ + q3dbars-series \ + q3dscatter \ + q3dscatter-proxy \ + q3dscatter-modelproxy \ + q3dscatter-series \ + q3dsurface \ + q3dsurface-proxy \ + q3dsurface-modelproxy \ + q3dsurface-heightproxy \ + q3dsurface-series \ + q3daxis-category \ + q3daxis-logvalue \ + q3daxis-value \ + q3dscene \ + q3dscene-camera \ + q3dscene-light \ + q3dtheme \ + q3dinput \ + q3dinput-touch \ + q3dcustom \ + q3dcustom-label \ + q3dcustom-volume diff --git a/tests/auto/cpptest/q3daxis-category/q3daxis-category.pro b/tests/auto/cpptest/q3daxis-category/q3daxis-category.pro new file mode 100644 index 00000000..74415397 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-category/q3daxis-category.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_axis.cpp diff --git a/tests/auto/cpptest/q3daxis-category/tst_axis.cpp b/tests/auto/cpptest/q3daxis-category/tst_axis.cpp new file mode 100644 index 00000000..800a0953 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-category/tst_axis.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QCategory3DAxis> + +using namespace QtDataVisualization; + +class tst_axis: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QCategory3DAxis *m_axis; +}; + +void tst_axis::initTestCase() +{ +} + +void tst_axis::cleanupTestCase() +{ +} + +void tst_axis::init() +{ + m_axis = new QCategory3DAxis(); +} + +void tst_axis::cleanup() +{ + delete m_axis; +} + +void tst_axis::construct() +{ + QCategory3DAxis *axis = new QCategory3DAxis(); + QVERIFY(axis); + delete axis; +} + +void tst_axis::initialProperties() +{ + QVERIFY(m_axis); + + QCOMPARE(m_axis->labels().length(), 0); + + // Common (from QAbstract3DAxis) + QCOMPARE(m_axis->isAutoAdjustRange(), true); + QCOMPARE(m_axis->labelAutoRotation(), 0.0f); + QCOMPARE(m_axis->max(), 10.0f); + QCOMPARE(m_axis->min(), 0.0f); + QCOMPARE(m_axis->orientation(), QAbstract3DAxis::AxisOrientationNone); + QCOMPARE(m_axis->title(), QString("")); + QCOMPARE(m_axis->isTitleFixed(), true); + QCOMPARE(m_axis->isTitleVisible(), false); + QCOMPARE(m_axis->type(), QAbstract3DAxis::AxisTypeCategory); +} + +void tst_axis::initializeProperties() +{ + QVERIFY(m_axis); + + m_axis->setLabels(QStringList() << "first" << "second"); + + QCOMPARE(m_axis->labels().length(), 2); + QCOMPARE(m_axis->labels().at(1), QString("second")); + + // Common (from QAbstract3DAxis) + m_axis->setAutoAdjustRange(false); + m_axis->setLabelAutoRotation(15.0f); + m_axis->setMax(25.0f); + m_axis->setMin(5.0f); + m_axis->setTitle("title"); + m_axis->setTitleFixed(false); + m_axis->setTitleVisible(true); + + QCOMPARE(m_axis->isAutoAdjustRange(), false); + QCOMPARE(m_axis->labelAutoRotation(), 15.0f); + QCOMPARE(m_axis->max(), 25.0f); + QCOMPARE(m_axis->min(), 5.0f); + QCOMPARE(m_axis->title(), QString("title")); + QCOMPARE(m_axis->isTitleFixed(), false); + QCOMPARE(m_axis->isTitleVisible(), true); +} + +void tst_axis::invalidProperties() +{ + m_axis->setLabelAutoRotation(-15.0f); + QCOMPARE(m_axis->labelAutoRotation(), 0.0f); + + m_axis->setLabelAutoRotation(100.0f); + QCOMPARE(m_axis->labelAutoRotation(), 90.0f); + + m_axis->setMax(-10.0f); + QCOMPARE(m_axis->max(), 0.0f); + QCOMPARE(m_axis->min(), 0.0f); + + m_axis->setMin(10.0f); + QCOMPARE(m_axis->max(), 11.0f); + QCOMPARE(m_axis->min(), 10.0f); +} + +QTEST_MAIN(tst_axis) +#include "tst_axis.moc" diff --git a/tests/auto/cpptest/q3daxis-logvalue/q3daxis-logvalue.pro b/tests/auto/cpptest/q3daxis-logvalue/q3daxis-logvalue.pro new file mode 100644 index 00000000..74415397 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-logvalue/q3daxis-logvalue.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_axis.cpp diff --git a/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp b/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp new file mode 100644 index 00000000..adbedb24 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-logvalue/tst_axis.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QLogValue3DAxisFormatter> + +using namespace QtDataVisualization; + +class tst_axis: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QLogValue3DAxisFormatter *m_formatter; +}; + +void tst_axis::initTestCase() +{ +} + +void tst_axis::cleanupTestCase() +{ +} + +void tst_axis::init() +{ + m_formatter = new QLogValue3DAxisFormatter(); +} + +void tst_axis::cleanup() +{ + delete m_formatter; +} + +void tst_axis::construct() +{ + QLogValue3DAxisFormatter *formatter = new QLogValue3DAxisFormatter(); + QVERIFY(formatter); + delete formatter; +} + +void tst_axis::initialProperties() +{ + QVERIFY(m_formatter); + + QCOMPARE(m_formatter->autoSubGrid(), true); + QCOMPARE(m_formatter->base(), 10.0); + QCOMPARE(m_formatter->showEdgeLabels(), true); +} + +void tst_axis::initializeProperties() +{ + QVERIFY(m_formatter); + + m_formatter->setAutoSubGrid(false); + m_formatter->setBase(0.1); + m_formatter->setShowEdgeLabels(false); + + QCOMPARE(m_formatter->autoSubGrid(), false); + QCOMPARE(m_formatter->base(), 0.1); + QCOMPARE(m_formatter->showEdgeLabels(), false); +} + +void tst_axis::invalidProperties() +{ + m_formatter->setBase(-1.0); + QCOMPARE(m_formatter->base(), 10.0); + + m_formatter->setBase(1.0); + QCOMPARE(m_formatter->base(), 10.0); +} + +QTEST_MAIN(tst_axis) +#include "tst_axis.moc" diff --git a/tests/auto/cpptest/q3daxis-value/q3daxis-value.pro b/tests/auto/cpptest/q3daxis-value/q3daxis-value.pro new file mode 100644 index 00000000..74415397 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-value/q3daxis-value.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_axis.cpp diff --git a/tests/auto/cpptest/q3daxis-value/tst_axis.cpp b/tests/auto/cpptest/q3daxis-value/tst_axis.cpp new file mode 100644 index 00000000..92029f13 --- /dev/null +++ b/tests/auto/cpptest/q3daxis-value/tst_axis.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QValue3DAxis> + +using namespace QtDataVisualization; + +class tst_axis: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QValue3DAxis *m_axis; +}; + +void tst_axis::initTestCase() +{ +} + +void tst_axis::cleanupTestCase() +{ +} + +void tst_axis::init() +{ + m_axis = new QValue3DAxis(); +} + +void tst_axis::cleanup() +{ + delete m_axis; +} + +void tst_axis::construct() +{ + QValue3DAxis *axis = new QValue3DAxis(); + QVERIFY(axis); + delete axis; +} + +void tst_axis::initialProperties() +{ + QVERIFY(m_axis); + + QCOMPARE(m_axis->labelFormat(), QString("%.2f")); + QCOMPARE(m_axis->reversed(), false); + QCOMPARE(m_axis->segmentCount(), 5); + QCOMPARE(m_axis->subSegmentCount(), 1); + + // Common (from QAbstract3DAxis) + QCOMPARE(m_axis->isAutoAdjustRange(), true); + QCOMPARE(m_axis->labelAutoRotation(), 0.0f); + QCOMPARE(m_axis->labels().length(), 6); + QCOMPARE(m_axis->labels().at(0), QString("0.00")); + QCOMPARE(m_axis->labels().at(1), QString("2.00")); + QCOMPARE(m_axis->labels().at(2), QString("4.00")); + QCOMPARE(m_axis->labels().at(3), QString("6.00")); + QCOMPARE(m_axis->labels().at(4), QString("8.00")); + QCOMPARE(m_axis->labels().at(5), QString("10.00")); + QCOMPARE(m_axis->max(), 10.0f); + QCOMPARE(m_axis->min(), 0.0f); + QCOMPARE(m_axis->orientation(), QAbstract3DAxis::AxisOrientationNone); + QCOMPARE(m_axis->title(), QString("")); + QCOMPARE(m_axis->isTitleFixed(), true); + QCOMPARE(m_axis->isTitleVisible(), false); + QCOMPARE(m_axis->type(), QAbstract3DAxis::AxisTypeValue); +} + +void tst_axis::initializeProperties() +{ + QVERIFY(m_axis); + + m_axis->setLabelFormat("%.0fm"); + m_axis->setReversed(true); + m_axis->setSegmentCount(2); + m_axis->setSubSegmentCount(5); + + QCOMPARE(m_axis->labelFormat(), QString("%.0fm")); + QCOMPARE(m_axis->reversed(), true); + QCOMPARE(m_axis->segmentCount(), 2); + QCOMPARE(m_axis->subSegmentCount(), 5); + + // Common (from QAbstract3DAxis) + m_axis->setAutoAdjustRange(false); + m_axis->setLabelAutoRotation(15.0f); + m_axis->setMax(25.0f); + m_axis->setMin(5.0f); + m_axis->setTitle("title"); + m_axis->setTitleFixed(false); + m_axis->setTitleVisible(true); + + QCOMPARE(m_axis->isAutoAdjustRange(), false); + QCOMPARE(m_axis->labelAutoRotation(), 15.0f); + QCOMPARE(m_axis->labels().length(), 3); + QCOMPARE(m_axis->labels().at(0), QString("5m")); + QCOMPARE(m_axis->labels().at(1), QString("15m")); + QCOMPARE(m_axis->labels().at(2), QString("25m")); + QCOMPARE(m_axis->max(), 25.0f); + QCOMPARE(m_axis->min(), 5.0f); + QCOMPARE(m_axis->title(), QString("title")); + QCOMPARE(m_axis->isTitleFixed(), false); + QCOMPARE(m_axis->isTitleVisible(), true); +} + +void tst_axis::invalidProperties() +{ + m_axis->setSegmentCount(-1); + QCOMPARE(m_axis->segmentCount(), 1); + + m_axis->setSubSegmentCount(-1); + QCOMPARE(m_axis->subSegmentCount(), 1); + + m_axis->setLabelAutoRotation(-15.0f); + QCOMPARE(m_axis->labelAutoRotation(), 0.0f); + + m_axis->setLabelAutoRotation(100.0f); + QCOMPARE(m_axis->labelAutoRotation(), 90.0f); + + m_axis->setMax(-10.0f); + QCOMPARE(m_axis->max(), -10.0f); + QCOMPARE(m_axis->min(), -11.0f); + + m_axis->setMin(10.0f); + QCOMPARE(m_axis->max(), 11.0f); + QCOMPARE(m_axis->min(), 10.0f); +} + +QTEST_MAIN(tst_axis) +#include "tst_axis.moc" diff --git a/tests/auto/cpptest/q3dbars-modelproxy/q3dbars-modelproxy.pro b/tests/auto/cpptest/q3dbars-modelproxy/q3dbars-modelproxy.pro new file mode 100644 index 00000000..c383ec25 --- /dev/null +++ b/tests/auto/cpptest/q3dbars-modelproxy/q3dbars-modelproxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization widgets + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp new file mode 100644 index 00000000..c65e151b --- /dev/null +++ b/tests/auto/cpptest/q3dbars-modelproxy/tst_proxy.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QItemModelBarDataProxy> +#include <QtDataVisualization/Q3DBars> +#include <QtWidgets/QTableWidget> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + + void multiMatch(); + +private: + QItemModelBarDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QItemModelBarDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QItemModelBarDataProxy *proxy = new QItemModelBarDataProxy(); + QVERIFY(proxy); + delete proxy; + + QTableWidget *table = new QTableWidget(); + + proxy = new QItemModelBarDataProxy(table->model()); + QVERIFY(proxy); + delete proxy; + + proxy = new QItemModelBarDataProxy(table->model(), "val"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("")); + QCOMPARE(proxy->columnRole(), QString("")); + QCOMPARE(proxy->valueRole(), QString("val")); + QCOMPARE(proxy->rotationRole(), QString("")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("col")); + QCOMPARE(proxy->valueRole(), QString("val")); + QCOMPARE(proxy->rotationRole(), QString("")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val", "rot"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("col")); + QCOMPARE(proxy->valueRole(), QString("val")); + QCOMPARE(proxy->rotationRole(), QString("rot")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val", + QStringList() << "rowCat", QStringList() << "colCat"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("col")); + QCOMPARE(proxy->valueRole(), QString("val")); + QCOMPARE(proxy->rotationRole(), QString("")); + QCOMPARE(proxy->rowCategories().length(), 1); + QCOMPARE(proxy->columnCategories().length(), 1); + delete proxy; + + proxy = new QItemModelBarDataProxy(table->model(), "row", "col", "val", "rot", + QStringList() << "rowCat", QStringList() << "colCat"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("col")); + QCOMPARE(proxy->valueRole(), QString("val")); + QCOMPARE(proxy->rotationRole(), QString("rot")); + QCOMPARE(proxy->rowCategories().length(), 1); + QCOMPARE(proxy->columnCategories().length(), 1); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->autoColumnCategories(), true); + QCOMPARE(m_proxy->autoRowCategories(), true); + QCOMPARE(m_proxy->columnCategories(), QStringList()); + QCOMPARE(m_proxy->columnRole(), QString()); + QCOMPARE(m_proxy->columnRolePattern(), QRegExp()); + QCOMPARE(m_proxy->columnRoleReplace(), QString()); + QVERIFY(!m_proxy->itemModel()); + QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelBarDataProxy::MMBLast); + QCOMPARE(m_proxy->rotationRole(), QString()); + QCOMPARE(m_proxy->rotationRolePattern(), QRegExp()); + QCOMPARE(m_proxy->rotationRoleReplace(), QString()); + QCOMPARE(m_proxy->rowCategories(), QStringList()); + QCOMPARE(m_proxy->rowRole(), QString()); + QCOMPARE(m_proxy->rowRolePattern(), QRegExp()); + QCOMPARE(m_proxy->rowRoleReplace(), QString()); + QCOMPARE(m_proxy->useModelCategories(), false); + QCOMPARE(m_proxy->valueRole(), QString()); + QCOMPARE(m_proxy->valueRolePattern(), QRegExp()); + QCOMPARE(m_proxy->valueRoleReplace(), QString()); + + QCOMPARE(m_proxy->columnLabels().count(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + QCOMPARE(m_proxy->rowLabels().count(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeBar); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + QTableWidget *table = new QTableWidget(); + + m_proxy->setAutoColumnCategories(false); + m_proxy->setAutoRowCategories(false); + m_proxy->setColumnCategories(QStringList() << "col1" << "col2"); + m_proxy->setColumnRole("column"); + m_proxy->setColumnRolePattern(QRegExp("/^.*-(\\d\\d)$/")); + m_proxy->setColumnRoleReplace("\\\\1"); + m_proxy->setItemModel(table->model()); + m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBAverage); + m_proxy->setRotationRole("rotation"); + m_proxy->setRotationRolePattern(QRegExp("/-/")); + m_proxy->setRotationRoleReplace("\\\\1"); + m_proxy->setRowCategories(QStringList() << "row1" << "row2"); + m_proxy->setRowRole("row"); + m_proxy->setRowRolePattern(QRegExp("/^(\\d\\d\\d\\d).*$/")); + m_proxy->setRowRoleReplace("\\\\1"); + m_proxy->setUseModelCategories(true); + m_proxy->setValueRole("value"); + m_proxy->setValueRolePattern(QRegExp("/-/")); + m_proxy->setValueRoleReplace("\\\\1"); + + QCOMPARE(m_proxy->autoColumnCategories(), false); + QCOMPARE(m_proxy->autoRowCategories(), false); + QCOMPARE(m_proxy->columnCategories().count(), 2); + QCOMPARE(m_proxy->columnRole(), QString("column")); + QCOMPARE(m_proxy->columnRolePattern(), QRegExp("/^.*-(\\d\\d)$/")); + QCOMPARE(m_proxy->columnRoleReplace(), QString("\\\\1")); + QVERIFY(m_proxy->itemModel()); + QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelBarDataProxy::MMBAverage); + QCOMPARE(m_proxy->rotationRole(), QString("rotation")); + QCOMPARE(m_proxy->rotationRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->rotationRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->rowCategories().count(), 2); + QCOMPARE(m_proxy->rowRole(), QString("row")); + QCOMPARE(m_proxy->rowRolePattern(), QRegExp("/^(\\d\\d\\d\\d).*$/")); + QCOMPARE(m_proxy->rowRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->useModelCategories(), true); + QCOMPARE(m_proxy->valueRole(), QString("value")); + QCOMPARE(m_proxy->valueRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->valueRoleReplace(), QString("\\\\1")); +} + +void tst_proxy::multiMatch() +{ + Q3DBars *graph = new Q3DBars(); + + QTableWidget *table = new QTableWidget(); + QStringList rows; + rows << "row 1" << "row 2" << "row 3"; + QStringList columns; + columns << "col 1"; + const char *values[1][3] = {{"0/0/3.5/30", "0/0/5.0/30", "0/0/6.5/30"}}; + + table->setRowCount(1); + table->setColumnCount(3); + + for (int col = 0; col < columns.size(); col++) { + for (int row = 0; row < rows.size(); row++) { + QModelIndex index = table->model()->index(col, row); + table->model()->setData(index, values[col][row]); + } + } + + m_proxy->setItemModel(table->model()); + m_proxy->setRowRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setColumnRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setRowRolePattern(QRegExp(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setRowRoleReplace(QStringLiteral("\\2")); + m_proxy->setValueRolePattern(QRegExp(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setValueRoleReplace(QStringLiteral("\\3")); + m_proxy->setColumnRolePattern(QRegExp(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setColumnRoleReplace(QStringLiteral("\\1")); + + QBar3DSeries *series = new QBar3DSeries(m_proxy); + + graph->addSeries(series); + + QCoreApplication::processEvents(); + QCOMPARE(graph->valueAxis()->max(), 6.5f); + m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBFirst); + QCoreApplication::processEvents(); + QCOMPARE(graph->valueAxis()->max(), 3.5f); + m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBLast); + QCoreApplication::processEvents(); + QCOMPARE(graph->valueAxis()->max(), 6.5f); + m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBAverage); + QCoreApplication::processEvents(); + QCOMPARE(graph->valueAxis()->max(), 5.0f); + m_proxy->setMultiMatchBehavior(QItemModelBarDataProxy::MMBCumulative); + QCoreApplication::processEvents(); + QCOMPARE(graph->valueAxis()->max(), 15.0f); + + QCOMPARE(m_proxy->columnLabels().count(), 1); + QCOMPARE(m_proxy->rowCount(), 1); + QCOMPARE(m_proxy->rowLabels().count(), 1); + QVERIFY(m_proxy->series()); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dbars-proxy/q3dbars-proxy.pro b/tests/auto/cpptest/q3dbars-proxy/q3dbars-proxy.pro new file mode 100644 index 00000000..b0b5d361 --- /dev/null +++ b/tests/auto/cpptest/q3dbars-proxy/q3dbars-proxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp new file mode 100644 index 00000000..8d64ed54 --- /dev/null +++ b/tests/auto/cpptest/q3dbars-proxy/tst_proxy.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QBarDataProxy> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QBarDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QBarDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QBarDataProxy *proxy = new QBarDataProxy(); + QVERIFY(proxy); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->columnLabels().count(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + QCOMPARE(m_proxy->rowLabels().count(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeBar); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + m_proxy->setColumnLabels(QStringList() << "1" << "2" << "3"); + QBarDataRow *data = new QBarDataRow; + *data << 1.0f << 3.0f << 7.5f; + m_proxy->addRow(data); + m_proxy->setRowLabels(QStringList() << "1"); + + QCOMPARE(m_proxy->columnLabels().count(), 3); + QCOMPARE(m_proxy->rowCount(), 1); + QCOMPARE(m_proxy->rowLabels().count(), 1); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dbars-series/q3dbars-series.pro b/tests/auto/cpptest/q3dbars-series/q3dbars-series.pro new file mode 100644 index 00000000..481653ef --- /dev/null +++ b/tests/auto/cpptest/q3dbars-series/q3dbars-series.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_series.cpp diff --git a/tests/auto/cpptest/q3dbars-series/tst_series.cpp b/tests/auto/cpptest/q3dbars-series/tst_series.cpp new file mode 100644 index 00000000..e2a40ae7 --- /dev/null +++ b/tests/auto/cpptest/q3dbars-series/tst_series.cpp @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QBar3DSeries> + +using namespace QtDataVisualization; + +class tst_series: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QBar3DSeries *m_series; +}; + +void tst_series::initTestCase() +{ +} + +void tst_series::cleanupTestCase() +{ +} + +void tst_series::init() +{ + m_series = new QBar3DSeries(); +} + +void tst_series::cleanup() +{ + delete m_series; +} + +void tst_series::construct() +{ + QBar3DSeries *series = new QBar3DSeries(); + QVERIFY(series); + delete series; + + QBarDataProxy *proxy = new QBarDataProxy(); + + series = new QBar3DSeries(proxy); + QVERIFY(series); + QCOMPARE(series->dataProxy(), proxy); + delete series; +} + +void tst_series::initialProperties() +{ + QVERIFY(m_series); + + QVERIFY(m_series->dataProxy()); + QCOMPARE(m_series->meshAngle(), 0.0f); + QCOMPARE(m_series->selectedBar(), m_series->invalidSelectionPosition()); + + // Common properties + QCOMPARE(m_series->baseColor(), QColor(Qt::black)); + QCOMPARE(m_series->baseGradient(), QLinearGradient()); + QCOMPARE(m_series->colorStyle(), Q3DTheme::ColorStyleUniform); + QCOMPARE(m_series->itemLabel(), QString("")); + QCOMPARE(m_series->itemLabelFormat(), QString("@valueLabel")); + QCOMPARE(m_series->isItemLabelVisible(), true); + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshBevelBar); + QCOMPARE(m_series->meshRotation(), QQuaternion(1, 0, 0, 0)); + QCOMPARE(m_series->isMeshSmooth(), false); + QCOMPARE(m_series->multiHighlightColor(), QColor(Qt::black)); + QCOMPARE(m_series->multiHighlightGradient(), QLinearGradient()); + QCOMPARE(m_series->name(), QString("")); + QCOMPARE(m_series->singleHighlightColor(), QColor(Qt::black)); + QCOMPARE(m_series->singleHighlightGradient(), QLinearGradient()); + QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeBar); + QCOMPARE(m_series->userDefinedMesh(), QString("")); + QCOMPARE(m_series->isVisible(), true); +} + +void tst_series::initializeProperties() +{ + QVERIFY(m_series); + + m_series->setDataProxy(new QBarDataProxy()); + m_series->setMeshAngle(15.0f); + m_series->setSelectedBar(QPoint(0, 0)); + + QCOMPARE(m_series->meshAngle(), 15.0f); + QCOMPARE(m_series->selectedBar(), QPoint(0, 0)); + + QLinearGradient gradient1; + gradient1.setColorAt(0.0, Qt::red); + gradient1.setColorAt(1.0, Qt::blue); + QLinearGradient gradient2; + gradient2.setColorAt(0.0, Qt::yellow); + gradient2.setColorAt(1.0, Qt::green); + QLinearGradient gradient3; + gradient3.setColorAt(0.0, Qt::white); + gradient3.setColorAt(1.0, Qt::gray); + + // Common properties + m_series->setBaseColor(QColor(Qt::blue)); + m_series->setBaseGradient(gradient1); + m_series->setColorStyle(Q3DTheme::ColorStyleRangeGradient); + m_series->setItemLabelFormat("%f"); + m_series->setItemLabelVisible(false); + m_series->setMesh(QAbstract3DSeries::MeshCone); + m_series->setMeshSmooth(true); + m_series->setMultiHighlightColor(QColor(Qt::green)); + m_series->setMultiHighlightGradient(gradient2); + m_series->setName("name"); + m_series->setSingleHighlightColor(QColor(Qt::red)); + m_series->setSingleHighlightGradient(gradient3); + m_series->setUserDefinedMesh(":/customitem.obj"); + m_series->setVisible(false); + + QCOMPARE(m_series->baseColor(), QColor(Qt::blue)); + QCOMPARE(m_series->baseGradient(), gradient1); + QCOMPARE(m_series->baseGradient().stops().at(0).second, QColor(Qt::red)); + QCOMPARE(m_series->colorStyle(), Q3DTheme::ColorStyleRangeGradient); + QCOMPARE(m_series->itemLabelFormat(), QString("%f")); + QCOMPARE(m_series->isItemLabelVisible(), false); + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshCone); + QCOMPARE(m_series->isMeshSmooth(), true); + QCOMPARE(m_series->multiHighlightColor(), QColor(Qt::green)); + QCOMPARE(m_series->multiHighlightGradient(), gradient2); + QCOMPARE(m_series->multiHighlightGradient().stops().at(0).second, QColor(Qt::yellow)); + QCOMPARE(m_series->name(), QString("name")); + QCOMPARE(m_series->singleHighlightColor(), QColor(Qt::red)); + QCOMPARE(m_series->singleHighlightGradient(), gradient3); + QCOMPARE(m_series->singleHighlightGradient().stops().at(0).second, QColor(Qt::white)); + QCOMPARE(m_series->userDefinedMesh(), QString(":/customitem.obj")); + QCOMPARE(m_series->isVisible(), false); +} + +void tst_series::invalidProperties() +{ + m_series->setMesh(QAbstract3DSeries::MeshMinimal); + + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshBevelBar); +} + +QTEST_MAIN(tst_series) +#include "tst_series.moc" diff --git a/tests/auto/cpptest/q3dbars/q3dbars.pro b/tests/auto/cpptest/q3dbars/q3dbars.pro new file mode 100644 index 00000000..a7f7c809 --- /dev/null +++ b/tests/auto/cpptest/q3dbars/q3dbars.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_bars.cpp diff --git a/tests/auto/cpptest/q3dbars/tst_bars.cpp b/tests/auto/cpptest/q3dbars/tst_bars.cpp new file mode 100644 index 00000000..6910e4fa --- /dev/null +++ b/tests/auto/cpptest/q3dbars/tst_bars.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DBars> +#include <QtDataVisualization/QCustom3DItem> +#include <QtDataVisualization/Q3DInputHandler> +#include <QtDataVisualization/QTouch3DInputHandler> + +using namespace QtDataVisualization; + +class tst_bars: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + + void addSeries(); + void addMultipleSeries(); + void selectSeries(); + void removeSeries(); + void removeMultipleSeries(); + + // The following tests are not required for scatter or surface, as they are handled identically + void addInputHandler(); + void removeInputHandler(); + + void addTheme(); + void removeTheme(); + + void addCustomItem(); + void removeCustomItem(); + + void renderToImage(); + +private: + Q3DBars *m_graph; +}; + +QBar3DSeries *newSeries() +{ + QBar3DSeries *series = new QBar3DSeries; + QBarDataRow *data = new QBarDataRow; + *data << -1.0f << 3.0f << 7.5f << 5.0f << 2.2f; + series->dataProxy()->addRow(data); + return series; +} + +void tst_bars::initTestCase() +{ +} + +void tst_bars::cleanupTestCase() +{ +} + +void tst_bars::init() +{ + m_graph = new Q3DBars(); +} + +void tst_bars::cleanup() +{ + delete m_graph; +} + +void tst_bars::construct() +{ + Q3DBars *graph = new Q3DBars(); + QVERIFY(graph); + delete graph; + + graph = new Q3DBars(new QSurfaceFormat()); + QVERIFY(graph); + delete graph; +} + +void tst_bars::initialProperties() +{ + QVERIFY(m_graph); + QCOMPARE(m_graph->isMultiSeriesUniform(), false); + QCOMPARE(m_graph->barThickness(), 1.0); + QCOMPARE(m_graph->barSpacing(), QSizeF(1.0f, 1.0f)); + QCOMPARE(m_graph->isBarSpacingRelative(), true); + QCOMPARE(m_graph->seriesList().length(), 0); + QVERIFY(!m_graph->selectedSeries()); + QVERIFY(!m_graph->primarySeries()); + QCOMPARE(m_graph->floorLevel(), 0.0); + QCOMPARE(m_graph->columnAxis()->orientation(), QAbstract3DAxis::AxisOrientationX); + QCOMPARE(m_graph->valueAxis()->orientation(), QAbstract3DAxis::AxisOrientationY); + QCOMPARE(m_graph->rowAxis()->orientation(), QAbstract3DAxis::AxisOrientationZ); + + // Common properties + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium); + QVERIFY(m_graph->scene()); + QCOMPARE(m_graph->measureFps(), false); + QCOMPARE(m_graph->isOrthoProjection(), false); + QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone); + QCOMPARE(m_graph->aspectRatio(), 2.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault); + QCOMPARE(m_graph->isPolar(), false); + QCOMPARE(m_graph->radialLabelOffset(), 1.0); + QCOMPARE(m_graph->horizontalAspectRatio(), 0.0); + QCOMPARE(m_graph->isReflection(), false); + QCOMPARE(m_graph->reflectivity(), 0.5); + QCOMPARE(m_graph->locale(), QLocale("C")); + QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0)); + QCOMPARE(m_graph->margin(), -1.0); +} + +void tst_bars::initializeProperties() +{ + QVERIFY(m_graph); + + m_graph->setMultiSeriesUniform(true); + m_graph->setBarThickness(0.2f); + m_graph->setBarSpacing(QSizeF(0.1f, 0.1f)); + m_graph->setBarSpacingRelative(false); + m_graph->setFloorLevel(1.0f); + + QCOMPARE(m_graph->isMultiSeriesUniform(), true); + QCOMPARE(m_graph->barThickness(), 0.2f); + QCOMPARE(m_graph->barSpacing(), QSizeF(0.1f, 0.1f)); + QCOMPARE(m_graph->isBarSpacingRelative(), false); + QCOMPARE(m_graph->floorLevel(), 1.0f); + + Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia); + m_graph->setActiveTheme(theme); + m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh); + m_graph->setMeasureFps(true); + m_graph->setOrthoProjection(true); + m_graph->setAspectRatio(1.0); + m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); + m_graph->setPolar(true); + m_graph->setRadialLabelOffset(0.1f); + m_graph->setHorizontalAspectRatio(1.0); + m_graph->setReflection(true); + m_graph->setReflectivity(0.1); + m_graph->setLocale(QLocale("FI")); + m_graph->setMargin(1.0); + + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows + QCOMPARE(m_graph->measureFps(), true); + QCOMPARE(m_graph->isOrthoProjection(), true); + QCOMPARE(m_graph->aspectRatio(), 1.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic); + QCOMPARE(m_graph->isPolar(), true); + QCOMPARE(m_graph->radialLabelOffset(), 0.1f); + QCOMPARE(m_graph->horizontalAspectRatio(), 1.0); + QCOMPARE(m_graph->isReflection(), true); + QCOMPARE(m_graph->reflectivity(), 0.1); + QCOMPARE(m_graph->locale(), QLocale("FI")); + QCOMPARE(m_graph->margin(), 1.0); +} + +void tst_bars::invalidProperties() +{ + m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + m_graph->setAspectRatio(-1.0); + m_graph->setHorizontalAspectRatio(-1.0); + m_graph->setReflectivity(-1.0); + m_graph->setLocale(QLocale("XX")); + + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->locale(), QLocale("C")); +} + +void tst_bars::addSeries() +{ + QBar3DSeries *series = newSeries(); + + m_graph->addSeries(series); + + QCOMPARE(m_graph->seriesList().length(), 1); + QVERIFY(!m_graph->selectedSeries()); + QCOMPARE(m_graph->primarySeries(), series); +} + +void tst_bars::addMultipleSeries() +{ + QBar3DSeries *series = newSeries(); + QBar3DSeries *series2 = newSeries(); + QBar3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + QCOMPARE(m_graph->seriesList().length(), 3); + QCOMPARE(m_graph->primarySeries(), series); + + m_graph->setPrimarySeries(series2); + + QCOMPARE(m_graph->primarySeries(), series2); +} + +void tst_bars::selectSeries() +{ + QBar3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->primarySeries()->setSelectedBar(QPoint(0, 0)); + + QCOMPARE(m_graph->seriesList().length(), 1); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->clearSelection(); + QVERIFY(!m_graph->selectedSeries()); +} + +void tst_bars::removeSeries() +{ + QBar3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +void tst_bars::removeMultipleSeries() +{ + QBar3DSeries *series = newSeries(); + QBar3DSeries *series2 = newSeries(); + QBar3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + m_graph->primarySeries()->setSelectedBar(QPoint(0, 0)); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 2); + QCOMPARE(m_graph->primarySeries(), series2); + QVERIFY(!m_graph->selectedSeries()); + + m_graph->removeSeries(series2); + QCOMPARE(m_graph->seriesList().length(), 1); + QCOMPARE(m_graph->primarySeries(), series3); + + m_graph->removeSeries(series3); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +// The following tests are not required for scatter or surface, as they are handled identically +void tst_bars::addInputHandler() +{ + Q3DInputHandler *handler = new Q3DInputHandler(); + QTouch3DInputHandler *handler2 = new QTouch3DInputHandler(); + QAbstract3DInputHandler *initialHandler = m_graph->activeInputHandler(); + + m_graph->addInputHandler(handler); + m_graph->addInputHandler(handler2); + + QCOMPARE(m_graph->inputHandlers().length(), 3); // Default, as it is still active, plus added ones + QCOMPARE(m_graph->activeInputHandler(), initialHandler); + m_graph->setActiveInputHandler(handler2); + QCOMPARE(m_graph->activeInputHandler(), handler2); + + m_graph->setActiveInputHandler(NULL); + QVERIFY(!m_graph->activeInputHandler()); + QCOMPARE(m_graph->inputHandlers().length(), 2); +} + +void tst_bars::removeInputHandler() +{ + Q3DInputHandler *handler = new Q3DInputHandler(); + QTouch3DInputHandler *handler2 = new QTouch3DInputHandler(); + + m_graph->addInputHandler(handler); + m_graph->addInputHandler(handler2); + + m_graph->setActiveInputHandler(handler2); + QCOMPARE(m_graph->inputHandlers().length(), 2); // Default handler removed by previous call + QCOMPARE(m_graph->activeInputHandler(), handler2); + m_graph->releaseInputHandler(handler2); + QCOMPARE(m_graph->inputHandlers().length(), 1); + m_graph->releaseInputHandler(handler); + QCOMPARE(m_graph->inputHandlers().length(), 0); + + delete handler2; + delete handler; +} + +void tst_bars::addTheme() +{ + Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia); + Q3DTheme *theme2 = new Q3DTheme(); + Q3DTheme *initialTheme = m_graph->activeTheme(); + m_graph->addTheme(theme); + m_graph->addTheme(theme2); + + QCOMPARE(m_graph->themes().length(), 3); // Default, plus added ones + QCOMPARE(m_graph->activeTheme(), initialTheme); + m_graph->setActiveTheme(theme2); + QCOMPARE(m_graph->activeTheme(), theme2); +} + +void tst_bars::removeTheme() +{ + Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia); + Q3DTheme *theme2 = new Q3DTheme(); + m_graph->addTheme(theme); + m_graph->addTheme(theme2); + + m_graph->setActiveTheme(theme2); + QCOMPARE(m_graph->activeTheme(), theme2); + m_graph->releaseTheme(theme2); + QCOMPARE(m_graph->themes().length(), 2); + m_graph->releaseTheme(theme); + QCOMPARE(m_graph->themes().length(), 1); // Default theme remains + + delete theme2; + delete theme; +} + +void tst_bars::addCustomItem() +{ + QCustom3DItem *item = new QCustom3DItem(); + QCustom3DItem *item2 = new QCustom3DItem(); + + m_graph->addCustomItem(item); + QCOMPARE(m_graph->customItems().length(), 1); + m_graph->addCustomItem(item2); + QCOMPARE(m_graph->customItems().length(), 2); +} + +void tst_bars::removeCustomItem() +{ + QCustom3DItem *item = new QCustom3DItem(); + QCustom3DItem *item2 = new QCustom3DItem(); + QCustom3DItem *item3 = new QCustom3DItem(); + item3->setPosition(QVector3D(1, 1, 1)); + + m_graph->addCustomItem(item); + m_graph->addCustomItem(item2); + m_graph->addCustomItem(item3); + + m_graph->releaseCustomItem(item); + QCOMPARE(m_graph->customItems().length(), 2); + m_graph->removeCustomItem(item2); + QCOMPARE(m_graph->customItems().length(), 1); + m_graph->addCustomItem(item); + m_graph->removeCustomItemAt(QVector3D(1, 1, 1)); + QCOMPARE(m_graph->customItems().length(), 1); + m_graph->removeCustomItems(); + QCOMPARE(m_graph->customItems().length(), 0); +} + +void tst_bars::renderToImage() +{ + m_graph->addSeries(newSeries()); + + QImage image = m_graph->renderToImage(); + QCOMPARE(image.size(), m_graph->size()); + + image = m_graph->renderToImage(8); + QCOMPARE(image.size(), m_graph->size()); + + image = m_graph->renderToImage(4, QSize(300, 300)); + QCOMPARE(image.size(), QSize(300, 300)); +} + +QTEST_MAIN(tst_bars) +#include "tst_bars.moc" diff --git a/tests/auto/cpptest/q3dcustom-label/q3dcustom-label.pro b/tests/auto/cpptest/q3dcustom-label/q3dcustom-label.pro new file mode 100644 index 00000000..af584baa --- /dev/null +++ b/tests/auto/cpptest/q3dcustom-label/q3dcustom-label.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_custom.cpp diff --git a/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp b/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp new file mode 100644 index 00000000..40bd5eac --- /dev/null +++ b/tests/auto/cpptest/q3dcustom-label/tst_custom.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QCustom3DLabel> + +using namespace QtDataVisualization; + +class tst_custom: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QCustom3DLabel *m_custom; +}; + +void tst_custom::initTestCase() +{ +} + +void tst_custom::cleanupTestCase() +{ +} + +void tst_custom::init() +{ + m_custom = new QCustom3DLabel(); +} + +void tst_custom::cleanup() +{ + delete m_custom; +} + +void tst_custom::construct() +{ + QCustom3DLabel *custom = new QCustom3DLabel(); + QVERIFY(custom); + delete custom; + + custom = new QCustom3DLabel("label", QFont("Times New Roman", 10.0), QVector3D(1.0, 1.0, 1.0), + QVector3D(1.0, 1.0, 1.0), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QVERIFY(custom); + QCOMPARE(custom->backgroundColor(), QColor(Qt::gray)); + QCOMPARE(custom->isBackgroundEnabled(), true); + QCOMPARE(custom->isBorderEnabled(), true); + QCOMPARE(custom->isFacingCamera(), false); + QCOMPARE(custom->font(), QFont("Times New Roman", 10)); + QCOMPARE(custom->text(), QString("label")); + QCOMPARE(custom->textColor(), QColor(Qt::white)); + QCOMPARE(custom->meshFile(), QString(":/defaultMeshes/plane")); + QCOMPARE(custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isPositionAbsolute(), false); + QCOMPARE(custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isScalingAbsolute(), true); + QCOMPARE(custom->isShadowCasting(), false); + QCOMPARE(custom->textureFile(), QString()); + QCOMPARE(custom->isVisible(), true); + delete custom; +} + +void tst_custom::initialProperties() +{ + QVERIFY(m_custom); + + QCOMPARE(m_custom->backgroundColor(), QColor(Qt::gray)); + QCOMPARE(m_custom->isBackgroundEnabled(), true); + QCOMPARE(m_custom->isBorderEnabled(), true); + QCOMPARE(m_custom->isFacingCamera(), false); + QCOMPARE(m_custom->font(), QFont("Arial", 20)); + QCOMPARE(m_custom->text(), QString()); + QCOMPARE(m_custom->textColor(), QColor(Qt::white)); + + // Common (from QCustom3DItem) + QCOMPARE(m_custom->meshFile(), QString(":/defaultMeshes/plane")); + QCOMPARE(m_custom->position(), QVector3D()); + QCOMPARE(m_custom->isPositionAbsolute(), false); + QCOMPARE(m_custom->rotation(), QQuaternion()); + QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f)); + QCOMPARE(m_custom->isScalingAbsolute(), true); + QCOMPARE(m_custom->isShadowCasting(), false); + QCOMPARE(m_custom->textureFile(), QString()); + QCOMPARE(m_custom->isVisible(), true); +} + +void tst_custom::initializeProperties() +{ + QVERIFY(m_custom); + + m_custom->setBackgroundColor(QColor(Qt::red)); + m_custom->setBackgroundEnabled(false); + m_custom->setBorderEnabled(false); + m_custom->setFacingCamera(true); + m_custom->setFont(QFont("Times New Roman", 10)); + m_custom->setText(QString("This is a Custom Label")); + m_custom->setTextColor(QColor(Qt::blue)); + + QCOMPARE(m_custom->backgroundColor(), QColor(Qt::red)); + QCOMPARE(m_custom->isBackgroundEnabled(), false); + QCOMPARE(m_custom->isBorderEnabled(), false); + QCOMPARE(m_custom->isFacingCamera(), true); + QCOMPARE(m_custom->font(), QFont("Times New Roman", 10)); + QCOMPARE(m_custom->text(), QString("This is a Custom Label")); + QCOMPARE(m_custom->textColor(), QColor(Qt::blue)); + + // Common (from QCustom3DItem) + m_custom->setPosition(QVector3D(1.0, 1.0, 1.0)); + m_custom->setPositionAbsolute(true); + m_custom->setRotation(QQuaternion(1.0, 1.0, 10.0, 100.0)); + m_custom->setScaling(QVector3D(1.0, 1.0, 1.0)); + m_custom->setShadowCasting(true); + m_custom->setVisible(false); + + QCOMPARE(m_custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isPositionAbsolute(), true); + QCOMPARE(m_custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(m_custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isShadowCasting(), true); + QCOMPARE(m_custom->isVisible(), false); +} + +void tst_custom::invalidProperties() +{ + m_custom->setScalingAbsolute(false); + QCOMPARE(m_custom->isScalingAbsolute(), true); +} + +QTEST_MAIN(tst_custom) +#include "tst_custom.moc" diff --git a/tests/auto/cpptest/q3dcustom-volume/q3dcustom-volume.pro b/tests/auto/cpptest/q3dcustom-volume/q3dcustom-volume.pro new file mode 100644 index 00000000..af584baa --- /dev/null +++ b/tests/auto/cpptest/q3dcustom-volume/q3dcustom-volume.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_custom.cpp diff --git a/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp b/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp new file mode 100644 index 00000000..372e8ecf --- /dev/null +++ b/tests/auto/cpptest/q3dcustom-volume/tst_custom.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QCustom3DVolume> + +using namespace QtDataVisualization; + +class tst_custom: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QCustom3DVolume *m_custom; +}; + +void tst_custom::initTestCase() +{ +} + +void tst_custom::cleanupTestCase() +{ +} + +void tst_custom::init() +{ + m_custom = new QCustom3DVolume(); +} + +void tst_custom::cleanup() +{ + delete m_custom; +} + +void tst_custom::construct() +{ + QCustom3DVolume *custom = new QCustom3DVolume(); + QVERIFY(custom); + delete custom; + + QVector<uchar> *tdata = new QVector<uchar>(1000); + + QVector<QRgb> table; + table << QRgb(0xff00ff) << QRgb(0x00ff00); + + custom = new QCustom3DVolume(QVector3D(1.0, 1.0, 1.0), QVector3D(1.0, 1.0, 1.0), + QQuaternion(1.0, 1.0, 10.0, 100.0), 10, 10, 10, + tdata, QImage::Format_ARGB32, table); + QVERIFY(custom); + QCOMPARE(custom->alphaMultiplier(), 1.0f); + QCOMPARE(custom->drawSliceFrames(), false); + QCOMPARE(custom->drawSliceFrames(), false); + QCOMPARE(custom->preserveOpacity(), true); + QCOMPARE(custom->sliceFrameColor(), QColor(Qt::black)); + QCOMPARE(custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(custom->sliceIndexX(), -1); + QCOMPARE(custom->sliceIndexY(), -1); + QCOMPARE(custom->sliceIndexZ(), -1); + QCOMPARE(custom->useHighDefShader(), true); + QCOMPARE(custom->textureData()->length(), 1000); + QCOMPARE(custom->textureDataWidth(), 40); + QCOMPARE(custom->textureFormat(), QImage::Format_ARGB32); + QCOMPARE(custom->textureHeight(), 10); + QCOMPARE(custom->textureWidth(), 10); + QCOMPARE(custom->textureDepth(), 10); + QCOMPARE(custom->meshFile(), QString(":/defaultMeshes/barFull")); + QCOMPARE(custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isPositionAbsolute(), false); + QCOMPARE(custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isScalingAbsolute(), true); + QCOMPARE(custom->isShadowCasting(), false); + QCOMPARE(custom->textureFile(), QString()); + QCOMPARE(custom->isVisible(), true); + delete custom; +} + +void tst_custom::initialProperties() +{ + QVERIFY(m_custom); + + QCOMPARE(m_custom->alphaMultiplier(), 1.0f); + QCOMPARE(m_custom->drawSliceFrames(), false); + QCOMPARE(m_custom->drawSliceFrames(), false); + QCOMPARE(m_custom->preserveOpacity(), true); + QCOMPARE(m_custom->sliceFrameColor(), QColor(Qt::black)); + QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f)); + QCOMPARE(m_custom->sliceIndexX(), -1); + QCOMPARE(m_custom->sliceIndexY(), -1); + QCOMPARE(m_custom->sliceIndexZ(), -1); + QCOMPARE(m_custom->useHighDefShader(), true); + + // Common (from QCustom3DVolume) + QCOMPARE(m_custom->meshFile(), QString(":/defaultMeshes/barFull")); + QCOMPARE(m_custom->position(), QVector3D()); + QCOMPARE(m_custom->isPositionAbsolute(), false); + QCOMPARE(m_custom->rotation(), QQuaternion()); + QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f)); + QCOMPARE(m_custom->isScalingAbsolute(), true); + QCOMPARE(m_custom->isShadowCasting(), true); + QCOMPARE(m_custom->textureFile(), QString()); + QCOMPARE(m_custom->isVisible(), true); +} + +void tst_custom::initializeProperties() +{ + QVERIFY(m_custom); + + m_custom->setAlphaMultiplier(0.1f); + m_custom->setDrawSliceFrames(true); + m_custom->setDrawSliceFrames(true); + m_custom->setPreserveOpacity(false); + m_custom->setSliceFrameColor(QColor(Qt::red)); + m_custom->setSliceFrameGaps(QVector3D(2.0f, 2.0f, 2.0f)); + m_custom->setSliceFrameThicknesses(QVector3D(2.0f, 2.0f, 2.0f)); + m_custom->setSliceFrameWidths(QVector3D(2.0f, 2.0f, 2.0f)); + m_custom->setSliceIndexX(0); + m_custom->setSliceIndexY(0); + m_custom->setSliceIndexZ(0); + m_custom->setUseHighDefShader(false); + + QCOMPARE(m_custom->alphaMultiplier(), 0.1f); + QCOMPARE(m_custom->drawSliceFrames(), true); + QCOMPARE(m_custom->drawSliceFrames(), true); + QCOMPARE(m_custom->preserveOpacity(), false); + QCOMPARE(m_custom->sliceFrameColor(), QColor(Qt::red)); + QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(2.0f, 2.0f, 2.0f)); + QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(2.0f, 2.0f, 2.0f)); + QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(2.0f, 2.0f, 2.0f)); + QCOMPARE(m_custom->sliceIndexX(), 0); + QCOMPARE(m_custom->sliceIndexY(), 0); + QCOMPARE(m_custom->sliceIndexZ(), 0); + QCOMPARE(m_custom->useHighDefShader(), false); + + // Common (from QCustom3DVolume) + m_custom->setPosition(QVector3D(1.0, 1.0, 1.0)); + m_custom->setPositionAbsolute(true); + m_custom->setRotation(QQuaternion(1.0, 1.0, 10.0, 100.0)); + m_custom->setScaling(QVector3D(1.0, 1.0, 1.0)); + m_custom->setScalingAbsolute(false); + m_custom->setShadowCasting(false); + m_custom->setVisible(false); + + QCOMPARE(m_custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isPositionAbsolute(), true); + QCOMPARE(m_custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(m_custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isScalingAbsolute(), false); + QCOMPARE(m_custom->isShadowCasting(), false); + QCOMPARE(m_custom->isVisible(), false); +} + +void tst_custom::invalidProperties() +{ + m_custom->setAlphaMultiplier(-1.0f); + QCOMPARE(m_custom->alphaMultiplier(), 1.0f); + + m_custom->setSliceFrameGaps(QVector3D(-0.1f, -0.1f, -0.1f)); + QCOMPARE(m_custom->sliceFrameGaps(), QVector3D(0.01f, 0.01f, 0.01f)); + + m_custom->setSliceFrameThicknesses(QVector3D(-0.1f, -0.1f, -0.1f)); + QCOMPARE(m_custom->sliceFrameThicknesses(), QVector3D(0.01f, 0.01f, 0.01f)); + + m_custom->setSliceFrameWidths(QVector3D(-0.1f, -0.1f, -0.1f)); + QCOMPARE(m_custom->sliceFrameWidths(), QVector3D(0.01f, 0.01f, 0.01f)); + + m_custom->setTextureFormat(QImage::Format_ARGB8555_Premultiplied); + QCOMPARE(m_custom->textureFormat(), QImage::Format_ARGB32); +} + +QTEST_MAIN(tst_custom) +#include "tst_custom.moc" diff --git a/tests/auto/cpptest/q3dcustom/q3dcustom.pro b/tests/auto/cpptest/q3dcustom/q3dcustom.pro new file mode 100644 index 00000000..af584baa --- /dev/null +++ b/tests/auto/cpptest/q3dcustom/q3dcustom.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_custom.cpp diff --git a/tests/auto/cpptest/q3dcustom/tst_custom.cpp b/tests/auto/cpptest/q3dcustom/tst_custom.cpp new file mode 100644 index 00000000..abc088f9 --- /dev/null +++ b/tests/auto/cpptest/q3dcustom/tst_custom.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QCustom3DItem> + +using namespace QtDataVisualization; + +class tst_custom: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QCustom3DItem *m_custom; +}; + +void tst_custom::initTestCase() +{ +} + +void tst_custom::cleanupTestCase() +{ +} + +void tst_custom::init() +{ + m_custom = new QCustom3DItem(); +} + +void tst_custom::cleanup() +{ + delete m_custom; +} + +void tst_custom::construct() +{ + QCustom3DItem *custom = new QCustom3DItem(); + QVERIFY(custom); + delete custom; + + custom = new QCustom3DItem(":/customitem.obj", QVector3D(1.0, 1.0, 1.0), + QVector3D(1.0, 1.0, 1.0), QQuaternion(1.0, 1.0, 10.0, 100.0), + QImage(":/customtexture.jpg")); + QVERIFY(custom); + QCOMPARE(custom->meshFile(), QString(":/customitem.obj")); + QCOMPARE(custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isPositionAbsolute(), false); + QCOMPARE(custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(custom->isScalingAbsolute(), true); + QCOMPARE(custom->isShadowCasting(), true); + QCOMPARE(custom->textureFile(), QString()); + QCOMPARE(custom->isVisible(), true); + delete custom; +} + +void tst_custom::initialProperties() +{ + QVERIFY(m_custom); + + QCOMPARE(m_custom->meshFile(), QString()); + QCOMPARE(m_custom->position(), QVector3D()); + QCOMPARE(m_custom->isPositionAbsolute(), false); + QCOMPARE(m_custom->rotation(), QQuaternion()); + QCOMPARE(m_custom->scaling(), QVector3D(0.1f, 0.1f, 0.1f)); + QCOMPARE(m_custom->isScalingAbsolute(), true); + QCOMPARE(m_custom->isShadowCasting(), true); + QCOMPARE(m_custom->textureFile(), QString()); + QCOMPARE(m_custom->isVisible(), true); +} + +void tst_custom::initializeProperties() +{ + QVERIFY(m_custom); + + m_custom->setMeshFile(":/customitem.obj"); + m_custom->setPosition(QVector3D(1.0, 1.0, 1.0)); + m_custom->setPositionAbsolute(true); + m_custom->setRotation(QQuaternion(1.0, 1.0, 10.0, 100.0)); + m_custom->setScaling(QVector3D(1.0, 1.0, 1.0)); + m_custom->setScalingAbsolute(false); + m_custom->setShadowCasting(false); + m_custom->setTextureFile(":/customtexture.jpg"); + m_custom->setVisible(false); + + QCOMPARE(m_custom->meshFile(), QString(":/customitem.obj")); + QCOMPARE(m_custom->position(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isPositionAbsolute(), true); + QCOMPARE(m_custom->rotation(), QQuaternion(1.0, 1.0, 10.0, 100.0)); + QCOMPARE(m_custom->scaling(), QVector3D(1.0, 1.0, 1.0)); + QCOMPARE(m_custom->isScalingAbsolute(), false); + QCOMPARE(m_custom->isShadowCasting(), false); + QCOMPARE(m_custom->textureFile(), QString(":/customtexture.jpg")); + QCOMPARE(m_custom->isVisible(), false); + + m_custom->setTextureImage(QImage(QSize(10, 10), QImage::Format_ARGB32)); + QCOMPARE(m_custom->textureFile(), QString()); +} + +QTEST_MAIN(tst_custom) +#include "tst_custom.moc" diff --git a/tests/auto/cpptest/q3dinput-touch/q3dinput-touch.pro b/tests/auto/cpptest/q3dinput-touch/q3dinput-touch.pro new file mode 100644 index 00000000..2de48158 --- /dev/null +++ b/tests/auto/cpptest/q3dinput-touch/q3dinput-touch.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_input.cpp diff --git a/tests/auto/cpptest/q3dinput-touch/tst_input.cpp b/tests/auto/cpptest/q3dinput-touch/tst_input.cpp new file mode 100644 index 00000000..53d760ae --- /dev/null +++ b/tests/auto/cpptest/q3dinput-touch/tst_input.cpp @@ -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 +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtDataVisualization/QTouch3DInputHandler> + +using namespace QtDataVisualization; + +class tst_input: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QTouch3DInputHandler *m_input; +}; + +void tst_input::initTestCase() +{ +} + +void tst_input::cleanupTestCase() +{ +} + +void tst_input::init() +{ + m_input = new QTouch3DInputHandler(); +} + +void tst_input::cleanup() +{ + delete m_input; +} + +void tst_input::construct() +{ + QTouch3DInputHandler *input = new QTouch3DInputHandler(); + QVERIFY(input); + delete input; +} + +void tst_input::initialProperties() +{ + QVERIFY(m_input); + + // Common (from Q3DInputHandler and QAbstract3DInputHandler) + QCOMPARE(m_input->isRotationEnabled(), true); + QCOMPARE(m_input->isSelectionEnabled(), true); + QCOMPARE(m_input->isZoomAtTargetEnabled(), true); + QCOMPARE(m_input->isZoomEnabled(), true); + QCOMPARE(m_input->inputPosition(), QPoint(0, 0)); + QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewNone); + QVERIFY(!m_input->scene()); +} + +void tst_input::initializeProperties() +{ + QVERIFY(m_input); + + // Common (from Q3DInputHandler and QAbstract3DInputHandler) + m_input->setRotationEnabled(false); + m_input->setSelectionEnabled(false); + m_input->setZoomAtTargetEnabled(false); + m_input->setZoomEnabled(false); + m_input->setInputPosition(QPoint(100, 100)); + m_input->setInputView(QAbstract3DInputHandler::InputViewOnPrimary); + + QCOMPARE(m_input->isRotationEnabled(), false); + QCOMPARE(m_input->isSelectionEnabled(), false); + QCOMPARE(m_input->isZoomAtTargetEnabled(), false); + QCOMPARE(m_input->isZoomEnabled(), false); + QCOMPARE(m_input->inputPosition(), QPoint(100, 100)); + QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewOnPrimary); +} + +// TODO: QTRD-3380 (mouse/touch events) + +QTEST_MAIN(tst_input) +#include "tst_input.moc" diff --git a/tests/auto/cpptest/q3dinput/q3dinput.pro b/tests/auto/cpptest/q3dinput/q3dinput.pro new file mode 100644 index 00000000..2de48158 --- /dev/null +++ b/tests/auto/cpptest/q3dinput/q3dinput.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_input.cpp diff --git a/tests/auto/cpptest/q3dinput/tst_input.cpp b/tests/auto/cpptest/q3dinput/tst_input.cpp new file mode 100644 index 00000000..68b2225c --- /dev/null +++ b/tests/auto/cpptest/q3dinput/tst_input.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DInputHandler> + +using namespace QtDataVisualization; + +class tst_input: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + Q3DInputHandler *m_input; +}; + +void tst_input::initTestCase() +{ +} + +void tst_input::cleanupTestCase() +{ +} + +void tst_input::init() +{ + m_input = new Q3DInputHandler(); +} + +void tst_input::cleanup() +{ + delete m_input; +} + +void tst_input::construct() +{ + Q3DInputHandler *input = new Q3DInputHandler(); + QVERIFY(input); + delete input; +} + +void tst_input::initialProperties() +{ + QVERIFY(m_input); + + QCOMPARE(m_input->isRotationEnabled(), true); + QCOMPARE(m_input->isSelectionEnabled(), true); + QCOMPARE(m_input->isZoomAtTargetEnabled(), true); + QCOMPARE(m_input->isZoomEnabled(), true); + + // Common (from QAbstract3DInputHandler) + QCOMPARE(m_input->inputPosition(), QPoint(0, 0)); + QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewNone); + QVERIFY(!m_input->scene()); +} + +void tst_input::initializeProperties() +{ + QVERIFY(m_input); + + m_input->setRotationEnabled(false); + m_input->setSelectionEnabled(false); + m_input->setZoomAtTargetEnabled(false); + m_input->setZoomEnabled(false); + + QCOMPARE(m_input->isRotationEnabled(), false); + QCOMPARE(m_input->isSelectionEnabled(), false); + QCOMPARE(m_input->isZoomAtTargetEnabled(), false); + QCOMPARE(m_input->isZoomEnabled(), false); + + // Common (from QAbstract3DInputHandler) + m_input->setInputPosition(QPoint(100, 100)); + m_input->setInputView(QAbstract3DInputHandler::InputViewOnPrimary); + + QCOMPARE(m_input->inputPosition(), QPoint(100, 100)); + QCOMPARE(m_input->inputView(), QAbstract3DInputHandler::InputViewOnPrimary); +} + +// TODO: QTRD-3380 (mouse events) + +QTEST_MAIN(tst_input) +#include "tst_input.moc" diff --git a/tests/auto/cpptest/q3dscatter-modelproxy/q3dscatter-modelproxy.pro b/tests/auto/cpptest/q3dscatter-modelproxy/q3dscatter-modelproxy.pro new file mode 100644 index 00000000..c383ec25 --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-modelproxy/q3dscatter-modelproxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization widgets + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp new file mode 100644 index 00000000..9d5cea90 --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-modelproxy/tst_proxy.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QItemModelScatterDataProxy> +#include <QtDataVisualization/Q3DScatter> +#include <QtWidgets/QTableWidget> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + + void addModel(); + +private: + QItemModelScatterDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QItemModelScatterDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QItemModelScatterDataProxy *proxy = new QItemModelScatterDataProxy(); + QVERIFY(proxy); + delete proxy; + + QTableWidget *table = new QTableWidget(); + + proxy = new QItemModelScatterDataProxy(table->model()); + QVERIFY(proxy); + delete proxy; + + proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z"); + QVERIFY(proxy); + QCOMPARE(proxy->xPosRole(), QString("x")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("z")); + QCOMPARE(proxy->rotationRole(), QString("")); + delete proxy; + + proxy = new QItemModelScatterDataProxy(table->model(), "x", "y", "z", "rot"); + QVERIFY(proxy); + QCOMPARE(proxy->xPosRole(), QString("x")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("z")); + QCOMPARE(proxy->rotationRole(), QString("rot")); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QVERIFY(!m_proxy->itemModel()); + QCOMPARE(m_proxy->rotationRole(), QString()); + QCOMPARE(m_proxy->rotationRolePattern(), QRegExp()); + QCOMPARE(m_proxy->rotationRoleReplace(), QString()); + QCOMPARE(m_proxy->xPosRole(), QString()); + QCOMPARE(m_proxy->xPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->xPosRoleReplace(), QString()); + QCOMPARE(m_proxy->yPosRole(), QString()); + QCOMPARE(m_proxy->yPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->yPosRoleReplace(), QString()); + QCOMPARE(m_proxy->zPosRole(), QString()); + QCOMPARE(m_proxy->zPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->zPosRoleReplace(), QString()); + + QCOMPARE(m_proxy->itemCount(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeScatter); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + QTableWidget *table = new QTableWidget(); + + m_proxy->setItemModel(table->model()); + m_proxy->setRotationRole("rotation"); + m_proxy->setRotationRolePattern(QRegExp("/-/")); + m_proxy->setRotationRoleReplace("\\\\1"); + m_proxy->setXPosRole("X"); + m_proxy->setXPosRolePattern(QRegExp("/-/")); + m_proxy->setXPosRoleReplace("\\\\1"); + m_proxy->setYPosRole("Y"); + m_proxy->setYPosRolePattern(QRegExp("/-/")); + m_proxy->setYPosRoleReplace("\\\\1"); + m_proxy->setZPosRole("Z"); + m_proxy->setZPosRolePattern(QRegExp("/-/")); + m_proxy->setZPosRoleReplace("\\\\1"); + + QVERIFY(m_proxy->itemModel()); + QCOMPARE(m_proxy->rotationRole(), QString("rotation")); + QCOMPARE(m_proxy->rotationRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->rotationRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->xPosRole(), QString("X")); + QCOMPARE(m_proxy->xPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->xPosRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->yPosRole(), QString("Y")); + QCOMPARE(m_proxy->yPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->yPosRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->zPosRole(), QString("Z")); + QCOMPARE(m_proxy->zPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->zPosRoleReplace(), QString("\\\\1")); +} + +void tst_proxy::addModel() +{ + QTableWidget *table = new QTableWidget(); + QStringList rows; + rows << "row 1"; + QStringList columns; + columns << "col 1"; + const char *values[1][2] = {{"0/0/5.5/30", "0/0/10.5/30"}}; + + table->setRowCount(2); + table->setColumnCount(1); + + for (int col = 0; col < columns.size(); col++) { + for (int row = 0; row < rows.size(); row++) { + QModelIndex index = table->model()->index(col, row); + table->model()->setData(index, values[col][row]); + } + } + + m_proxy->setItemModel(table->model()); + m_proxy->setXPosRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setZPosRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setXPosRolePattern(QRegExp(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setXPosRoleReplace(QStringLiteral("\\2")); + m_proxy->setYPosRolePattern(QRegExp(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setYPosRoleReplace(QStringLiteral("\\3")); + m_proxy->setZPosRolePattern(QRegExp(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setZPosRoleReplace(QStringLiteral("\\1")); + + QScatter3DSeries *series = new QScatter3DSeries(m_proxy); + Q_UNUSED(series) + + QCoreApplication::processEvents(); + + QCOMPARE(m_proxy->itemCount(), 2); + QVERIFY(m_proxy->series()); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dscatter-proxy/q3dscatter-proxy.pro b/tests/auto/cpptest/q3dscatter-proxy/q3dscatter-proxy.pro new file mode 100644 index 00000000..b0b5d361 --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-proxy/q3dscatter-proxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp new file mode 100644 index 00000000..436350dc --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-proxy/tst_proxy.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QScatterDataProxy> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QScatterDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QScatterDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QScatterDataProxy *proxy = new QScatterDataProxy(); + QVERIFY(proxy); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->itemCount(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeScatter); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + QScatterDataArray data; + data << QVector3D(0.5f, 0.5f, 0.5f) << QVector3D(-0.3f, -0.5f, -0.4f); + m_proxy->addItems(data); + + QCOMPARE(m_proxy->itemCount(), 2); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dscatter-series/q3dscatter-series.pro b/tests/auto/cpptest/q3dscatter-series/q3dscatter-series.pro new file mode 100644 index 00000000..481653ef --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-series/q3dscatter-series.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_series.cpp diff --git a/tests/auto/cpptest/q3dscatter-series/tst_series.cpp b/tests/auto/cpptest/q3dscatter-series/tst_series.cpp new file mode 100644 index 00000000..df290579 --- /dev/null +++ b/tests/auto/cpptest/q3dscatter-series/tst_series.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QScatter3DSeries> + +using namespace QtDataVisualization; + +class tst_series: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QScatter3DSeries *m_series; +}; + +void tst_series::initTestCase() +{ +} + +void tst_series::cleanupTestCase() +{ +} + +void tst_series::init() +{ + m_series = new QScatter3DSeries(); +} + +void tst_series::cleanup() +{ + delete m_series; +} + +void tst_series::construct() +{ + QScatter3DSeries *series = new QScatter3DSeries(); + QVERIFY(series); + delete series; + + QScatterDataProxy *proxy = new QScatterDataProxy(); + + series = new QScatter3DSeries(proxy); + QVERIFY(series); + QCOMPARE(series->dataProxy(), proxy); + delete series; +} + +void tst_series::initialProperties() +{ + QVERIFY(m_series); + + QVERIFY(m_series->dataProxy()); + QCOMPARE(m_series->itemSize(), 0.0f); + QCOMPARE(m_series->selectedItem(), m_series->invalidSelectionIndex()); + + // Common properties. The ones identical between different series are tested in QBar3DSeries tests + QCOMPARE(m_series->itemLabelFormat(), QString("@xLabel, @yLabel, @zLabel")); + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere); + QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeScatter); +} + +void tst_series::initializeProperties() +{ + QVERIFY(m_series); + + m_series->setDataProxy(new QScatterDataProxy()); + m_series->setItemSize(0.5f); + m_series->setSelectedItem(0); + + QCOMPARE(m_series->itemSize(), 0.5f); + QCOMPARE(m_series->selectedItem(), 0); + + // Common properties. The ones identical between different series are tested in QBar3DSeries tests + m_series->setMesh(QAbstract3DSeries::MeshPoint); + m_series->setMeshRotation(QQuaternion(1, 1, 10, 20)); + + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshPoint); + QCOMPARE(m_series->meshRotation(), QQuaternion(1, 1, 10, 20)); +} + +QTEST_MAIN(tst_series) +#include "tst_series.moc" diff --git a/tests/auto/cpptest/q3dscatter/q3dscatter.pro b/tests/auto/cpptest/q3dscatter/q3dscatter.pro new file mode 100644 index 00000000..9f356ebc --- /dev/null +++ b/tests/auto/cpptest/q3dscatter/q3dscatter.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_scatter.cpp diff --git a/tests/auto/cpptest/q3dscatter/tst_scatter.cpp b/tests/auto/cpptest/q3dscatter/tst_scatter.cpp new file mode 100644 index 00000000..5a3b6550 --- /dev/null +++ b/tests/auto/cpptest/q3dscatter/tst_scatter.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DScatter> + +using namespace QtDataVisualization; + +class tst_scatter: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + + void addSeries(); + void addMultipleSeries(); + void selectSeries(); + void removeSeries(); + void removeMultipleSeries(); + +private: + Q3DScatter *m_graph; +}; + +QScatter3DSeries *newSeries() +{ + QScatter3DSeries *series = new QScatter3DSeries; + QScatterDataArray data; + data << QVector3D(0.5f, 0.5f, 0.5f) << QVector3D(-0.3f, -0.5f, -0.4f) << QVector3D(0.0f, -0.3f, 0.2f); + series->dataProxy()->addItems(data); + return series; +} + +void tst_scatter::initTestCase() +{ +} + +void tst_scatter::cleanupTestCase() +{ +} + +void tst_scatter::init() +{ + m_graph = new Q3DScatter(); +} + +void tst_scatter::cleanup() +{ + delete m_graph; +} + +void tst_scatter::construct() +{ + Q3DScatter *graph = new Q3DScatter(); + QVERIFY(graph); + delete graph; + + graph = new Q3DScatter(new QSurfaceFormat()); + QVERIFY(graph); + delete graph; +} + +void tst_scatter::initialProperties() +{ + QVERIFY(m_graph); + QCOMPARE(m_graph->seriesList().length(), 0); + QVERIFY(!m_graph->selectedSeries()); + QCOMPARE(m_graph->axisX()->orientation(), QAbstract3DAxis::AxisOrientationX); + QCOMPARE(m_graph->axisY()->orientation(), QAbstract3DAxis::AxisOrientationY); + QCOMPARE(m_graph->axisZ()->orientation(), QAbstract3DAxis::AxisOrientationZ); + + // Common properties + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium); + QVERIFY(m_graph->scene()); + QCOMPARE(m_graph->measureFps(), false); + QCOMPARE(m_graph->isOrthoProjection(), false); + QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone); + QCOMPARE(m_graph->aspectRatio(), 2.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault); + QCOMPARE(m_graph->isPolar(), false); + QCOMPARE(m_graph->radialLabelOffset(), 1.0); + QCOMPARE(m_graph->horizontalAspectRatio(), 0.0); + QCOMPARE(m_graph->isReflection(), false); + QCOMPARE(m_graph->reflectivity(), 0.5); + QCOMPARE(m_graph->locale(), QLocale("C")); + QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0)); + QCOMPARE(m_graph->margin(), -1.0); +} + +void tst_scatter::initializeProperties() +{ + Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia); + m_graph->setActiveTheme(theme); + m_graph->setSelectionMode(QAbstract3DGraph::SelectionNone); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh); + m_graph->setMeasureFps(true); + m_graph->setOrthoProjection(true); + m_graph->setAspectRatio(1.0); + m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); + m_graph->setPolar(true); + m_graph->setRadialLabelOffset(0.1f); + m_graph->setHorizontalAspectRatio(1.0); + m_graph->setReflection(true); + m_graph->setReflectivity(0.1); + m_graph->setLocale(QLocale("FI")); + m_graph->setMargin(1.0); + + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionNone); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows + QCOMPARE(m_graph->measureFps(), true); + QCOMPARE(m_graph->isOrthoProjection(), true); + QCOMPARE(m_graph->aspectRatio(), 1.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic); + QCOMPARE(m_graph->isPolar(), true); + QCOMPARE(m_graph->radialLabelOffset(), 0.1f); + QCOMPARE(m_graph->horizontalAspectRatio(), 1.0); + QCOMPARE(m_graph->isReflection(), true); + QCOMPARE(m_graph->reflectivity(), 0.1); + QCOMPARE(m_graph->locale(), QLocale("FI")); + QCOMPARE(m_graph->margin(), 1.0); +} + +void tst_scatter::invalidProperties() +{ + m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + m_graph->setAspectRatio(-1.0); + m_graph->setHorizontalAspectRatio(-1.0); + m_graph->setReflectivity(-1.0); + m_graph->setLocale(QLocale("XX")); + + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->locale(), QLocale("C")); +} + +void tst_scatter::addSeries() +{ + m_graph->addSeries(newSeries()); + + QCOMPARE(m_graph->seriesList().length(), 1); + QVERIFY(!m_graph->selectedSeries()); +} + +void tst_scatter::addMultipleSeries() +{ + QScatter3DSeries *series = newSeries(); + QScatter3DSeries *series2 = newSeries(); + QScatter3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + QCOMPARE(m_graph->seriesList().length(), 3); +} + +void tst_scatter::selectSeries() +{ + QScatter3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->seriesList()[0]->setSelectedItem(1); + + QCOMPARE(m_graph->seriesList().length(), 1); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->clearSelection(); + QVERIFY(!m_graph->selectedSeries()); +} + +void tst_scatter::removeSeries() +{ + QScatter3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +void tst_scatter::removeMultipleSeries() +{ + QScatter3DSeries *series = newSeries(); + QScatter3DSeries *series2 = newSeries(); + QScatter3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + m_graph->seriesList()[0]->setSelectedItem(1); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 2); + QVERIFY(!m_graph->selectedSeries()); + + m_graph->removeSeries(series2); + QCOMPARE(m_graph->seriesList().length(), 1); + + m_graph->removeSeries(series3); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +QTEST_MAIN(tst_scatter) +#include "tst_scatter.moc" diff --git a/tests/auto/cpptest/q3dscene-camera/q3dscene-camera.pro b/tests/auto/cpptest/q3dscene-camera/q3dscene-camera.pro new file mode 100644 index 00000000..c575a55e --- /dev/null +++ b/tests/auto/cpptest/q3dscene-camera/q3dscene-camera.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_camera.cpp diff --git a/tests/auto/cpptest/q3dscene-camera/tst_camera.cpp b/tests/auto/cpptest/q3dscene-camera/tst_camera.cpp new file mode 100644 index 00000000..ee321b22 --- /dev/null +++ b/tests/auto/cpptest/q3dscene-camera/tst_camera.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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DCamera> + +using namespace QtDataVisualization; + +class tst_camera: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + + void changePresets(); + +private: + Q3DCamera *m_camera; +}; + +void tst_camera::initTestCase() +{ +} + +void tst_camera::cleanupTestCase() +{ +} + +void tst_camera::init() +{ + m_camera = new Q3DCamera(); +} + +void tst_camera::cleanup() +{ + delete m_camera; +} + +void tst_camera::construct() +{ + Q3DCamera *camera = new Q3DCamera(); + QVERIFY(camera); + delete camera; +} + +void tst_camera::initialProperties() +{ + QVERIFY(m_camera); + + QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone); + QCOMPARE(m_camera->maxZoomLevel(), 500.0f); + QCOMPARE(m_camera->minZoomLevel(), 10.0f); + QCOMPARE(m_camera->target(), QVector3D(0.0, 0.0, 0.0)); + QCOMPARE(m_camera->wrapXRotation(), true); + QCOMPARE(m_camera->wrapYRotation(), false); + QCOMPARE(m_camera->xRotation(), 0.0f); + QCOMPARE(m_camera->yRotation(), 0.0f); + QCOMPARE(m_camera->zoomLevel(), 100.0f); + + // Common (from Q3DObject) + QVERIFY(!m_camera->parentScene()); + QCOMPARE(m_camera->position(), QVector3D(0, 0, 0)); +} + +void tst_camera::initializeProperties() +{ + QVERIFY(m_camera); + + m_camera->setMaxZoomLevel(1000.0f); + m_camera->setMinZoomLevel(100.0f); + m_camera->setTarget(QVector3D(1.0, -1.0, 1.0)); + m_camera->setWrapXRotation(false); + m_camera->setWrapYRotation(true); + m_camera->setXRotation(30.0f); + m_camera->setYRotation(30.0f); + m_camera->setZoomLevel(500.0f); + + QCOMPARE(m_camera->maxZoomLevel(), 1000.0f); + QCOMPARE(m_camera->minZoomLevel(), 100.0f); + QCOMPARE(m_camera->target(), QVector3D(1.0, -1.0, 1.0)); + QCOMPARE(m_camera->wrapXRotation(), false); + QCOMPARE(m_camera->wrapYRotation(), true); + QCOMPARE(m_camera->xRotation(), 30.0f); + QCOMPARE(m_camera->yRotation(), 30.0f); + QCOMPARE(m_camera->zoomLevel(), 500.0f); + + m_camera->setPosition(QVector3D(1.0, 1.0, 1.0)); + + // Common (from Q3DObject) + QCOMPARE(m_camera->position(), QVector3D(1.0, 1.0, 1.0)); +} + +void tst_camera::invalidProperties() +{ + m_camera->setTarget(QVector3D(-1.5, -1.5, -1.5)); + QCOMPARE(m_camera->target(), QVector3D(-1.0, -1.0, -1.0)); + + m_camera->setTarget(QVector3D(1.5, 1.5, 1.5)); + QCOMPARE(m_camera->target(), QVector3D(1.0, 1.0, 1.0)); + + m_camera->setMinZoomLevel(0.1f); + QCOMPARE(m_camera->minZoomLevel(), 1.0f); +} + +void tst_camera::changePresets() +{ + m_camera->setCameraPreset(Q3DCamera::CameraPresetBehind); // Will be overridden by the the following sets + m_camera->setMaxZoomLevel(1000.0f); + m_camera->setMinZoomLevel(100.0f); + m_camera->setTarget(QVector3D(1.0, -1.0, 1.0)); + m_camera->setWrapXRotation(false); + m_camera->setWrapYRotation(true); + m_camera->setXRotation(30.0f); + m_camera->setYRotation(30.0f); + m_camera->setZoomLevel(500.0f); + + QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone); + QCOMPARE(m_camera->maxZoomLevel(), 1000.0f); + QCOMPARE(m_camera->minZoomLevel(), 100.0f); + QCOMPARE(m_camera->target(), QVector3D(1.0, -1.0, 1.0)); + QCOMPARE(m_camera->wrapXRotation(), false); + QCOMPARE(m_camera->wrapYRotation(), true); + QCOMPARE(m_camera->xRotation(), 30.0f); + QCOMPARE(m_camera->yRotation(), 30.0f); + QCOMPARE(m_camera->zoomLevel(), 500.0f); + + m_camera->setCameraPreset(Q3DCamera::CameraPresetBehind); // Sets target and rotations + + QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetBehind); + QCOMPARE(m_camera->maxZoomLevel(), 1000.0f); + QCOMPARE(m_camera->minZoomLevel(), 100.0f); + QCOMPARE(m_camera->target(), QVector3D(0.0, 0.0, 0.0)); + QCOMPARE(m_camera->wrapXRotation(), false); + QCOMPARE(m_camera->wrapYRotation(), true); + QCOMPARE(m_camera->xRotation(), 180.0f); + QCOMPARE(m_camera->yRotation(), 22.5f); + QCOMPARE(m_camera->zoomLevel(), 500.0f); + + m_camera->setCameraPosition(10.0f, 15.0f, 125.0f); // Overrides preset + + QCOMPARE(m_camera->cameraPreset(), Q3DCamera::CameraPresetNone); + QCOMPARE(m_camera->maxZoomLevel(), 1000.0f); + QCOMPARE(m_camera->minZoomLevel(), 100.0f); + QCOMPARE(m_camera->target(), QVector3D(0.0, 0.0, 0.0)); + QCOMPARE(m_camera->wrapXRotation(), false); + QCOMPARE(m_camera->wrapYRotation(), true); + QCOMPARE(m_camera->xRotation(), 10.0f); + QCOMPARE(m_camera->yRotation(), 15.0f); + QCOMPARE(m_camera->zoomLevel(), 125.0f); +} + +QTEST_MAIN(tst_camera) +#include "tst_camera.moc" diff --git a/tests/auto/cpptest/q3dscene-light/q3dscene-light.pro b/tests/auto/cpptest/q3dscene-light/q3dscene-light.pro new file mode 100644 index 00000000..21a3c934 --- /dev/null +++ b/tests/auto/cpptest/q3dscene-light/q3dscene-light.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_light.cpp diff --git a/tests/auto/cpptest/q3dscene-light/tst_light.cpp b/tests/auto/cpptest/q3dscene-light/tst_light.cpp new file mode 100644 index 00000000..4568b01e --- /dev/null +++ b/tests/auto/cpptest/q3dscene-light/tst_light.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DLight> + +using namespace QtDataVisualization; + +class tst_light: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + Q3DLight *m_light; +}; + +void tst_light::initTestCase() +{ +} + +void tst_light::cleanupTestCase() +{ +} + +void tst_light::init() +{ + m_light = new Q3DLight(); +} + +void tst_light::cleanup() +{ + delete m_light; +} + +void tst_light::construct() +{ + Q3DLight *light = new Q3DLight(); + QVERIFY(light); + delete light; +} + +void tst_light::initialProperties() +{ + QVERIFY(m_light); + + // TODO: Has no adjustable properties yet. + // Keeping this as a placeholder for future implementations (QTRD-2406) + + // Common (from Q3DObject) + QVERIFY(!m_light->parentScene()); + QCOMPARE(m_light->position(), QVector3D(0, 0, 0)); +} + +void tst_light::initializeProperties() +{ + QVERIFY(m_light); + + m_light->setPosition(QVector3D(1.0, 1.0, 1.0)); + + // Common (from Q3DObject) + QCOMPARE(m_light->position(), QVector3D(1.0, 1.0, 1.0)); +} + +QTEST_MAIN(tst_light) +#include "tst_light.moc" diff --git a/tests/auto/cpptest/q3dscene/q3dscene.pro b/tests/auto/cpptest/q3dscene/q3dscene.pro new file mode 100644 index 00000000..b9be69c0 --- /dev/null +++ b/tests/auto/cpptest/q3dscene/q3dscene.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_scene.cpp diff --git a/tests/auto/cpptest/q3dscene/tst_scene.cpp b/tests/auto/cpptest/q3dscene/tst_scene.cpp new file mode 100644 index 00000000..7d1ecad3 --- /dev/null +++ b/tests/auto/cpptest/q3dscene/tst_scene.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DScene> +#include <QtDataVisualization/Q3DBars> + +using namespace QtDataVisualization; + +class tst_scene: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + + void subViews(); + +private: + Q3DScene *m_scene; +}; + +void tst_scene::initTestCase() +{ +} + +void tst_scene::cleanupTestCase() +{ +} + +void tst_scene::init() +{ + m_scene = new Q3DScene(); +} + +void tst_scene::cleanup() +{ + delete m_scene; +} + +void tst_scene::construct() +{ + Q3DScene *scene = new Q3DScene(); + QVERIFY(scene); + delete scene; +} + +void tst_scene::initialProperties() +{ + QVERIFY(m_scene); + + QVERIFY(m_scene->activeCamera()); + QVERIFY(m_scene->activeLight()); + QCOMPARE(m_scene->devicePixelRatio(), 1.0f); + QCOMPARE(m_scene->graphPositionQuery(), m_scene->invalidSelectionPoint()); + QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 0, 0)); + QCOMPARE(m_scene->secondarySubViewport(), QRect(0, 0, 0, 0)); + QCOMPARE(m_scene->isSecondarySubviewOnTop(), true); + QCOMPARE(m_scene->selectionQueryPosition(), m_scene->invalidSelectionPoint()); + QCOMPARE(m_scene->isSlicingActive(), false); + QCOMPARE(m_scene->viewport(), QRect(0, 0, 0, 0)); +} + +void tst_scene::initializeProperties() +{ + QVERIFY(m_scene); + + Q3DCamera *camera1 = new Q3DCamera(); + Q3DLight *light1 = new Q3DLight(); + + m_scene->setActiveCamera(camera1); + m_scene->setActiveLight(light1); + m_scene->setDevicePixelRatio(2.0f); + m_scene->setGraphPositionQuery(QPoint(0, 0)); + m_scene->setPrimarySubViewport(QRect(0, 0, 50, 50)); + m_scene->setSecondarySubViewport(QRect(50, 50, 100, 100)); + m_scene->setSecondarySubviewOnTop(false); + m_scene->setSelectionQueryPosition(QPoint(0, 0)); + m_scene->setSlicingActive(true); + + QCOMPARE(m_scene->activeCamera(), camera1); + QCOMPARE(m_scene->activeLight(), light1); + QCOMPARE(m_scene->devicePixelRatio(), 2.0f); + QCOMPARE(m_scene->graphPositionQuery(), QPoint(0, 0)); // TODO: When doing signal checks, add tests to check that queries return something (asynchronously) + // TODO: subviewports are not set (QTRD-2435) + //QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 50, 50)); + //QCOMPARE(m_scene->secondarySubViewport(), QRect(50, 50, 100, 100)); + QCOMPARE(m_scene->isSecondarySubviewOnTop(), false); + QCOMPARE(m_scene->selectionQueryPosition(), QPoint(0, 0)); // TODO: When doing signal checks, add tests to check that queries return something (asynchronously) + QCOMPARE(m_scene->isSlicingActive(), true); + // TODO: viewport is not set by subviewports (QTRD-2435) + //QCOMPARE(m_scene->viewport(), QRect(0, 0, 100, 100)); +} + +void tst_scene::invalidProperties() +{ + m_scene->setPrimarySubViewport(QRect(0, 0, -50, -50)); + m_scene->setSecondarySubViewport(QRect(-50, -50, -100, -100)); + QCOMPARE(m_scene->primarySubViewport(), QRect(0, 0, 0, 0)); + QCOMPARE(m_scene->secondarySubViewport(), QRect(0, 0, 0, 0)); +} + +void tst_scene::subViews() +{ + Q3DBars *graph = new Q3DBars(); + graph->setPosition(QPoint(0, 0)); + graph->setWidth(200); + graph->setHeight(200); + + Q3DScene *scene = graph->scene(); + + QCoreApplication::processEvents(); + + QCOMPARE(scene->viewport(), QRect(0, 0, 200, 200)); + QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 200, 200)); + QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 0, 0)); + + QCOMPARE(scene->isSecondarySubviewOnTop(), true); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), true); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(201, 201)), false); + QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), false); + + scene->setSlicingActive(true); + + QCOMPARE(scene->isSecondarySubviewOnTop(), false); + QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 40, 40)); + QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 200, 200)); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), false); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(30, 30)), true); + QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), true); + QCOMPARE(scene->isPointInSecondarySubView(QPoint(30, 30)), false); + + scene->setSecondarySubviewOnTop(true); + + QCOMPARE(scene->isSecondarySubviewOnTop(), true); + QCOMPARE(scene->primarySubViewport(), QRect(0, 0, 40, 40)); + QCOMPARE(scene->secondarySubViewport(), QRect(0, 0, 200, 200)); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(100, 100)), false); + QCOMPARE(scene->isPointInPrimarySubView(QPoint(30, 30)), false); + QCOMPARE(scene->isPointInSecondarySubView(QPoint(100, 100)), true); + QCOMPARE(scene->isPointInSecondarySubView(QPoint(30, 30)), true); +} + +QTEST_MAIN(tst_scene) +#include "tst_scene.moc" diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg b/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg Binary files differnew file mode 100644 index 00000000..2580f5bd --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-heightproxy/customtexture.jpg diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.pro b/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.pro new file mode 100644 index 00000000..56a964d0 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.pro @@ -0,0 +1,11 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp + +RESOURCES += \ + q3dsurface-heightproxy.qrc diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.qrc b/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.qrc new file mode 100644 index 00000000..b83c7ef9 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-heightproxy/q3dsurface-heightproxy.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>customtexture.jpg</file> + </qresource> +</RCC> diff --git a/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp new file mode 100644 index 00000000..20ed1aeb --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-heightproxy/tst_proxy.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QHeightMapSurfaceDataProxy> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QHeightMapSurfaceDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QHeightMapSurfaceDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QHeightMapSurfaceDataProxy *proxy = new QHeightMapSurfaceDataProxy(); + QVERIFY(proxy); + delete proxy; + + proxy = new QHeightMapSurfaceDataProxy(QImage(QSize(10, 10), QImage::Format_ARGB32)); + QVERIFY(proxy); + QCoreApplication::processEvents(); + QCOMPARE(proxy->columnCount(), 10); + QCOMPARE(proxy->rowCount(), 10); + delete proxy; + + proxy = new QHeightMapSurfaceDataProxy(":/customtexture.jpg"); + QVERIFY(proxy); + QCoreApplication::processEvents(); + QCOMPARE(proxy->columnCount(), 24); + QCOMPARE(proxy->rowCount(), 24); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->heightMap(), QImage()); + QCOMPARE(m_proxy->heightMapFile(), QString("")); + QCOMPARE(m_proxy->maxXValue(), 10.0f); + QCOMPARE(m_proxy->maxZValue(), 10.0f); + QCOMPARE(m_proxy->minXValue(), 0.0f); + QCOMPARE(m_proxy->minZValue(), 0.0f); + + QCOMPARE(m_proxy->columnCount(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + m_proxy->setHeightMapFile(":/customtexture.jpg"); + m_proxy->setMaxXValue(11.0f); + m_proxy->setMaxZValue(11.0f); + m_proxy->setMinXValue(-10.0f); + m_proxy->setMinZValue(-10.0f); + + QCoreApplication::processEvents(); + + QCOMPARE(m_proxy->heightMapFile(), QString(":/customtexture.jpg")); + QCOMPARE(m_proxy->maxXValue(), 11.0f); + QCOMPARE(m_proxy->maxZValue(), 11.0f); + QCOMPARE(m_proxy->minXValue(), -10.0f); + QCOMPARE(m_proxy->minZValue(), -10.0f); + + QCOMPARE(m_proxy->columnCount(), 24); + QCOMPARE(m_proxy->rowCount(), 24); + + m_proxy->setHeightMapFile(""); + + QCoreApplication::processEvents(); + + QCOMPARE(m_proxy->columnCount(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + + m_proxy->setHeightMap(QImage(":/customtexture.jpg")); + + QCoreApplication::processEvents(); + + QCOMPARE(m_proxy->columnCount(), 24); + QCOMPARE(m_proxy->rowCount(), 24); +} + +void tst_proxy::invalidProperties() +{ + m_proxy->setMaxXValue(-10.0f); + m_proxy->setMaxZValue(-10.0f); + QCOMPARE(m_proxy->maxXValue(), -10.0f); + QCOMPARE(m_proxy->maxZValue(), -10.0f); + QCOMPARE(m_proxy->minXValue(), -11.0f); + QCOMPARE(m_proxy->minZValue(), -11.0f); + + m_proxy->setMinXValue(10.0f); + m_proxy->setMinZValue(10.0f); + QCOMPARE(m_proxy->maxXValue(), 11.0f); + QCOMPARE(m_proxy->maxZValue(), 11.0f); + QCOMPARE(m_proxy->minXValue(), 10.0f); + QCOMPARE(m_proxy->minZValue(), 10.0f); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dsurface-modelproxy/q3dsurface-modelproxy.pro b/tests/auto/cpptest/q3dsurface-modelproxy/q3dsurface-modelproxy.pro new file mode 100644 index 00000000..c383ec25 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-modelproxy/q3dsurface-modelproxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization widgets + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp new file mode 100644 index 00000000..6bef9478 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-modelproxy/tst_proxy.cpp @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QItemModelSurfaceDataProxy> +#include <QtDataVisualization/Q3DSurface> +#include <QtWidgets/QTableWidget> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + + void multiMatch(); + +private: + QItemModelSurfaceDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QItemModelSurfaceDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + + +void tst_proxy::construct() +{ + QItemModelSurfaceDataProxy *proxy = new QItemModelSurfaceDataProxy(); + QVERIFY(proxy); + delete proxy; + + QTableWidget *table = new QTableWidget(); + + proxy = new QItemModelSurfaceDataProxy(table->model()); + QVERIFY(proxy); + delete proxy; + + proxy = new QItemModelSurfaceDataProxy(table->model(), "y"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("")); + QCOMPARE(proxy->columnRole(), QString("")); + QCOMPARE(proxy->xPosRole(), QString("")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelSurfaceDataProxy(table->model(), "row", "column", "y"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("column")); + QCOMPARE(proxy->xPosRole(), QString("column")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("row")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelSurfaceDataProxy(table->model(), "row", "column", "x", "y", "z"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("column")); + QCOMPARE(proxy->xPosRole(), QString("x")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("z")); + QCOMPARE(proxy->rowCategories().length(), 0); + QCOMPARE(proxy->columnCategories().length(), 0); + delete proxy; + + proxy = new QItemModelSurfaceDataProxy(table->model(), "row", "column", "y", + QStringList() << "rowCat", QStringList() << "colCat"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("column")); + QCOMPARE(proxy->xPosRole(), QString("column")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("row")); + QCOMPARE(proxy->rowCategories().length(), 1); + QCOMPARE(proxy->columnCategories().length(), 1); + delete proxy; + + proxy = new QItemModelSurfaceDataProxy(table->model(), "row", "column", "x", "y", "z", + QStringList() << "rowCat", QStringList() << "colCat"); + QVERIFY(proxy); + QCOMPARE(proxy->rowRole(), QString("row")); + QCOMPARE(proxy->columnRole(), QString("column")); + QCOMPARE(proxy->xPosRole(), QString("x")); + QCOMPARE(proxy->yPosRole(), QString("y")); + QCOMPARE(proxy->zPosRole(), QString("z")); + QCOMPARE(proxy->rowCategories().length(), 1); + QCOMPARE(proxy->columnCategories().length(), 1); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->autoColumnCategories(), true); + QCOMPARE(m_proxy->autoRowCategories(), true); + QCOMPARE(m_proxy->columnCategories(), QStringList()); + QCOMPARE(m_proxy->columnRole(), QString()); + QCOMPARE(m_proxy->columnRolePattern(), QRegExp()); + QCOMPARE(m_proxy->columnRoleReplace(), QString()); + QVERIFY(!m_proxy->itemModel()); + QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelSurfaceDataProxy::MMBLast); + QCOMPARE(m_proxy->rowCategories(), QStringList()); + QCOMPARE(m_proxy->rowRole(), QString()); + QCOMPARE(m_proxy->rowRolePattern(), QRegExp()); + QCOMPARE(m_proxy->rowRoleReplace(), QString()); + QCOMPARE(m_proxy->useModelCategories(), false); + QCOMPARE(m_proxy->xPosRole(), QString()); + QCOMPARE(m_proxy->xPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->xPosRoleReplace(), QString()); + QCOMPARE(m_proxy->yPosRole(), QString()); + QCOMPARE(m_proxy->yPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->yPosRoleReplace(), QString()); + QCOMPARE(m_proxy->zPosRole(), QString()); + QCOMPARE(m_proxy->zPosRolePattern(), QRegExp()); + QCOMPARE(m_proxy->zPosRoleReplace(), QString()); + + QCOMPARE(m_proxy->columnCount(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + QTableWidget *table = new QTableWidget(); + + m_proxy->setAutoColumnCategories(false); + m_proxy->setAutoRowCategories(false); + m_proxy->setColumnCategories(QStringList() << "col1" << "col2"); + m_proxy->setColumnRole("column"); + m_proxy->setColumnRolePattern(QRegExp("/^.*-(\\d\\d)$/")); + m_proxy->setColumnRoleReplace("\\\\1"); + m_proxy->setItemModel(table->model()); + m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBAverage); + m_proxy->setRowCategories(QStringList() << "row1" << "row2"); + m_proxy->setRowRole("row"); + m_proxy->setRowRolePattern(QRegExp("/^(\\d\\d\\d\\d).*$/")); + m_proxy->setRowRoleReplace("\\\\1"); + m_proxy->setUseModelCategories(true); + m_proxy->setXPosRole("X"); + m_proxy->setXPosRolePattern(QRegExp("/-/")); + m_proxy->setXPosRoleReplace("\\\\1"); + m_proxy->setYPosRole("Y"); + m_proxy->setYPosRolePattern(QRegExp("/-/")); + m_proxy->setYPosRoleReplace("\\\\1"); + m_proxy->setZPosRole("Z"); + m_proxy->setZPosRolePattern(QRegExp("/-/")); + m_proxy->setZPosRoleReplace("\\\\1"); + + QCOMPARE(m_proxy->autoColumnCategories(), false); + QCOMPARE(m_proxy->autoRowCategories(), false); + QCOMPARE(m_proxy->columnCategories().count(), 2); + QCOMPARE(m_proxy->columnRole(), QString("column")); + QCOMPARE(m_proxy->columnRolePattern(), QRegExp("/^.*-(\\d\\d)$/")); + QCOMPARE(m_proxy->columnRoleReplace(), QString("\\\\1")); + QVERIFY(m_proxy->itemModel()); + QCOMPARE(m_proxy->multiMatchBehavior(), QItemModelSurfaceDataProxy::MMBAverage); + QCOMPARE(m_proxy->rowCategories().count(), 2); + QCOMPARE(m_proxy->rowRole(), QString("row")); + QCOMPARE(m_proxy->rowRolePattern(), QRegExp("/^(\\d\\d\\d\\d).*$/")); + QCOMPARE(m_proxy->rowRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->useModelCategories(), true); + QCOMPARE(m_proxy->xPosRole(), QString("X")); + QCOMPARE(m_proxy->xPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->xPosRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->yPosRole(), QString("Y")); + QCOMPARE(m_proxy->yPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->yPosRoleReplace(), QString("\\\\1")); + QCOMPARE(m_proxy->zPosRole(), QString("Z")); + QCOMPARE(m_proxy->zPosRolePattern(), QRegExp("/-/")); + QCOMPARE(m_proxy->zPosRoleReplace(), QString("\\\\1")); +} + +void tst_proxy::multiMatch() +{ + Q3DSurface *graph = new Q3DSurface(); + + QTableWidget *table = new QTableWidget(); + QStringList rows; + rows << "row 1" << "row 2"; + QStringList columns; + columns << "col 1" << "col 2" << "col 3" << "col 4"; + const char *values[4][2] = {{"0/0/5.5/30", "0/0/10.5/30"}, + {"0/1/5.5/30", "0/1/0.5/30"}, + {"1/0/5.5/30", "1/0/0.5/30"}, + {"1/1/0.0/30", "1/1/0.0/30"}}; + + table->setRowCount(2); + table->setColumnCount(4); + + for (int col = 0; col < columns.size(); col++) { + for (int row = 0; row < rows.size(); row++) { + QModelIndex index = table->model()->index(col, row); + table->model()->setData(index, values[col][row]); + } + } + + m_proxy->setItemModel(table->model()); + m_proxy->setRowRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setColumnRole(table->model()->roleNames().value(Qt::DisplayRole)); + m_proxy->setRowRolePattern(QRegExp(QStringLiteral("^(\\d*)\\/(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setRowRoleReplace(QStringLiteral("\\2")); + m_proxy->setYPosRolePattern(QRegExp(QStringLiteral("^\\d*(\\/)(\\d*)\\/(\\d*[\\.\\,]?\\d*)\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setYPosRoleReplace(QStringLiteral("\\3")); + m_proxy->setColumnRolePattern(QRegExp(QStringLiteral("^(\\d*)(\\/)(\\d*)\\/\\d*[\\.\\,]?\\d*\\/\\d*[\\.\\,]?\\d*$"))); + m_proxy->setColumnRoleReplace(QStringLiteral("\\1")); + + QSurface3DSeries *series = new QSurface3DSeries(m_proxy); + + graph->addSeries(series); + + QCoreApplication::processEvents(); + QCOMPARE(graph->axisY()->max(), 10.5f); + m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBFirst); + QCoreApplication::processEvents(); + QCOMPARE(graph->axisY()->max(), 5.5f); + m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBLast); + QCoreApplication::processEvents(); + QCOMPARE(graph->axisY()->max(), 10.5f); + m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBAverage); + QCoreApplication::processEvents(); + QCOMPARE(graph->axisY()->max(), 8.0f); + m_proxy->setMultiMatchBehavior(QItemModelSurfaceDataProxy::MMBCumulativeY); + QCoreApplication::processEvents(); + QCOMPARE(graph->axisY()->max(), 16.0f); + + QCOMPARE(m_proxy->columnCount(), 2); + QCOMPARE(m_proxy->rowCount(), 3); + QVERIFY(m_proxy->series()); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dsurface-proxy/q3dsurface-proxy.pro b/tests/auto/cpptest/q3dsurface-proxy/q3dsurface-proxy.pro new file mode 100644 index 00000000..b0b5d361 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-proxy/q3dsurface-proxy.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_proxy.cpp diff --git a/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp b/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp new file mode 100644 index 00000000..4274899d --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-proxy/tst_proxy.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QSurfaceDataProxy> + +using namespace QtDataVisualization; + +class tst_proxy: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + +private: + QSurfaceDataProxy *m_proxy; +}; + +void tst_proxy::initTestCase() +{ +} + +void tst_proxy::cleanupTestCase() +{ +} + +void tst_proxy::init() +{ + m_proxy = new QSurfaceDataProxy(); +} + +void tst_proxy::cleanup() +{ + delete m_proxy; +} + +void tst_proxy::construct() +{ + QSurfaceDataProxy *proxy = new QSurfaceDataProxy(); + QVERIFY(proxy); + delete proxy; +} + +void tst_proxy::initialProperties() +{ + QVERIFY(m_proxy); + + QCOMPARE(m_proxy->columnCount(), 0); + QCOMPARE(m_proxy->rowCount(), 0); + QVERIFY(!m_proxy->series()); + + QCOMPARE(m_proxy->type(), QAbstractDataProxy::DataTypeSurface); +} + +void tst_proxy::initializeProperties() +{ + QVERIFY(m_proxy); + + QSurfaceDataArray *data = new QSurfaceDataArray; + QSurfaceDataRow *dataRow1 = new QSurfaceDataRow; + QSurfaceDataRow *dataRow2 = new QSurfaceDataRow; + *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f); + *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f); + *data << dataRow1 << dataRow2; + + m_proxy->resetArray(data); + + QCOMPARE(m_proxy->columnCount(), 2); + QCOMPARE(m_proxy->rowCount(), 2); +} + +QTEST_MAIN(tst_proxy) +#include "tst_proxy.moc" diff --git a/tests/auto/cpptest/q3dsurface-series/q3dsurface-series.pro b/tests/auto/cpptest/q3dsurface-series/q3dsurface-series.pro new file mode 100644 index 00000000..481653ef --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-series/q3dsurface-series.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_series.cpp diff --git a/tests/auto/cpptest/q3dsurface-series/tst_series.cpp b/tests/auto/cpptest/q3dsurface-series/tst_series.cpp new file mode 100644 index 00000000..50eed686 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface-series/tst_series.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/QSurface3DSeries> + +using namespace QtDataVisualization; + +class tst_series: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + QSurface3DSeries *m_series; +}; + +void tst_series::initTestCase() +{ +} + +void tst_series::cleanupTestCase() +{ +} + +void tst_series::init() +{ + m_series = new QSurface3DSeries(); +} + +void tst_series::cleanup() +{ + delete m_series; +} + +void tst_series::construct() +{ + QSurface3DSeries *series = new QSurface3DSeries(); + QVERIFY(series); + delete series; + + QSurfaceDataProxy *proxy = new QSurfaceDataProxy(); + + series = new QSurface3DSeries(proxy); + QVERIFY(series); + QCOMPARE(series->dataProxy(), proxy); + delete series; +} + +void tst_series::initialProperties() +{ + QVERIFY(m_series); + + QVERIFY(m_series->dataProxy()); + QCOMPARE(m_series->drawMode(), QSurface3DSeries::DrawSurfaceAndWireframe); + QCOMPARE(m_series->isFlatShadingEnabled(), true); + QCOMPARE(m_series->isFlatShadingSupported(), true); + QCOMPARE(m_series->selectedPoint(), m_series->invalidSelectionPosition()); + + // Common properties. The ones identical between different series are tested in QBar3DSeries tests + QCOMPARE(m_series->itemLabelFormat(), QString("@xLabel, @yLabel, @zLabel")); + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere); + QCOMPARE(m_series->type(), QAbstract3DSeries::SeriesTypeSurface); +} + +void tst_series::initializeProperties() +{ + QVERIFY(m_series); + + m_series->setDataProxy(new QSurfaceDataProxy()); + m_series->setDrawMode(QSurface3DSeries::DrawWireframe); + m_series->setFlatShadingEnabled(false); + m_series->setSelectedPoint(QPoint(0, 0)); + + QCOMPARE(m_series->drawMode(), QSurface3DSeries::DrawWireframe); + QCOMPARE(m_series->isFlatShadingEnabled(), false); + QCOMPARE(m_series->selectedPoint(), QPoint(0, 0)); + + // Common properties. The ones identical between different series are tested in QBar3DSeries tests + m_series->setMesh(QAbstract3DSeries::MeshPyramid); + + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshPyramid); +} + +void tst_series::invalidProperties() +{ + m_series->setMesh(QAbstract3DSeries::MeshPoint); + + QCOMPARE(m_series->mesh(), QAbstract3DSeries::MeshSphere); +} + +QTEST_MAIN(tst_series) +#include "tst_series.moc" diff --git a/tests/auto/cpptest/q3dsurface/q3dsurface.pro b/tests/auto/cpptest/q3dsurface/q3dsurface.pro new file mode 100644 index 00000000..b7a6bf08 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface/q3dsurface.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_surface.cpp diff --git a/tests/auto/cpptest/q3dsurface/tst_surface.cpp b/tests/auto/cpptest/q3dsurface/tst_surface.cpp new file mode 100644 index 00000000..0ae0a326 --- /dev/null +++ b/tests/auto/cpptest/q3dsurface/tst_surface.cpp @@ -0,0 +1,247 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DSurface> + +using namespace QtDataVisualization; + +class tst_surface: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + + void addSeries(); + void addMultipleSeries(); + void selectSeries(); + void removeSeries(); + void removeMultipleSeries(); + +private: + Q3DSurface *m_graph; +}; + +QSurface3DSeries *newSeries() +{ + QSurface3DSeries *series = new QSurface3DSeries; + QSurfaceDataArray *data = new QSurfaceDataArray; + QSurfaceDataRow *dataRow1 = new QSurfaceDataRow; + QSurfaceDataRow *dataRow2 = new QSurfaceDataRow; + *dataRow1 << QVector3D(0.0f, 0.1f, 0.5f) << QVector3D(1.0f, 0.5f, 0.5f); + *dataRow2 << QVector3D(0.0f, 1.8f, 1.0f) << QVector3D(1.0f, 1.2f, 1.0f); + *data << dataRow1 << dataRow2; + series->dataProxy()->resetArray(data); + + return series; +} + +void tst_surface::initTestCase() +{ +} + +void tst_surface::cleanupTestCase() +{ +} + +void tst_surface::init() +{ + m_graph = new Q3DSurface(); +} + +void tst_surface::cleanup() +{ + delete m_graph; +} + +void tst_surface::construct() +{ + Q3DSurface *graph = new Q3DSurface(); + QVERIFY(graph); + delete graph; + + graph = new Q3DSurface(new QSurfaceFormat()); + QVERIFY(graph); + delete graph; +} + +void tst_surface::initialProperties() +{ + QVERIFY(m_graph); + QCOMPARE(m_graph->seriesList().length(), 0); + QVERIFY(!m_graph->selectedSeries()); + QCOMPARE(m_graph->flipHorizontalGrid(), false); + QCOMPARE(m_graph->axisX()->orientation(), QAbstract3DAxis::AxisOrientationX); + QCOMPARE(m_graph->axisY()->orientation(), QAbstract3DAxis::AxisOrientationY); + QCOMPARE(m_graph->axisZ()->orientation(), QAbstract3DAxis::AxisOrientationZ); + + // Common properties + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeQt); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityMedium); + QVERIFY(m_graph->scene()); + QCOMPARE(m_graph->measureFps(), false); + QCOMPARE(m_graph->isOrthoProjection(), false); + QCOMPARE(m_graph->selectedElement(), QAbstract3DGraph::ElementNone); + QCOMPARE(m_graph->aspectRatio(), 2.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationDefault); + QCOMPARE(m_graph->isPolar(), false); + QCOMPARE(m_graph->radialLabelOffset(), 1.0); + QCOMPARE(m_graph->horizontalAspectRatio(), 0.0); + QCOMPARE(m_graph->isReflection(), false); + QCOMPARE(m_graph->reflectivity(), 0.5); + QCOMPARE(m_graph->locale(), QLocale("C")); + QCOMPARE(m_graph->queriedGraphPosition(), QVector3D(0, 0, 0)); + QCOMPARE(m_graph->margin(), -1.0); +} + +void tst_surface::initializeProperties() +{ + m_graph->setFlipHorizontalGrid(true); + + QCOMPARE(m_graph->flipHorizontalGrid(), true); + + Q3DTheme *theme = new Q3DTheme(Q3DTheme::ThemeDigia); + m_graph->setActiveTheme(theme); + m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualitySoftHigh); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualitySoftHigh); + m_graph->setMeasureFps(true); + m_graph->setOrthoProjection(true); + m_graph->setAspectRatio(1.0); + m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); + m_graph->setPolar(true); + m_graph->setRadialLabelOffset(0.1f); + m_graph->setHorizontalAspectRatio(1.0); + m_graph->setReflection(true); + m_graph->setReflectivity(0.1); + m_graph->setLocale(QLocale("FI")); + m_graph->setMargin(1.0); + + QCOMPARE(m_graph->activeTheme()->type(), Q3DTheme::ThemeDigia); + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + QCOMPARE(m_graph->shadowQuality(), QAbstract3DGraph::ShadowQualityNone); // Ortho disables shadows + QCOMPARE(m_graph->measureFps(), true); + QCOMPARE(m_graph->isOrthoProjection(), true); + QCOMPARE(m_graph->aspectRatio(), 1.0); + QCOMPARE(m_graph->optimizationHints(), QAbstract3DGraph::OptimizationStatic); + QCOMPARE(m_graph->isPolar(), true); + QCOMPARE(m_graph->radialLabelOffset(), 0.1f); + QCOMPARE(m_graph->horizontalAspectRatio(), 1.0); + QCOMPARE(m_graph->isReflection(), true); + QCOMPARE(m_graph->reflectivity(), 0.1); + QCOMPARE(m_graph->locale(), QLocale("FI")); + QCOMPARE(m_graph->margin(), 1.0); +} + +void tst_surface::invalidProperties() +{ + m_graph->setSelectionMode(QAbstract3DGraph::SelectionColumn | QAbstract3DGraph::SelectionRow | QAbstract3DGraph::SelectionSlice); + m_graph->setAspectRatio(-1.0); + m_graph->setHorizontalAspectRatio(-1.0); + m_graph->setReflectivity(-1.0); + m_graph->setLocale(QLocale("XX")); + + QCOMPARE(m_graph->selectionMode(), QAbstract3DGraph::SelectionItem); + QCOMPARE(m_graph->aspectRatio(), -1.0/*2.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->horizontalAspectRatio(), -1.0/*0.0*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->reflectivity(), -1.0/*0.5*/); // TODO: Fix once QTRD-3367 is done + QCOMPARE(m_graph->locale(), QLocale("C")); +} + +void tst_surface::addSeries() +{ + m_graph->addSeries(newSeries()); + + QCOMPARE(m_graph->seriesList().length(), 1); + QVERIFY(!m_graph->selectedSeries()); +} + +void tst_surface::addMultipleSeries() +{ + QSurface3DSeries *series = newSeries(); + QSurface3DSeries *series2 = newSeries(); + QSurface3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + QCOMPARE(m_graph->seriesList().length(), 3); +} + +void tst_surface::selectSeries() +{ + QSurface3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->seriesList()[0]->setSelectedPoint(QPoint(0, 0)); + + QCOMPARE(m_graph->seriesList().length(), 1); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->clearSelection(); + QVERIFY(!m_graph->selectedSeries()); +} + +void tst_surface::removeSeries() +{ + QSurface3DSeries *series = newSeries(); + + m_graph->addSeries(series); + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +void tst_surface::removeMultipleSeries() +{ + QSurface3DSeries *series = newSeries(); + QSurface3DSeries *series2 = newSeries(); + QSurface3DSeries *series3 = newSeries(); + + m_graph->addSeries(series); + m_graph->addSeries(series2); + m_graph->addSeries(series3); + + m_graph->seriesList()[0]->setSelectedPoint(QPoint(0, 0)); + QCOMPARE(m_graph->selectedSeries(), series); + + m_graph->removeSeries(series); + QCOMPARE(m_graph->seriesList().length(), 2); + QVERIFY(!m_graph->selectedSeries()); + + m_graph->removeSeries(series2); + QCOMPARE(m_graph->seriesList().length(), 1); + + m_graph->removeSeries(series3); + QCOMPARE(m_graph->seriesList().length(), 0); +} + +QTEST_MAIN(tst_surface) +#include "tst_surface.moc" diff --git a/tests/auto/cpptest/q3dtheme/q3dtheme.pro b/tests/auto/cpptest/q3dtheme/q3dtheme.pro new file mode 100644 index 00000000..30a4802c --- /dev/null +++ b/tests/auto/cpptest/q3dtheme/q3dtheme.pro @@ -0,0 +1,8 @@ +QT += testlib datavisualization + +TARGET = tst_cpptest +CONFIG += console testcase + +TEMPLATE = app + +SOURCES += tst_theme.cpp diff --git a/tests/auto/cpptest/q3dtheme/tst_theme.cpp b/tests/auto/cpptest/q3dtheme/tst_theme.cpp new file mode 100644 index 00000000..35945aef --- /dev/null +++ b/tests/auto/cpptest/q3dtheme/tst_theme.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> + +#include <QtDataVisualization/Q3DTheme> + +using namespace QtDataVisualization; + +class tst_theme: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void construct(); + + void initialProperties(); + void initializeProperties(); + void invalidProperties(); + +private: + Q3DTheme *m_theme; +}; + +void tst_theme::initTestCase() +{ +} + +void tst_theme::cleanupTestCase() +{ +} + +void tst_theme::init() +{ + m_theme = new Q3DTheme(); +} + +void tst_theme::cleanup() +{ + delete m_theme; +} + +void tst_theme::construct() +{ + Q3DTheme *theme = new Q3DTheme(); + QVERIFY(theme); + delete theme; + + theme = new Q3DTheme(Q3DTheme::ThemeEbony); + QVERIFY(theme); + QCOMPARE(theme->ambientLightStrength(), 0.5f); + QCOMPARE(theme->backgroundColor(), QColor(Qt::black)); + QCOMPARE(theme->isBackgroundEnabled(), true); + QCOMPARE(theme->baseColors().length(), 5); + QCOMPARE(theme->baseColors().at(0), QColor(Qt::white)); + QCOMPARE(theme->baseColors().at(4), QColor(QRgb(0x6b6b6b))); + QCOMPARE(theme->baseGradients().length(), 5); + QCOMPARE(theme->baseGradients().at(0).stops().at(1).second, QColor(Qt::white)); + QCOMPARE(theme->baseGradients().at(4).stops().at(1).second, QColor(QRgb(0x6b6b6b))); + QCOMPARE(theme->colorStyle(), Q3DTheme::ColorStyleUniform); + QCOMPARE(theme->font(), QFont("Arial")); + QCOMPARE(theme->isGridEnabled(), true); + QCOMPARE(theme->gridLineColor(), QColor(QRgb(0x35322f))); + QCOMPARE(theme->highlightLightStrength(), 5.0f); + QCOMPARE(theme->labelBackgroundColor(), QColor(0x00, 0x00, 0x00, 0xcd)); + QCOMPARE(theme->isLabelBackgroundEnabled(), true); + QCOMPARE(theme->isLabelBorderEnabled(), false); + QCOMPARE(theme->labelTextColor(), QColor(QRgb(0xaeadac))); + QCOMPARE(theme->lightColor(), QColor(Qt::white)); + QCOMPARE(theme->lightStrength(), 5.0f); + QCOMPARE(theme->multiHighlightColor(), QColor(QRgb(0xd72222))); + QCOMPARE(theme->multiHighlightGradient().stops().at(1).second, QColor(QRgb(0xd72222))); + QCOMPARE(theme->singleHighlightColor(), QColor(QRgb(0xf5dc0d))); + QCOMPARE(theme->singleHighlightGradient().stops().at(1).second, QColor(QRgb(0xf5dc0d))); + QCOMPARE(theme->type(), Q3DTheme::ThemeEbony); + QCOMPARE(theme->windowColor(), QColor(Qt::black)); + delete theme; +} + +void tst_theme::initialProperties() +{ + QVERIFY(m_theme); + + QCOMPARE(m_theme->ambientLightStrength(), 0.25f); + QCOMPARE(m_theme->backgroundColor(), QColor(Qt::black)); + QCOMPARE(m_theme->isBackgroundEnabled(), true); + QCOMPARE(m_theme->baseColors().length(), 1); + QCOMPARE(m_theme->baseColors().at(0), QColor(Qt::black)); + QCOMPARE(m_theme->baseGradients().length(), 1); + QCOMPARE(m_theme->baseGradients().at(0).stops().at(0).second, QColor(Qt::black)); + QCOMPARE(m_theme->baseGradients().at(0).stops().at(1).second, QColor(Qt::white)); + QCOMPARE(m_theme->colorStyle(), Q3DTheme::ColorStyleUniform); + QCOMPARE(m_theme->font(), QFont()); + QCOMPARE(m_theme->isGridEnabled(), true); + QCOMPARE(m_theme->gridLineColor(), QColor(Qt::white)); + QCOMPARE(m_theme->highlightLightStrength(), 7.5f); + QCOMPARE(m_theme->labelBackgroundColor(), QColor(Qt::gray)); + QCOMPARE(m_theme->isLabelBackgroundEnabled(), true); + QCOMPARE(m_theme->isLabelBorderEnabled(), true); + QCOMPARE(m_theme->labelTextColor(), QColor(Qt::white)); + QCOMPARE(m_theme->lightColor(), QColor(Qt::white)); + QCOMPARE(m_theme->lightStrength(), 5.0f); + QCOMPARE(m_theme->multiHighlightColor(), QColor(Qt::blue)); + QCOMPARE(m_theme->multiHighlightGradient().stops(), QLinearGradient().stops()); + QCOMPARE(m_theme->singleHighlightColor(), QColor(Qt::red)); + QCOMPARE(m_theme->singleHighlightGradient().stops(), QLinearGradient().stops()); + QCOMPARE(m_theme->type(), Q3DTheme::ThemeUserDefined); + QCOMPARE(m_theme->windowColor(), QColor(Qt::black)); +} + +void tst_theme::initializeProperties() +{ + QVERIFY(m_theme); + + QLinearGradient gradient1; + QLinearGradient gradient2; + QLinearGradient gradient3; + QLinearGradient gradient4; + + QList<QColor> basecolors; + basecolors << QColor(Qt::red) << QColor(Qt::blue); + + QList<QLinearGradient> basegradients; + basegradients << gradient1 << gradient2; + + m_theme->setType(Q3DTheme::ThemeQt); // We'll override default values with the following setters + m_theme->setAmbientLightStrength(0.3f); + m_theme->setBackgroundColor(QColor(Qt::red)); + m_theme->setBackgroundEnabled(false); + m_theme->setBaseColors(basecolors); + m_theme->setBaseGradients(basegradients); + m_theme->setColorStyle(Q3DTheme::ColorStyleRangeGradient); + m_theme->setFont(QFont("Arial")); + m_theme->setGridEnabled(false); + m_theme->setGridLineColor(QColor(Qt::green)); + m_theme->setHighlightLightStrength(5.0f); + m_theme->setLabelBackgroundColor(QColor(Qt::gray)); + m_theme->setLabelBackgroundEnabled(false); + m_theme->setLabelBorderEnabled(false); + m_theme->setLabelTextColor(QColor(Qt::cyan)); + m_theme->setLightColor(QColor(Qt::yellow)); + m_theme->setLightStrength(2.5f); + m_theme->setMultiHighlightColor(QColor(Qt::darkBlue)); + m_theme->setMultiHighlightGradient(gradient3); + m_theme->setSingleHighlightColor(QColor(Qt::darkRed)); + m_theme->setSingleHighlightGradient(gradient4); + m_theme->setWindowColor(QColor(Qt::darkYellow)); + + QCOMPARE(m_theme->ambientLightStrength(), 0.3f); + QCOMPARE(m_theme->backgroundColor(), QColor(Qt::red)); + QCOMPARE(m_theme->isBackgroundEnabled(), false); + QCOMPARE(m_theme->baseColors().length(), 2); + QCOMPARE(m_theme->baseColors().at(0), QColor(Qt::red)); + QCOMPARE(m_theme->baseColors().at(1), QColor(Qt::blue)); + QCOMPARE(m_theme->baseGradients().length(), 2); + QCOMPARE(m_theme->baseGradients().at(0), gradient1); + QCOMPARE(m_theme->baseGradients().at(0), gradient2); + QCOMPARE(m_theme->colorStyle(), Q3DTheme::ColorStyleRangeGradient); + QCOMPARE(m_theme->font(), QFont("Arial")); + QCOMPARE(m_theme->isGridEnabled(), false); + QCOMPARE(m_theme->gridLineColor(), QColor(Qt::green)); + QCOMPARE(m_theme->highlightLightStrength(), 5.0f); + QCOMPARE(m_theme->labelBackgroundColor(), QColor(Qt::gray)); + QCOMPARE(m_theme->isLabelBackgroundEnabled(), false); + QCOMPARE(m_theme->isLabelBorderEnabled(), false); + QCOMPARE(m_theme->labelTextColor(), QColor(Qt::cyan)); + QCOMPARE(m_theme->lightColor(), QColor(Qt::yellow)); + QCOMPARE(m_theme->lightStrength(), 2.5f); + QCOMPARE(m_theme->multiHighlightColor(), QColor(Qt::darkBlue)); + QCOMPARE(m_theme->multiHighlightGradient(), gradient3); + QCOMPARE(m_theme->singleHighlightColor(), QColor(Qt::darkRed)); + QCOMPARE(m_theme->singleHighlightGradient(), gradient4); + QCOMPARE(m_theme->type(), Q3DTheme::ThemeQt); + QCOMPARE(m_theme->windowColor(), QColor(Qt::darkYellow)); +} + +void tst_theme::invalidProperties() +{ + m_theme->setAmbientLightStrength(-1.0f); + QCOMPARE(m_theme->ambientLightStrength(), 0.25f); + m_theme->setAmbientLightStrength(1.1f); + QCOMPARE(m_theme->ambientLightStrength(), 0.25f); + + m_theme->setHighlightLightStrength(-1.0f); + QCOMPARE(m_theme->highlightLightStrength(), 7.5f); + m_theme->setHighlightLightStrength(10.1f); + QCOMPARE(m_theme->highlightLightStrength(), 7.5f); + + m_theme->setLightStrength(-1.0f); + QCOMPARE(m_theme->lightStrength(), 5.0f); + m_theme->setLightStrength(10.1f); + QCOMPARE(m_theme->lightStrength(), 5.0f); +} + +QTEST_MAIN(tst_theme) +#include "tst_theme.moc" diff --git a/tests/auto/qmltest/axis3d/tst_category.qml b/tests/auto/qmltest/axis3d/tst_category.qml new file mode 100644 index 00000000..318fa011 --- /dev/null +++ b/tests/auto/qmltest/axis3d/tst_category.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + CategoryAxis3D { + id: initial + } + + CategoryAxis3D { + id: initialized + labels: ["first", "second"] + + autoAdjustRange: false + labelAutoRotation: 10.0 + max: 20 + min: 10 + title: "initialized" + titleFixed: false + titleVisible: true + } + + CategoryAxis3D { + id: change + } + + CategoryAxis3D { + id: invalid + } + + TestCase { + name: "CategoryAxis3D Initial" + + function test_initial() { + compare(initial.labels.length, 0) + + compare(initial.autoAdjustRange, true) + compare(initial.labelAutoRotation, 0.0) + compare(initial.max, 10) + compare(initial.min, 0) + compare(initial.orientation, AbstractAxis3D.AxisOrientationNone) + compare(initial.title, "") + compare(initial.titleFixed, true) + compare(initial.titleVisible, false) + compare(initial.type, AbstractAxis3D.AxisTypeCategory) + } + } + + TestCase { + name: "CategoryAxis3D Initialized" + + function test_initialized() { + compare(initialized.labels.length, 2) + compare(initialized.labels[0], "first") + compare(initialized.labels[1], "second") + + compare(initialized.autoAdjustRange, false) + compare(initialized.labelAutoRotation, 10.0) + compare(initialized.max, 20) + compare(initialized.min, 10) + compare(initialized.title, "initialized") + compare(initialized.titleFixed, false) + compare(initialized.titleVisible, true) + } + } + + TestCase { + name: "CategoryAxis3D Change" + + function test_change() { + change.labels = ["first"] + compare(change.labels.length, 1) + compare(change.labels[0], "first") + change.labels = ["first", "second"] + compare(change.labels.length, 2) + compare(change.labels[0], "first") + compare(change.labels[1], "second") + change.labels[1] = "another" + compare(change.labels[1], "another") + + change.autoAdjustRange = false + change.labelAutoRotation = 10.0 + change.max = 20 + change.min = 10 + change.title = "initialized" + change.titleFixed = false + change.titleVisible = true + + compare(change.autoAdjustRange, false) + compare(change.labelAutoRotation, 10.0) + compare(change.max, 20) + compare(change.min, 10) + compare(change.title, "initialized") + compare(change.titleFixed, false) + compare(change.titleVisible, true) + } + } + + TestCase { + name: "CategoryAxis3D Invalid" + + function test_invalid() { + invalid.labelAutoRotation = -10 + compare(invalid.labelAutoRotation, 0.0) + invalid.labelAutoRotation = 100 + compare(invalid.labelAutoRotation, 90.0) + invalid.max = -10 + compare(invalid.min, 0) + invalid.min = 10 + compare(invalid.max, 11) + } + } +} diff --git a/tests/auto/qmltest/axis3d/tst_logvalue.qml b/tests/auto/qmltest/axis3d/tst_logvalue.qml new file mode 100644 index 00000000..89228ad1 --- /dev/null +++ b/tests/auto/qmltest/axis3d/tst_logvalue.qml @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + LogValueAxis3DFormatter { + id: initial + } + + LogValueAxis3DFormatter { + id: initialized + autoSubGrid: false + base: 0.1 + showEdgeLabels: false + } + + LogValueAxis3DFormatter { + id: change + } + + LogValueAxis3DFormatter { + id: invalid + } + + TestCase { + name: "LogValueAxis3DFormatter Initial" + + function test_initial() { + compare(initial.autoSubGrid, true) + compare(initial.base, 10) + compare(initial.showEdgeLabels, true) + } + } + + TestCase { + name: "LogValueAxis3DFormatter Initialized" + + function test_initialized() { + compare(initialized.autoSubGrid, false) + compare(initialized.base, 0.1) + compare(initialized.showEdgeLabels, false) + } + } + + TestCase { + name: "LogValueAxis3DFormatter Change" + + function test_change() { + change.autoSubGrid = false + change.base = 0.1 + change.showEdgeLabels = false + + compare(change.autoSubGrid, false) + compare(change.base, 0.1) + compare(change.showEdgeLabels, false) + } + } + + TestCase { + name: "LogValueAxis3DFormatter Invalid" + + function test_invalid() { + invalid.base = 1 + compare(invalid.base, 10) + invalid.base = -1 + compare(invalid.base, 10) + } + } +} diff --git a/tests/auto/qmltest/axis3d/tst_value.qml b/tests/auto/qmltest/axis3d/tst_value.qml new file mode 100644 index 00000000..54189c8b --- /dev/null +++ b/tests/auto/qmltest/axis3d/tst_value.qml @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + ValueAxis3D { + id: initial + } + + ValueAxis3D { + id: initialized + formatter: ValueAxis3DFormatter { objectName: "formatter1" } + labelFormat: "%f" + reversed: true + segmentCount: 10 + subSegmentCount: 5 + + autoAdjustRange: false + labelAutoRotation: 10.0 + max: 20 + min: -10 + title: "initialized" + titleFixed: false + titleVisible: true + } + + ValueAxis3D { + id: change + } + + ValueAxis3D { + id: invalid + } + + TestCase { + name: "ValueAxis3D Initial" + + function test_initial() { + verify(initial.formatter) + compare(initial.labelFormat, "%.2f") + compare(initial.reversed, false) + compare(initial.segmentCount, 5) + compare(initial.subSegmentCount, 1) + + compare(initial.autoAdjustRange, true) + compare(initial.labelAutoRotation, 0.0) + compare(initial.max, 10) + compare(initial.min, 0) + compare(initial.orientation, AbstractAxis3D.AxisOrientationNone) + compare(initial.title, "") + compare(initial.titleFixed, true) + compare(initial.titleVisible, false) + compare(initial.type, AbstractAxis3D.AxisTypeValue) + } + } + + TestCase { + name: "ValueAxis3D Initialized" + + function test_initialized() { + compare(initialized.formatter.objectName, "formatter1") + compare(initialized.labelFormat, "%f") + compare(initialized.reversed, true) + compare(initialized.segmentCount, 10) + compare(initialized.subSegmentCount, 5) + + compare(initialized.autoAdjustRange, false) + compare(initialized.labelAutoRotation, 10.0) + compare(initialized.max, 20) + compare(initialized.min, -10) + compare(initialized.title, "initialized") + compare(initialized.titleFixed, false) + compare(initialized.titleVisible, true) + } + } + + TestCase { + name: "ValueAxis3D Change" + + ValueAxis3DFormatter { id: formatter1 } + + function test_change() { + change.formatter = formatter1 + change.labelFormat = "%f" + change.reversed = true + change.segmentCount = 10 + change.subSegmentCount = 5 + + compare(change.formatter, formatter1) + compare(change.labelFormat, "%f") + compare(change.reversed, true) + compare(change.segmentCount, 10) + compare(change.subSegmentCount, 5) + + change.autoAdjustRange = false + change.labelAutoRotation = 10.0 + change.max = 20 + change.min = -10 + change.title = "initialized" + change.titleFixed = false + change.titleVisible = true + + compare(change.autoAdjustRange, false) + compare(change.labelAutoRotation, 10.0) + compare(change.max, 20) + compare(change.min, -10) + compare(change.title, "initialized") + compare(change.titleFixed, false) + compare(change.titleVisible, true) + } + } + + TestCase { + name: "ValueAxis3D Invalid" + + function test_invalid() { + invalid.segmentCount = -1 + compare(invalid.segmentCount, 1) + invalid.subSegmentCount = -1 + compare(invalid.subSegmentCount, 1) + + invalid.labelAutoRotation = -10 + compare(invalid.labelAutoRotation, 0.0) + invalid.labelAutoRotation = 100 + compare(invalid.labelAutoRotation, 90.0) + invalid.max = -10 + compare(invalid.min, -11) + invalid.min = 10 + compare(invalid.max, 11) + } + } +} diff --git a/tests/auto/qmltest/bars3d/tst_bars.qml b/tests/auto/qmltest/bars3d/tst_bars.qml new file mode 100644 index 00000000..a64aaf1a --- /dev/null +++ b/tests/auto/qmltest/bars3d/tst_bars.qml @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Bars3D { + id: series + anchors.fill: parent + } + + TestCase { + name: "Bars3D Series" + + Bar3DSeries { id: series1 } + Bar3DSeries { id: series2 } + + function test_1_add_series() { + series.seriesList = [series1, series2] + compare(series.seriesList.length, 2) + } + + function test_2_remove_series() { + series.seriesList = [series1] + compare(series.seriesList.length, 1) + } + + function test_3_remove_series() { + series.seriesList = [] + compare(series.seriesList.length, 0) + } + + function test_4_primary_series() { + series.seriesList = [series1, series2] + compare(series.primarySeries, series1) + series.primarySeries = series2 + compare(series.primarySeries, series2) + } + + function test_5_selected_series() { + series.seriesList[0].selectedBar = Qt.point(0, 0) + compare(series.selectedSeries, series1) + } + } + + // The following tests are not required for scatter or surface, as they are handled identically + Bars3D { + id: theme + anchors.fill: parent + } + + Bars3D { + id: input + anchors.fill: parent + } + + Custom3DItem { id: item1; meshFile: ":/customitem.obj" } + Custom3DItem { id: item2; meshFile: ":/customitem.obj" } + Custom3DItem { id: item3; meshFile: ":/customitem.obj" } + Custom3DItem { id: item4; meshFile: ":/customitem.obj"; position: Qt.vector3d(0.0, 1.0, 0.0) } + + Bars3D { + id: custom + anchors.fill: parent + customItemList: [item1, item2] + } + + TestCase { + name: "Bars3D Theme" + when: windowShown + + Theme3D { id: newTheme } + + function test_1_add_theme() { + theme.theme = newTheme + compare(theme.theme, newTheme) + } + + function test_2_change_theme() { + newTheme.type = Theme3D.ThemePrimaryColors + compare(theme.theme.type, Theme3D.ThemePrimaryColors) + } + } + + TestCase { + name: "Bars3D Input" + when: windowShown + + function test_1_remove_input() { + input.inputHandler = null + compare(input.inputHandler, null) + } + } + + TestCase { + name: "Bars3D Custom" + when: windowShown + + function test_1_custom_items() { + compare(custom.customItemList.length, 2) + } + + function test_2_add_custom_items() { + custom.addCustomItem(item3) + compare(custom.customItemList.length, 3) + custom.addCustomItem(item4) + compare(custom.customItemList.length, 4) + } + + function test_3_change_custom_items() { + item1.position = Qt.vector3d(1.0, 1.0, 1.0) + compare(custom.customItemList[0].position, Qt.vector3d(1.0, 1.0, 1.0)) + } + + function test_4_remove_custom_items() { + custom.removeCustomItemAt(Qt.vector3d(0.0, 1.0, 0.0)) + compare(custom.customItemList.length, 3) + custom.releaseCustomItem(item1) + compare(custom.customItemList[0], item2) + custom.releaseCustomItem(item2) + compare(custom.customItemList.length, 1) + custom.removeCustomItems() + compare(custom.customItemList.length, 0) + } + } +} diff --git a/tests/auto/qmltest/bars3d/tst_barseries.qml b/tests/auto/qmltest/bars3d/tst_barseries.qml new file mode 100644 index 00000000..7e303ab0 --- /dev/null +++ b/tests/auto/qmltest/bars3d/tst_barseries.qml @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Bar3DSeries { + id: initial + } + + ColorGradient { + id: gradient1; + stops: [ + ColorGradientStop { color: "red"; position: 0 }, + ColorGradientStop { color: "blue"; position: 1 } + ] + } + + ColorGradient { + id: gradient2; + stops: [ + ColorGradientStop { color: "green"; position: 0 }, + ColorGradientStop { color: "red"; position: 1 } + ] + } + + ColorGradient { + id: gradient3; + stops: [ + ColorGradientStop { color: "gray"; position: 0 }, + ColorGradientStop { color: "darkgray"; position: 1 } + ] + } + + Bar3DSeries { + id: initialized + dataProxy: ItemModelBarDataProxy { + itemModel: ListModel { + ListElement{ year: "2012"; city: "Oulu"; expenses: "4200"; } + ListElement{ year: "2012"; city: "Rauma"; expenses: "2100"; } + } + rowRole: "city" + columnRole: "year" + valueRole: "expenses" + } + meshAngle: 15.0 + selectedBar: Qt.point(0, 0) + + baseColor: "blue" + baseGradient: gradient1 + colorStyle: Theme3D.ColorStyleObjectGradient + itemLabelFormat: "%f" + itemLabelVisible: false + mesh: Abstract3DSeries.MeshCone + meshSmooth: true + multiHighlightColor: "green" + multiHighlightGradient: gradient2 + name: "series1" + singleHighlightColor: "red" + singleHighlightGradient: gradient3 + userDefinedMesh: ":/customitem.obj" + visible: false + } + + ItemModelBarDataProxy { + id: proxy1 + itemModel: ListModel { + ListElement{ year: "2012"; city: "Oulu"; expenses: "4200"; } + ListElement{ year: "2012"; city: "Rauma"; expenses: "2100"; } + ListElement{ year: "2012"; city: "Helsinki"; expenses: "7040"; } + } + rowRole: "city" + columnRole: "year" + valueRole: "expenses" + } + + Bar3DSeries { + id: change + } + + TestCase { + name: "Bar3DSeries Initial" + + function test_1_initial() { + compare(initial.dataProxy.rowCount, 0) + compare(initial.invalidSelectionPosition, Qt.point(-1, -1)) + compare(initial.meshAngle, 0) + compare(initial.selectedBar, Qt.point(-1, -1)) + } + + function test_2_initial_common() { + // Common properties + compare(initial.baseColor, "#000000") + compare(initial.baseGradient, null) + compare(initial.colorStyle, Theme3D.ColorStyleUniform) + compare(initial.itemLabel, "") + compare(initial.itemLabelFormat, "@valueLabel") + compare(initial.itemLabelVisible, true) + compare(initial.mesh, Abstract3DSeries.MeshBevelBar) + compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0)) + compare(initial.meshSmooth, false) + compare(initial.multiHighlightColor, "#000000") + compare(initial.multiHighlightGradient, null) + compare(initial.name, "") + compare(initial.singleHighlightColor, "#000000") + compare(initial.singleHighlightGradient, null) + compare(initial.type, Abstract3DSeries.SeriesTypeBar) + compare(initial.userDefinedMesh, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Bar3DSeries Initialized" + + function test_1_initialized() { + compare(initialized.dataProxy.rowCount, 2) + fuzzyCompare(initialized.meshAngle, 15.0, 0.01) + compare(initialized.selectedBar, Qt.point(0, 0)) + } + + function test_2_initialized_common() { + // Common properties + compare(initialized.baseColor, "#0000ff") + compare(initialized.baseGradient, gradient1) + compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(initialized.itemLabelFormat, "%f") + compare(initialized.itemLabelVisible, false) + compare(initialized.mesh, Abstract3DSeries.MeshCone) + compare(initialized.meshSmooth, true) + compare(initialized.multiHighlightColor, "#008000") + compare(initialized.multiHighlightGradient, gradient2) + compare(initialized.name, "series1") + compare(initialized.singleHighlightColor, "#ff0000") + compare(initialized.singleHighlightGradient, gradient3) + compare(initialized.userDefinedMesh, ":/customitem.obj") + compare(initialized.visible, false) + } + } + + TestCase { + name: "Bar3DSeries Change" + + function test_1_change() { + change.dataProxy = proxy1 + change.meshAngle = 15.0 + change.selectedBar = Qt.point(0, 0) + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.dataProxy.rowCount, 3) + fuzzyCompare(change.meshAngle, 15.0, 0.01) + compare(change.selectedBar, Qt.point(0, 0)) + } + + function test_3_change_common() { + change.baseColor = "blue" + change.baseGradient = gradient1 + change.colorStyle = Theme3D.ColorStyleObjectGradient + change.itemLabelFormat = "%f" + change.itemLabelVisible = false + change.mesh = Abstract3DSeries.MeshCone + change.meshSmooth = true + change.multiHighlightColor = "green" + change.multiHighlightGradient = gradient2 + change.name = "series1" + change.singleHighlightColor = "red" + change.singleHighlightGradient = gradient3 + change.userDefinedMesh = ":/customitem.obj" + change.visible = false + + compare(change.baseColor, "#0000ff") + compare(change.baseGradient, gradient1) + compare(change.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(change.itemLabelFormat, "%f") + compare(change.itemLabelVisible, false) + compare(change.mesh, Abstract3DSeries.MeshCone) + compare(change.meshSmooth, true) + compare(change.multiHighlightColor, "#008000") + compare(change.multiHighlightGradient, gradient2) + compare(change.name, "series1") + compare(change.singleHighlightColor, "#ff0000") + compare(change.singleHighlightGradient, gradient3) + compare(change.userDefinedMesh, ":/customitem.obj") + compare(change.visible, false) + } + + function test_4_change_gradient_stop() { + gradient1.stops[0].color = "yellow" + compare(change.baseGradient.stops[0].color, "#ffff00") + } + } +} diff --git a/tests/auto/qmltest/bars3d/tst_basic.qml b/tests/auto/qmltest/bars3d/tst_basic.qml new file mode 100644 index 00000000..bf27ae8c --- /dev/null +++ b/tests/auto/qmltest/bars3d/tst_basic.qml @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Bars3D { + id: empty + } + + Bars3D { + id: basic + anchors.fill: parent + multiSeriesUniform: true + barThickness: 0.1 + barSpacing.width: 0.1 + barSpacing.height: 0.1 + barSpacingRelative: false + floorLevel: 1.0 + } + + Bars3D { + id: common + anchors.fill: parent + } + + Bars3D { + id: common_init + anchors.fill: parent + selectionMode: AbstractGraph3D.SelectionNone + shadowQuality: AbstractGraph3D.ShadowQualityLow + msaaSamples: 2 + theme: Theme3D { } + renderingMode: AbstractGraph3D.RenderIndirect + measureFps: true + orthoProjection: false + aspectRatio: 3.0 + optimizationHints: AbstractGraph3D.OptimizationStatic + polar: false + radialLabelOffset: 2 + horizontalAspectRatio: 0.2 + reflection: true + reflectivity: 0.1 + locale: Qt.locale("UK") + margin: 0.2 + } + + TestCase { + name: "Bars3D Empty" + + function test_empty() { + compare(empty.width, 0, "width") + compare(empty.height, 0, "height") + compare(empty.multiSeriesUniform, false, "multiSeriesUniform") + compare(empty.barThickness, 1.0, "barThickness") + compare(empty.barSpacing, Qt.size(0.2, 0.2), "barSpacing") + compare(empty.barSpacingRelative, true, "barSpacingRelative") + compare(empty.seriesList.length, 0, "seriesList") + compare(empty.selectedSeries, null, "selectedSeries") + compare(empty.primarySeries, null, "primarySeries") + compare(empty.floorLevel, 0.0, "floorLevel") + compare(empty.columnAxis.orientation, AbstractAxis3D.AxisOrientationX) + compare(empty.rowAxis.orientation, AbstractAxis3D.AxisOrientationZ) + compare(empty.valueAxis.orientation, AbstractAxis3D.AxisOrientationY) + compare(empty.columnAxis.type, AbstractAxis3D.AxisTypeCategory) + compare(empty.rowAxis.type, AbstractAxis3D.AxisTypeCategory) + compare(empty.valueAxis.type, AbstractAxis3D.AxisTypeValue) + } + } + + TestCase { + name: "Bars3D Basic" + when: windowShown + + function test_basic() { + compare(basic.width, 150, "width") + compare(basic.height, 150, "height") + compare(basic.multiSeriesUniform, true, "multiSeriesUniform") + compare(basic.barThickness, 0.1, "barThickness") + compare(basic.barSpacing, Qt.size(0.1, 0.1), "barSpacing") + compare(basic.barSpacingRelative, false, "barSpacingRelative") + compare(basic.floorLevel, 1.0, "floorLevel") + } + + function test_change_basic() { + basic.multiSeriesUniform = false + basic.barThickness = 0.5 + basic.barSpacing = Qt.size(1.0, 0.0) + basic.barSpacingRelative = true + basic.floorLevel = 0.2 + compare(basic.multiSeriesUniform, false, "multiSeriesUniform") + compare(basic.barThickness, 0.5, "barThickness") + compare(basic.barSpacing, Qt.size(1.0, 0.0), "barSpacing") + compare(basic.barSpacingRelative, true, "barSpacingRelative") + compare(basic.floorLevel, 0.2, "floorLevel") + } + + function test_change_invalid_basic() { + basic.barThickness = -1 + basic.barSpacing = Qt.size(-1.0, -1.0) + compare(basic.barThickness, -1/*0.5*/, "barThickness") // TODO: Fix once QTRD-3367 is done + compare(basic.barSpacing, Qt.size(1.0, 0.0), "barSpacing") + } + } + + TestCase { + name: "Bars3D Common" + when: windowShown + + function test_1_common() { + compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality") + if (common.shadowsSupported === true) + compare(common.msaaSamples, 4, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + compare(common.theme.type, Theme3D.ThemeQt, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common.measureFps, false, "measureFps") + compare(common.customItemList.length, 0, "customItemList") + compare(common.orthoProjection, false, "orthoProjection") + compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement") + compare(common.aspectRatio, 2.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints") + compare(common.polar, false, "polar") + compare(common.radialLabelOffset, 1, "radialLabelOffset") + compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio") + compare(common.reflection, false, "reflection") + compare(common.reflectivity, 0.5, "reflectivity") + compare(common.locale, Qt.locale("C"), "locale") + compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition") + compare(common.margin, -1, "margin") + } + + function test_2_change_common() { + common.selectionMode = AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice + common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh + compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality") + common.msaaSamples = 8 + if (common.shadowsSupported === true) + compare(common.msaaSamples, 8, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + common.theme.type = Theme3D.ThemeRetro + common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear + common.measureFps = true + common.orthoProjection = true + common.aspectRatio = 1.0 + common.optimizationHints = AbstractGraph3D.OptimizationStatic + common.polar = true + common.radialLabelOffset = 2 + common.horizontalAspectRatio = 1 + common.reflection = true + common.reflectivity = 1.0 + common.locale = Qt.locale("FI") + common.margin = 1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows + compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero + compare(common.theme.type, Theme3D.ThemeRetro, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode") + compare(common.measureFps, true, "measureFps") + compare(common.orthoProjection, true, "orthoProjection") + compare(common.aspectRatio, 1.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common.polar, true, "polar") + compare(common.radialLabelOffset, 2, "radialLabelOffset") + compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio") + compare(common.reflection, true, "reflection") + compare(common.reflectivity, 1.0, "reflectivity") + compare(common.locale, Qt.locale("FI"), "locale") + compare(common.margin, 1.0, "margin") + } + + function test_3_change_invalid_common() { + common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice + common.theme.type = -2 + common.renderingMode = -1 + common.measureFps = false + common.orthoProjection = false + common.aspectRatio = -1.0 + common.polar = false + common.horizontalAspectRatio = -2 + common.reflection = false + common.reflectivity = -1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode") + compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done + compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done + compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done + } + + function test_4_common_initialized() { + compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode") + if (common_init.shadowsSupported === true) { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityLow, "shadowQuality") + compare(common_init.msaaSamples, 2, "msaaSamples") + } else { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") + compare(common_init.msaaSamples, 0, "msaaSamples") + } + compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme") + compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common_init.measureFps, true, "measureFps") + compare(common_init.customItemList.length, 0, "customItemList") + compare(common_init.orthoProjection, false, "orthoProjection") + compare(common_init.aspectRatio, 3.0, "aspectRatio") + compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common_init.polar, false, "polar") + compare(common_init.radialLabelOffset, 2, "radialLabelOffset") + compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio") + compare(common_init.reflection, true, "reflection") + compare(common_init.reflectivity, 0.1, "reflectivity") + compare(common_init.locale, Qt.locale("UK"), "locale") + compare(common_init.margin, 0.2, "margin") + } + } +} diff --git a/tests/auto/qmltest/bars3d/tst_proxy.qml b/tests/auto/qmltest/bars3d/tst_proxy.qml new file mode 100644 index 00000000..8d91c055 --- /dev/null +++ b/tests/auto/qmltest/bars3d/tst_proxy.qml @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + ItemModelBarDataProxy { + id: initial + } + + ItemModelBarDataProxy { + id: initialized + + autoColumnCategories: false + autoRowCategories: false + columnCategories: ["colcat1", "colcat2"] + columnRole: "col" + columnRolePattern: /^.*-(\d\d)$/ + columnRoleReplace: "\\1" + itemModel: ListModel { objectName: "model1" } + multiMatchBehavior: ItemModelBarDataProxy.MMBAverage + rotationRole: "rot" + rotationRolePattern: /-/ + rotationRoleReplace: "\\1" + rowCategories: ["rowcat1", "rowcat2"] + rowRole: "row" + rowRolePattern: /^(\d\d\d\d).*$/ + rowRoleReplace: "\\1" + valueRole: "val" + valueRolePattern: /-/ + valueRoleReplace: "\\1" + + columnLabels: ["col1", "col2"] + rowLabels: ["row1", "row2"] + } + + ItemModelBarDataProxy { + id: change + } + + TestCase { + name: "ItemModelBarDataProxy Initial" + + function test_initial() { + compare(initial.autoColumnCategories, true) + compare(initial.autoRowCategories, true) + compare(initial.columnCategories, []) + compare(initial.columnRole, "") + verify(initial.columnRolePattern) + compare(initial.columnRoleReplace, "") + verify(!initial.itemModel) + compare(initial.multiMatchBehavior, ItemModelBarDataProxy.MMBLast) + compare(initial.rotationRole, "") + verify(initial.rotationRolePattern) + compare(initial.rotationRoleReplace, "") + compare(initial.rowCategories, []) + compare(initial.rowRole, "") + verify(initial.rowRolePattern) + compare(initial.rowRoleReplace, "") + compare(initial.useModelCategories, false) + compare(initial.valueRole, "") + verify(initial.valueRolePattern) + compare(initial.valueRoleReplace, "") + + compare(initial.columnLabels.length, 0) + compare(initial.rowCount, 0) + compare(initial.rowLabels.length, 0) + verify(!initial.series) + + compare(initial.type, AbstractDataProxy.DataTypeBar) + } + } + + TestCase { + name: "ItemModelBarDataProxy Initialized" + + function test_initialized() { + compare(initialized.autoColumnCategories, false) + compare(initialized.autoRowCategories, false) + compare(initialized.columnCategories.length, 2) + compare(initialized.columnCategories[0], "colcat1") + compare(initialized.columnCategories[1], "colcat2") + compare(initialized.columnRole, "col") + compare(initialized.columnRolePattern, /^.*-(\d\d)$/) + compare(initialized.columnRoleReplace, "\\1") + compare(initialized.itemModel.objectName, "model1") + compare(initialized.multiMatchBehavior, ItemModelBarDataProxy.MMBAverage) + compare(initialized.rotationRole, "rot") + compare(initialized.rotationRolePattern, /-/) + compare(initialized.rotationRoleReplace, "\\1") + compare(initialized.rowCategories.length, 2) + compare(initialized.rowCategories[0], "rowcat1") + compare(initialized.rowCategories[1], "rowcat2") + compare(initialized.rowRole, "row") + compare(initialized.rowRolePattern, /^(\d\d\d\d).*$/) + compare(initialized.rowRoleReplace, "\\1") + compare(initialized.valueRole, "val") + compare(initialized.valueRolePattern, /-/) + compare(initialized.valueRoleReplace, "\\1") + + compare(initialized.columnLabels.length, 2) + compare(initialized.rowCount, 2) + compare(initialized.rowLabels.length, 2) + } + } + + TestCase { + name: "ItemModelBarDataProxy Change" + + ListModel { id: model1; objectName: "model1" } + + function test_1_change() { + change.autoColumnCategories = false + change.autoRowCategories = false + change.columnCategories = ["colcat1", "colcat2"] + change.columnRole = "col" + change.columnRolePattern = /^.*-(\d\d)$/ + change.columnRoleReplace = "\\1" + change.itemModel = model1 + change.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage + change.rotationRole = "rot" + change.rotationRolePattern = /-/ + change.rotationRoleReplace = "\\1" + change.rowCategories = ["rowcat1", "rowcat2"] + change.rowRole = "row" + change.rowRolePattern = /^(\d\d\d\d).*$/ + change.rowRoleReplace = "\\1" + change.useModelCategories = true // Overwrites columnLabels and rowLabels + change.valueRole = "val" + change.valueRolePattern = /-/ + change.valueRoleReplace = "\\1" + + change.columnLabels = ["col1", "col2"] + change.rowLabels = ["row1", "row2"] + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.autoColumnCategories, false) + compare(change.autoRowCategories, false) + compare(change.columnCategories.length, 2) + compare(change.columnCategories[0], "colcat1") + compare(change.columnCategories[1], "colcat2") + compare(change.columnRole, "col") + compare(change.columnRolePattern, /^.*-(\d\d)$/) + compare(change.columnRoleReplace, "\\1") + compare(change.itemModel.objectName, "model1") + compare(change.multiMatchBehavior, ItemModelBarDataProxy.MMBAverage) + compare(change.rotationRole, "rot") + compare(change.rotationRolePattern, /-/) + compare(change.rotationRoleReplace, "\\1") + compare(change.rowCategories.length, 2) + compare(change.rowCategories[0], "rowcat1") + compare(change.rowCategories[1], "rowcat2") + compare(change.rowRole, "row") + compare(change.rowRolePattern, /^(\d\d\d\d).*$/) + compare(change.rowRoleReplace, "\\1") + compare(change.useModelCategories, true) + compare(change.valueRole, "val") + compare(change.valueRolePattern, /-/) + compare(change.valueRoleReplace, "\\1") + + compare(change.columnLabels.length, 1) + compare(change.rowCount, 0) + compare(change.rowLabels.length, 0) + } + } + + TestCase { + name: "ItemModelBarDataProxy MultiMatchBehaviour" + + Bars3D { + id: bars1 + + Bar3DSeries { + ItemModelBarDataProxy { + id: barProxy + itemModel: ListModel { + ListElement{ coords: "0,0"; data: "5"; } + ListElement{ coords: "0,0"; data: "15"; } + } + rowRole: "coords" + columnRole: "coords" + valueRole: "data" + rowRolePattern: /(\d),\d/ + columnRolePattern: /(\d),(\d)/ + rowRoleReplace: "\\1" + columnRoleReplace: "\\2" + } + } + } + + function test_0_async_dummy() { + } + + function test_1_test_multimatch() { + compare(bars1.valueAxis.max, 15) + } + + function test_2_multimatch() { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBFirst + } + + function test_3_test_multimatch() { + compare(bars1.valueAxis.max, 5) + } + + function test_4_multimatch() { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBLast + } + + function test_5_test_multimatch() { + compare(bars1.valueAxis.max, 15) + } + + function test_6_multimatch() { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage + } + + function test_7_test_multimatch() { + compare(bars1.valueAxis.max, 10) + } + + function test_8_multimatch() { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBCumulative + } + + function test_9_test_multimatch() { + compare(bars1.valueAxis.max, 20) + } + } +} diff --git a/tests/auto/qmltest/custom3d/tst_customitem.qml b/tests/auto/qmltest/custom3d/tst_customitem.qml new file mode 100644 index 00000000..ee3d10fc --- /dev/null +++ b/tests/auto/qmltest/custom3d/tst_customitem.qml @@ -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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + Custom3DItem { + id: initial + } + + Custom3DItem { + id: initialized + meshFile: ":\customitem.obj" + position: Qt.vector3d(1.0, 0.5, 1.0) + positionAbsolute: true + rotation: Qt.quaternion(1, 0.5, 0, 0) + scaling: Qt.vector3d(0.2, 0.2, 0.2) + scalingAbsolute: false + shadowCasting: false + textureFile: ":\customtexture.jpg" + visible: false + } + + Custom3DItem { + id: change + } + + TestCase { + name: "Custom3DItem Initial" + + function test_initial() { + compare(initial.meshFile, "") + compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0)) + compare(initial.positionAbsolute, false) + compare(initial.rotation, Qt.quaternion(0, 0, 0, 0)) + compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1)) + compare(initial.scalingAbsolute, true) + compare(initial.shadowCasting, true) + compare(initial.textureFile, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Custom3DItem Initialized" + + function test_initialized() { + compare(initialized.meshFile, ":\customitem.obj") + compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(initialized.positionAbsolute, true) + compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(initialized.scalingAbsolute, false) + compare(initialized.shadowCasting, false) + compare(initialized.textureFile, ":\customtexture.jpg") + compare(initialized.visible, false) + } + } + + TestCase { + name: "Custom3DItem Change" + + function test_change() { + change.meshFile = ":\customitem.obj" + change.position = Qt.vector3d(1.0, 0.5, 1.0) + change.positionAbsolute = true + change.rotation = Qt.quaternion(1, 0.5, 0, 0) + change.scaling = Qt.vector3d(0.2, 0.2, 0.2) + change.scalingAbsolute = false + change.shadowCasting = false + change.textureFile = ":\customtexture.jpg" + change.visible = false + + compare(change.meshFile, ":\customitem.obj") + compare(change.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(change.positionAbsolute, true) + compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(change.scalingAbsolute, false) + compare(change.shadowCasting, false) + compare(change.textureFile, ":\customtexture.jpg") + compare(change.visible, false) + } + } +} diff --git a/tests/auto/qmltest/custom3d/tst_customlabel.qml b/tests/auto/qmltest/custom3d/tst_customlabel.qml new file mode 100644 index 00000000..acac1b63 --- /dev/null +++ b/tests/auto/qmltest/custom3d/tst_customlabel.qml @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + Custom3DLabel { + id: initial + } + + Custom3DLabel { + id: initialized + backgroundColor: "red" + backgroundEnabled: false + borderEnabled: false + facingCamera: true + font.family: "Times New Roman" + text: "test label" + textColor: "blue" + + position: Qt.vector3d(1.0, 0.5, 1.0) + positionAbsolute: true + rotation: Qt.quaternion(1, 0.5, 0, 0) + scaling: Qt.vector3d(0.2, 0.2, 0.2) + shadowCasting: true + visible: false + } + + Custom3DLabel { + id: change + } + + TestCase { + name: "Custom3DLabel Initial" + + function test_initial() { + compare(initial.backgroundColor, "#a0a0a4") + compare(initial.backgroundEnabled, true) + compare(initial.borderEnabled, true) + compare(initial.facingCamera, false) + compare(initial.font.family, "Arial") + compare(initial.text, "") + compare(initial.textColor, "#ffffff") + + compare(initial.meshFile, ":/defaultMeshes/plane") + compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0)) + compare(initial.positionAbsolute, false) + compare(initial.rotation, Qt.quaternion(0, 0, 0, 0)) + compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1)) + compare(initial.scalingAbsolute, true) + compare(initial.shadowCasting, false) + compare(initial.textureFile, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Custom3DLabel Initialized" + + function test_initialized() { + compare(initialized.backgroundColor, "#ff0000") + compare(initialized.backgroundEnabled, false) + compare(initialized.borderEnabled, false) + compare(initialized.facingCamera, true) + compare(initialized.font.family, "Times New Roman") + compare(initialized.text, "test label") + compare(initialized.textColor, "#0000ff") + + compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(initialized.positionAbsolute, true) + compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(initialized.shadowCasting, true) + compare(initialized.visible, false) + } + } + + TestCase { + name: "Custom3DLabel Change" + + function test_change() { + change.backgroundColor = "red" + change.backgroundEnabled = false + change.borderEnabled = false + change.facingCamera = true + change.font.family = "Times New Roman" + change.text = "test label" + change.textColor = "blue" + + change.position = Qt.vector3d(1.0, 0.5, 1.0) + change.positionAbsolute = true + change.rotation = Qt.quaternion(1, 0.5, 0, 0) + change.scaling = Qt.vector3d(0.2, 0.2, 0.2) + change.shadowCasting = true + change.visible = false + + compare(change.backgroundColor, "#ff0000") + compare(change.backgroundEnabled, false) + compare(change.borderEnabled, false) + compare(change.facingCamera, true) + compare(change.font.family, "Times New Roman") + compare(change.text, "test label") + compare(change.textColor, "#0000ff") + + compare(change.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(change.positionAbsolute, true) + compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(change.shadowCasting, true) + compare(change.visible, false) + } + } +} diff --git a/tests/auto/qmltest/custom3d/tst_customvolume.qml b/tests/auto/qmltest/custom3d/tst_customvolume.qml new file mode 100644 index 00000000..08c15013 --- /dev/null +++ b/tests/auto/qmltest/custom3d/tst_customvolume.qml @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + Custom3DVolume { + id: initial + } + + Custom3DVolume { + id: initialized + alphaMultiplier: 0.1 + drawSliceFrames: true + drawSlices: true + preserveOpacity: false + sliceFrameColor: "red" + sliceFrameGaps: Qt.vector3d(2.0, 2.0, 2.0) + sliceFrameThicknesses: Qt.vector3d(2.0, 2.0, 2.0) + sliceFrameWidths: Qt.vector3d(2.0, 2.0, 2.0) + sliceIndexX: 0 + sliceIndexY: 0 + sliceIndexZ: 0 + useHighDefShader: false + + position: Qt.vector3d(1.0, 0.5, 1.0) + positionAbsolute: true + rotation: Qt.quaternion(1, 0.5, 0, 0) + scaling: Qt.vector3d(0.2, 0.2, 0.2) + scalingAbsolute: false + shadowCasting: false + visible: false + } + + Custom3DVolume { + id: change + } + + Custom3DVolume { + id: invalid + } + + TestCase { + name: "Custom3DVolume Initial" + + function test_initial() { + compare(initial.alphaMultiplier, 1.0) + compare(initial.drawSliceFrames, false) + compare(initial.drawSlices, false) + compare(initial.preserveOpacity, true) + compare(initial.sliceFrameColor, "#000000") + compare(initial.sliceFrameGaps, Qt.vector3d(0.01, 0.01, 0.01)) + compare(initial.sliceFrameThicknesses, Qt.vector3d(0.01, 0.01, 0.01)) + compare(initial.sliceFrameWidths, Qt.vector3d(0.01, 0.01, 0.01)) + compare(initial.sliceIndexX, -1) + compare(initial.sliceIndexY, -1) + compare(initial.sliceIndexZ, -1) + compare(initial.useHighDefShader, true) + + compare(initial.meshFile, ":/defaultMeshes/barFull") + compare(initial.position, Qt.vector3d(0.0, 0.0, 0.0)) + compare(initial.positionAbsolute, false) + compare(initial.rotation, Qt.quaternion(0, 0, 0, 0)) + compare(initial.scaling, Qt.vector3d(0.1, 0.1, 0.1)) + compare(initial.scalingAbsolute, true) + compare(initial.shadowCasting, true) + compare(initial.textureFile, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Custom3DVolume Initialized" + + function test_initialized() { + compare(initialized.alphaMultiplier, 0.1) + compare(initialized.drawSliceFrames, true) + compare(initialized.drawSlices, true) + compare(initialized.preserveOpacity, false) + compare(initialized.sliceFrameColor, "#ff0000") + compare(initialized.sliceFrameGaps, Qt.vector3d(2.0, 2.0, 2.0)) + compare(initialized.sliceFrameThicknesses, Qt.vector3d(2.0, 2.0, 2.0)) + compare(initialized.sliceFrameWidths, Qt.vector3d(2.0, 2.0, 2.0)) + compare(initialized.sliceIndexX, 0) + compare(initialized.sliceIndexY, 0) + compare(initialized.sliceIndexZ, 0) + compare(initialized.useHighDefShader, false) + + compare(initialized.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(initialized.positionAbsolute, true) + compare(initialized.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(initialized.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(initialized.scalingAbsolute, false) + compare(initialized.shadowCasting, false) + compare(initialized.visible, false) + } + } + + TestCase { + name: "Custom3DVolume Change" + + function test_change() { + change.alphaMultiplier = 0.1 + change.drawSliceFrames = true + change.drawSlices = true + change.preserveOpacity = false + change.sliceFrameColor = "red" + change.sliceFrameGaps = Qt.vector3d(2.0, 2.0, 2.0) + change.sliceFrameThicknesses = Qt.vector3d(2.0, 2.0, 2.0) + change.sliceFrameWidths = Qt.vector3d(2.0, 2.0, 2.0) + change.sliceIndexX = 0 + change.sliceIndexY = 0 + change.sliceIndexZ = 0 + change.useHighDefShader = false + + change.position = Qt.vector3d(1.0, 0.5, 1.0) + change.positionAbsolute = true + change.rotation = Qt.quaternion(1, 0.5, 0, 0) + change.scaling = Qt.vector3d(0.2, 0.2, 0.2) + change.scalingAbsolute = false + change.shadowCasting = false + change.visible = false + + compare(change.alphaMultiplier, 0.1) + compare(change.drawSliceFrames, true) + compare(change.drawSlices, true) + compare(change.preserveOpacity, false) + compare(change.sliceFrameColor, "#ff0000") + compare(change.sliceFrameGaps, Qt.vector3d(2.0, 2.0, 2.0)) + compare(change.sliceFrameThicknesses, Qt.vector3d(2.0, 2.0, 2.0)) + compare(change.sliceFrameWidths, Qt.vector3d(2.0, 2.0, 2.0)) + compare(change.sliceIndexX, 0) + compare(change.sliceIndexY, 0) + compare(change.sliceIndexZ, 0) + compare(change.useHighDefShader, false) + + compare(change.position, Qt.vector3d(1.0, 0.5, 1.0)) + compare(change.positionAbsolute, true) + compare(change.rotation, Qt.quaternion(1, 0.5, 0, 0)) + compare(change.scaling, Qt.vector3d(0.2, 0.2, 0.2)) + compare(change.scalingAbsolute, false) + compare(change.shadowCasting, false) + compare(change.visible, false) + } + } + + TestCase { + name: "Custom3DVolume Invalid" + + function test_invalid() { + invalid.alphaMultiplier = -1.0 + compare(invalid.alphaMultiplier, 1.0) + invalid.sliceFrameGaps = Qt.vector3d(-0.1, -0.1, -0.1) + compare(invalid.sliceFrameGaps, Qt.vector3d(0.01, 0.01, 0.01)) + invalid.sliceFrameThicknesses = Qt.vector3d(-0.1, -0.1, -0.1) + compare(invalid.sliceFrameThicknesses, Qt.vector3d(0.01, 0.01, 0.01)) + invalid.sliceFrameWidths = Qt.vector3d(-0.1, -0.1, -0.1) + compare(invalid.sliceFrameWidths, Qt.vector3d(0.01, 0.01, 0.01)) + } + } +} diff --git a/tests/auto/qmltest/customitem.obj b/tests/auto/qmltest/customitem.obj new file mode 100644 index 00000000..108cf7ac --- /dev/null +++ b/tests/auto/qmltest/customitem.obj @@ -0,0 +1,54 @@ +# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend' +# www.blender.org +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +vt 0.666667 0.332314 +vt 0.334353 0.333333 +vt 0.665647 0.000000 +vt 0.001020 0.333333 +vt 0.000000 0.001020 +vt 0.333333 0.332314 +vt 0.333333 0.665647 +vt 0.001019 0.666667 +vt 0.000000 0.334353 +vt 0.334353 0.666667 +vt 0.333333 0.334353 +vt 0.665647 0.333333 +vt 0.333333 0.667686 +vt 0.665647 0.666667 +vt 0.666667 0.998980 +vt 0.667686 0.333333 +vt 0.666667 0.001019 +vt 0.998980 0.000000 +vt 0.333333 0.001019 +vt 0.332314 0.000000 +vt 0.332314 0.333333 +vt 0.666667 0.665647 +vt 0.334353 1.000000 +vt 1.000000 0.332314 +vn -1.000000 0.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 1.000000 -0.000000 0.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn -0.000000 -1.000000 -0.000000 +s off +f 5/1/1 6/2/1 1/3/1 +f 6/4/2 7/5/2 2/6/2 +f 7/7/3 8/8/3 4/9/3 +f 8/10/4 5/11/4 1/12/4 +f 8/13/5 7/14/5 6/15/5 +f 2/16/6 3/17/6 4/18/6 +f 6/2/1 2/19/1 1/3/1 +f 7/5/2 3/20/2 2/6/2 +f 3/21/3 7/7/3 4/9/3 +f 4/22/4 8/10/4 1/12/4 +f 5/23/5 8/13/5 6/15/5 +f 1/24/6 2/16/6 4/18/6 diff --git a/tests/auto/qmltest/customtexture.jpg b/tests/auto/qmltest/customtexture.jpg Binary files differnew file mode 100644 index 00000000..2580f5bd --- /dev/null +++ b/tests/auto/qmltest/customtexture.jpg diff --git a/tests/auto/qmltest/input3d/tst_input.qml b/tests/auto/qmltest/input3d/tst_input.qml new file mode 100644 index 00000000..fefb585e --- /dev/null +++ b/tests/auto/qmltest/input3d/tst_input.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + InputHandler3D { + id: initial + } + + InputHandler3D { + id: initialized + rotationEnabled: false + selectionEnabled: false + zoomAtTargetEnabled: false + zoomEnabled: false + } + + InputHandler3D { + id: change + } + + TestCase { + name: "InputHandler3D Initial" + + function test_initial() { + compare(initial.rotationEnabled, true) + compare(initial.selectionEnabled, true) + compare(initial.zoomAtTargetEnabled, true) + compare(initial.zoomEnabled, true) + } + } + + TestCase { + name: "InputHandler3D Initialized" + + function test_initialized() { + compare(initialized.rotationEnabled, false) + compare(initialized.selectionEnabled, false) + compare(initialized.zoomAtTargetEnabled, false) + compare(initialized.zoomEnabled, false) + } + } + + TestCase { + name: "InputHandler3D Change" + + function test_change() { + change.rotationEnabled = false + change.selectionEnabled = false + change.zoomAtTargetEnabled = false + change.zoomEnabled = false + + compare(change.rotationEnabled, false) + compare(change.selectionEnabled, false) + compare(change.zoomAtTargetEnabled, false) + compare(change.zoomEnabled, false) + } + + // TODO: QTRD-3380 (mouse events) + } +} diff --git a/tests/auto/qmltest/input3d/tst_touch.qml b/tests/auto/qmltest/input3d/tst_touch.qml new file mode 100644 index 00000000..626da68f --- /dev/null +++ b/tests/auto/qmltest/input3d/tst_touch.qml @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + TouchInputHandler3D { + id: initial + } + + TouchInputHandler3D { + id: initialized + rotationEnabled: false + selectionEnabled: false + zoomAtTargetEnabled: false + zoomEnabled: false + } + + TouchInputHandler3D { + id: change + } + + TestCase { + name: "TouchInputHandler3D Initial" + + function test_initial() { + compare(initial.rotationEnabled, true) + compare(initial.selectionEnabled, true) + compare(initial.zoomAtTargetEnabled, true) + compare(initial.zoomEnabled, true) + } + } + + TestCase { + name: "TouchInputHandler3D Initialized" + + function test_initialized() { + compare(initialized.rotationEnabled, false) + compare(initialized.selectionEnabled, false) + compare(initialized.zoomAtTargetEnabled, false) + compare(initialized.zoomEnabled, false) + } + } + + TestCase { + name: "TouchInputHandler3D Change" + + function test_change() { + change.rotationEnabled = false + change.selectionEnabled = false + change.zoomAtTargetEnabled = false + change.zoomEnabled = false + + compare(change.rotationEnabled, false) + compare(change.selectionEnabled, false) + compare(change.zoomAtTargetEnabled, false) + compare(change.zoomEnabled, false) + + // TODO: QTRD-3380 (mouse events) + } + } +} diff --git a/tests/auto/qmltest/qmltest.pro b/tests/auto/qmltest/qmltest.pro new file mode 100644 index 00000000..cbb9b8b8 --- /dev/null +++ b/tests/auto/qmltest/qmltest.pro @@ -0,0 +1,35 @@ +TEMPLATE = app +TARGET = tst_qmltest +CONFIG += qmltestcase +CONFIG += console +SOURCES += tst_qmltest.cpp +OTHER_FILES += bars3d\tst_basic.qml \ + bars3d\tst_bars.qml \ + bars3d\tst_barseries.qml \ + bars3d\tst_proxy.qml \ + scatter3d\tst_basic.qml \ + scatter3d\tst_scatter.qml \ + scatter3d\tst_scatterseries.qml \ + scatter3d\tst_proxy.qml \ + surface3d\tst_basic.qml \ + surface3d\tst_surface.qml \ + surface3d\tst_surfaceseries.qml \ + surface3d\tst_proxy.qml \ + surface3d\tst_heightproxy.qml \ + theme3d\tst_theme.qml \ + theme3d\tst_colorgradient.qml \ + theme3d\tst_themecolor.qml \ + custom3d\tst_customitem.qml \ + custom3d\tst_customlabel.qml \ + custom3d\tst_customvolume.qml \ + scene3d\tst_scene.qml \ + scene3d\tst_camera.qml \ + scene3d\tst_light.qml \ + input3d\tst_input.qml \ + input3d\tst_touch.qml \ + axis3d\tst_category.qml \ + axis3d\tst_value.qml \ + axis3d\tst_logvalue.qml \ + +RESOURCES += \ + qmltest.qrc diff --git a/tests/auto/qmltest/qmltest.qrc b/tests/auto/qmltest/qmltest.qrc new file mode 100644 index 00000000..61f19086 --- /dev/null +++ b/tests/auto/qmltest/qmltest.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>customitem.obj</file> + <file>customtexture.jpg</file> + </qresource> +</RCC> diff --git a/tests/auto/qmltest/scatter3d/tst_basic.qml b/tests/auto/qmltest/scatter3d/tst_basic.qml new file mode 100644 index 00000000..b7221701 --- /dev/null +++ b/tests/auto/qmltest/scatter3d/tst_basic.qml @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Scatter3D { + id: empty + } + + Scatter3D { + id: basic + anchors.fill: parent + } + + Scatter3D { + id: common + anchors.fill: parent + } + + Scatter3D { + id: common_init + anchors.fill: parent + selectionMode: AbstractGraph3D.SelectionNone + shadowQuality: AbstractGraph3D.ShadowQualityLow + msaaSamples: 2 + theme: Theme3D { } + renderingMode: AbstractGraph3D.RenderIndirect + measureFps: true + orthoProjection: false + aspectRatio: 3.0 + optimizationHints: AbstractGraph3D.OptimizationStatic + polar: false + radialLabelOffset: 2 + horizontalAspectRatio: 0.2 + reflection: true + reflectivity: 0.1 + locale: Qt.locale("UK") + margin: 0.2 + } + + TestCase { + name: "Scatter3D Empty" + + function test_empty() { + compare(empty.width, 0, "width") + compare(empty.height, 0, "height") + compare(empty.seriesList.length, 0, "seriesList") + compare(empty.selectedSeries, null, "selectedSeries") + compare(empty.axisX.orientation, AbstractAxis3D.AxisOrientationX) + compare(empty.axisZ.orientation, AbstractAxis3D.AxisOrientationZ) + compare(empty.axisY.orientation, AbstractAxis3D.AxisOrientationY) + compare(empty.axisX.type, AbstractAxis3D.AxisTypeValue) + compare(empty.axisZ.type, AbstractAxis3D.AxisTypeValue) + compare(empty.axisY.type, AbstractAxis3D.AxisTypeValue) + } + } + + TestCase { + name: "Scatter3D Basic" + when: windowShown + + function test_basic() { + compare(basic.width, 150, "width") + compare(basic.height, 150, "height") + } + } + + TestCase { + name: "Scatter3D Common" + when: windowShown + + function test_1_common() { + compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality") + if (common.shadowsSupported === true) + compare(common.msaaSamples, 4, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + compare(common.theme.type, Theme3D.ThemeQt, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common.measureFps, false, "measureFps") + compare(common.customItemList.length, 0, "customItemList") + compare(common.orthoProjection, false, "orthoProjection") + compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement") + compare(common.aspectRatio, 2.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints") + compare(common.polar, false, "polar") + compare(common.radialLabelOffset, 1, "radialLabelOffset") + compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio") + compare(common.reflection, false, "reflection") + compare(common.reflectivity, 0.5, "reflectivity") + compare(common.locale, Qt.locale("C"), "locale") + compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition") + compare(common.margin, -1, "margin") + } + + function test_2_change_common() { + common.selectionMode = AbstractGraph3D.SelectionNone + common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh + compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality") + common.msaaSamples = 8 + if (common.shadowsSupported === true) + compare(common.msaaSamples, 8, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + common.theme.type = Theme3D.ThemeRetro + common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear + common.measureFps = true + common.orthoProjection = true + common.aspectRatio = 1.0 + common.optimizationHints = AbstractGraph3D.OptimizationStatic + common.polar = true + common.radialLabelOffset = 2 + common.horizontalAspectRatio = 1 + common.reflection = true + common.reflectivity = 1.0 + common.locale = Qt.locale("FI") + common.margin = 1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows + compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero + compare(common.theme.type, Theme3D.ThemeRetro, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode") + compare(common.measureFps, true, "measureFps") + compare(common.orthoProjection, true, "orthoProjection") + compare(common.aspectRatio, 1.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common.polar, true, "polar") + compare(common.radialLabelOffset, 2, "radialLabelOffset") + compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio") + compare(common.reflection, true, "reflection") + compare(common.reflectivity, 1.0, "reflectivity") + compare(common.locale, Qt.locale("FI"), "locale") + compare(common.margin, 1.0, "margin") + } + + function test_3_change_invalid_common() { + common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice + common.theme.type = -2 + common.renderingMode = -1 + common.measureFps = false + common.orthoProjection = false + common.aspectRatio = -1.0 + common.polar = false + common.horizontalAspectRatio = -2 + common.reflection = false + common.reflectivity = -1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode") + compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done + compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done + compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done + } + + function test_4_common_initialized() { + compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode") + if (common_init.shadowsSupported === true) { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityLow, "shadowQuality") + compare(common_init.msaaSamples, 2, "msaaSamples") + } else { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") + compare(common_init.msaaSamples, 0, "msaaSamples") + } + compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme") + compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common_init.measureFps, true, "measureFps") + compare(common_init.customItemList.length, 0, "customItemList") + compare(common_init.orthoProjection, false, "orthoProjection") + compare(common_init.aspectRatio, 3.0, "aspectRatio") + compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common_init.polar, false, "polar") + compare(common_init.radialLabelOffset, 2, "radialLabelOffset") + compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio") + compare(common_init.reflection, true, "reflection") + compare(common_init.reflectivity, 0.1, "reflectivity") + compare(common_init.locale, Qt.locale("UK"), "locale") + compare(common_init.margin, 0.2, "margin") + } + } +} diff --git a/tests/auto/qmltest/scatter3d/tst_proxy.qml b/tests/auto/qmltest/scatter3d/tst_proxy.qml new file mode 100644 index 00000000..e6478f15 --- /dev/null +++ b/tests/auto/qmltest/scatter3d/tst_proxy.qml @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + ItemModelScatterDataProxy { + id: initial + } + + ItemModelScatterDataProxy { + id: initialized + + itemModel: ListModel { objectName: "model1" } + rotationRole: "rot" + rotationRolePattern: /-/ + rotationRoleReplace: "\\1" + xPosRole: "x" + xPosRolePattern: /^.*-(\d\d)$/ + xPosRoleReplace: "\\1" + yPosRole: "y" + yPosRolePattern: /^(\d\d\d\d).*$/ + yPosRoleReplace: "\\1" + zPosRole: "z" + zPosRolePattern: /-/ + zPosRoleReplace: "\\1" + } + + ItemModelScatterDataProxy { + id: change + } + + TestCase { + name: "ItemModelScatterDataProxy Initial" + + function test_initial() { + verify(!initial.itemModel) + compare(initial.rotationRole, "") + verify(initial.rotationRolePattern) + compare(initial.rotationRoleReplace, "") + compare(initial.xPosRole, "") + verify(initial.xPosRolePattern) + compare(initial.xPosRoleReplace, "") + compare(initial.yPosRole, "") + verify(initial.yPosRolePattern) + compare(initial.yPosRoleReplace, "") + compare(initial.zPosRole, "") + verify(initial.zPosRolePattern) + compare(initial.zPosRoleReplace, "") + + compare(initial.itemCount, 0) + verify(!initial.series) + + compare(initial.type, AbstractDataProxy.DataTypeScatter) + } + } + + TestCase { + name: "ItemModelScatterDataProxy Initialized" + + function test_initialized() { + compare(initialized.itemModel.objectName, "model1") + compare(initialized.rotationRole, "rot") + compare(initialized.rotationRolePattern, /-/) + compare(initialized.rotationRoleReplace, "\\1") + compare(initialized.xPosRole, "x") + compare(initialized.xPosRolePattern, /^.*-(\d\d)$/) + compare(initialized.xPosRoleReplace, "\\1") + compare(initialized.yPosRole, "y") + compare(initialized.yPosRolePattern, /^(\d\d\d\d).*$/) + compare(initialized.yPosRoleReplace, "\\1") + compare(initialized.zPosRole, "z") + compare(initialized.zPosRolePattern, /-/) + compare(initialized.zPosRoleReplace, "\\1") + } + } + + TestCase { + name: "ItemModelScatterDataProxy Change" + + ListModel { id: model1; objectName: "model1" } + + function test_change() { + change.itemModel = model1 + change.rotationRole = "rot" + change.rotationRolePattern = /-/ + change.rotationRoleReplace = "\\1" + change.xPosRole = "x" + change.xPosRolePattern = /^.*-(\d\d)$/ + change.xPosRoleReplace = "\\1" + change.yPosRole = "y" + change.yPosRolePattern = /^(\d\d\d\d).*$/ + change.yPosRoleReplace = "\\1" + change.zPosRole = "z" + change.zPosRolePattern = /-/ + change.zPosRoleReplace = "\\1" + + compare(change.itemModel.objectName, "model1") + compare(change.rotationRole, "rot") + compare(change.rotationRolePattern, /-/) + compare(change.rotationRoleReplace, "\\1") + compare(change.xPosRole, "x") + compare(change.xPosRolePattern, /^.*-(\d\d)$/) + compare(change.xPosRoleReplace, "\\1") + compare(change.yPosRole, "y") + compare(change.yPosRolePattern, /^(\d\d\d\d).*$/) + compare(change.yPosRoleReplace, "\\1") + compare(change.zPosRole, "z") + compare(change.zPosRolePattern, /-/) + compare(change.zPosRoleReplace, "\\1") + } + } +} diff --git a/tests/auto/qmltest/scatter3d/tst_scatter.qml b/tests/auto/qmltest/scatter3d/tst_scatter.qml new file mode 100644 index 00000000..b070b5f0 --- /dev/null +++ b/tests/auto/qmltest/scatter3d/tst_scatter.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Scatter3D { + id: series + anchors.fill: parent + } + + TestCase { + name: "Scatter3D Series" + + Scatter3DSeries { id: series1 } + Scatter3DSeries { id: series2 } + + function test_1_add_series() { + series.seriesList = [series1, series2] + compare(series.seriesList.length, 2) + } + + function test_2_remove_series() { + series.seriesList = [series1] + compare(series.seriesList.length, 1) + } + + function test_3_remove_series() { + series.seriesList = [] + compare(series.seriesList.length, 0) + } + + function test_4_selected_series() { + series.seriesList = [series1, series2] + series.seriesList[0].selectedItem = 0 + compare(series.selectedSeries, series1) + } + } +} diff --git a/tests/auto/qmltest/scatter3d/tst_scatterseries.qml b/tests/auto/qmltest/scatter3d/tst_scatterseries.qml new file mode 100644 index 00000000..4df58303 --- /dev/null +++ b/tests/auto/qmltest/scatter3d/tst_scatterseries.qml @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Scatter3DSeries { + id: initial + } + + ColorGradient { + id: gradient1; + stops: [ + ColorGradientStop { color: "red"; position: 0 }, + ColorGradientStop { color: "blue"; position: 1 } + ] + } + + ColorGradient { + id: gradient2; + stops: [ + ColorGradientStop { color: "green"; position: 0 }, + ColorGradientStop { color: "red"; position: 1 } + ] + } + + ColorGradient { + id: gradient3; + stops: [ + ColorGradientStop { color: "gray"; position: 0 }, + ColorGradientStop { color: "darkgray"; position: 1 } + ] + } + + Scatter3DSeries { + id: initialized + dataProxy: ItemModelScatterDataProxy { + itemModel: ListModel { + ListElement{ xPos: "2.754"; yPos: "1.455"; zPos: "3.362"; } + ListElement{ xPos: "3.164"; yPos: "2.022"; zPos: "4.348"; } + } + xPosRole: "xPos" + yPosRole: "yPos" + zPosRole: "zPos" + } + itemSize: 0.5 + selectedItem: 0 + + baseColor: "blue" + baseGradient: gradient1 + colorStyle: Theme3D.ColorStyleObjectGradient + itemLabelFormat: "%f" + itemLabelVisible: false + mesh: Abstract3DSeries.MeshMinimal + meshRotation: Qt.quaternion(1, 1, 1, 1) + meshSmooth: true + multiHighlightColor: "green" + multiHighlightGradient: gradient2 + name: "series1" + singleHighlightColor: "red" + singleHighlightGradient: gradient3 + userDefinedMesh: ":/customitem.obj" + visible: false + } + + ItemModelScatterDataProxy { + id: proxy1 + itemModel: ListModel { + ListElement{ xPos: "2.754"; yPos: "1.455"; zPos: "3.362"; } + ListElement{ xPos: "3.164"; yPos: "2.022"; zPos: "4.348"; } + ListElement{ xPos: "4.564"; yPos: "1.865"; zPos: "1.346"; } + } + xPosRole: "xPos" + yPosRole: "yPos" + zPosRole: "zPos" + } + + Scatter3DSeries { + id: change + } + + Scatter3DSeries { + id: invalid + } + + TestCase { + name: "Scatter3DSeries Initial" + + function test_1_initial() { + compare(initial.dataProxy.itemCount, 0) + compare(initial.invalidSelectionIndex, -1) + compare(initial.itemSize, 0.0) + compare(initial.selectedItem, -1) + } + + function test_2_initial_common() { + // Common properties + compare(initial.baseColor, "#000000") + compare(initial.baseGradient, null) + compare(initial.colorStyle, Theme3D.ColorStyleUniform) + compare(initial.itemLabel, "") + compare(initial.itemLabelFormat, "@xLabel, @yLabel, @zLabel") + compare(initial.itemLabelVisible, true) + compare(initial.mesh, Abstract3DSeries.MeshSphere) + compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0)) + compare(initial.meshSmooth, false) + compare(initial.multiHighlightColor, "#000000") + compare(initial.multiHighlightGradient, null) + compare(initial.name, "") + compare(initial.singleHighlightColor, "#000000") + compare(initial.singleHighlightGradient, null) + compare(initial.type, Abstract3DSeries.SeriesTypeScatter) + compare(initial.userDefinedMesh, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Scatter3DSeries Initialized" + + function test_1_initialized() { + compare(initialized.dataProxy.itemCount, 2) + compare(initialized.itemSize, 0.5) + compare(initialized.selectedItem, 0) + } + + function test_2_initialized_common() { + // Common properties + compare(initialized.baseColor, "#0000ff") + compare(initialized.baseGradient, gradient1) + compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(initialized.itemLabelFormat, "%f") + compare(initialized.itemLabelVisible, false) + compare(initialized.mesh, Abstract3DSeries.MeshMinimal) + compare(initialized.meshRotation, Qt.quaternion(1, 1, 1, 1)) + compare(initialized.meshSmooth, true) + compare(initialized.multiHighlightColor, "#008000") + compare(initialized.multiHighlightGradient, gradient2) + compare(initialized.name, "series1") + compare(initialized.singleHighlightColor, "#ff0000") + compare(initialized.singleHighlightGradient, gradient3) + compare(initialized.userDefinedMesh, ":/customitem.obj") + compare(initialized.visible, false) + } + } + + TestCase { + name: "Scatter3DSeries Change" + + function test_1_change() { + change.dataProxy = proxy1 + change.itemSize = 0.5 + change.selectedItem = 0 + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.dataProxy.itemCount, 3) + compare(change.itemSize, 0.5) + compare(change.selectedItem, 0) + } + + function test_3_change_common() { + change.baseColor = "blue" + change.baseGradient = gradient1 + change.colorStyle = Theme3D.ColorStyleObjectGradient + change.itemLabelFormat = "%f" + change.itemLabelVisible = false + change.mesh = Abstract3DSeries.MeshMinimal + change.meshRotation = Qt.quaternion(1, 1, 1, 1) + change.meshSmooth = true + change.multiHighlightColor = "green" + change.multiHighlightGradient = gradient2 + change.name = "series1" + change.singleHighlightColor = "red" + change.singleHighlightGradient = gradient3 + change.userDefinedMesh = ":/customitem.obj" + change.visible = false + + compare(change.baseColor, "#0000ff") + compare(change.baseGradient, gradient1) + compare(change.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(change.itemLabelFormat, "%f") + compare(change.itemLabelVisible, false) + compare(change.mesh, Abstract3DSeries.MeshMinimal) + compare(change.meshRotation, Qt.quaternion(1, 1, 1, 1)) + compare(change.meshSmooth, true) + compare(change.multiHighlightColor, "#008000") + compare(change.multiHighlightGradient, gradient2) + compare(change.name, "series1") + compare(change.singleHighlightColor, "#ff0000") + compare(change.singleHighlightGradient, gradient3) + compare(change.userDefinedMesh, ":/customitem.obj") + compare(change.visible, false) + } + + function test_4_change_gradient_stop() { + gradient1.stops[0].color = "yellow" + compare(change.baseGradient.stops[0].color, "#ffff00") + } + } + TestCase { + name: "Scatter3DSeries Invalid" + + function test_invalid() { + invalid.itemSize = -1.0 + compare(invalid.itemSize, 0.0) + invalid.itemSize = 1.1 + compare(invalid.itemSize, 0.0) + } + } +} diff --git a/tests/auto/qmltest/scene3d/tst_camera.qml b/tests/auto/qmltest/scene3d/tst_camera.qml new file mode 100644 index 00000000..07adf633 --- /dev/null +++ b/tests/auto/qmltest/scene3d/tst_camera.qml @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + Camera3D { + id: initial + } + + Camera3D { + id: initialized + maxZoomLevel: 1000.0 + minZoomLevel: 100.0 + target: Qt.vector3d(1.0, -1.0, 1.0) + wrapXRotation: false + wrapYRotation: true + xRotation: 30.0 + yRotation: 30.0 + zoomLevel: 500.0 + } + + Camera3D { + id: change + } + + Camera3D { + id: invalid + } + + TestCase { + name: "Camera3D Initial" + + function test_initial() { + compare(initial.cameraPreset, Camera3D.CameraPresetNone) + compare(initial.maxZoomLevel, 500.0) + compare(initial.minZoomLevel, 10.0) + compare(initial.target, Qt.vector3d(0.0, 0.0, 0.0)) + compare(initial.wrapXRotation, true) + compare(initial.wrapYRotation, false) + compare(initial.xRotation, 0.0) + compare(initial.yRotation, 0.0) + compare(initial.zoomLevel, 100.0) + } + } + + TestCase { + name: "Camera3D Initialized" + + function test_initialized() { + compare(initialized.maxZoomLevel, 1000.0) + compare(initialized.minZoomLevel, 100.0) + compare(initialized.target, Qt.vector3d(1.0, -1.0, 1.0)) + compare(initialized.wrapXRotation, false) + compare(initialized.wrapYRotation, true) + compare(initialized.xRotation, 30.0) + compare(initialized.yRotation, 30.0) + compare(initialized.zoomLevel, 500.0) + } + } + + TestCase { + name: "Camera3D Change" + + function test_1_change() { + change.cameraPreset = Camera3D.CameraPresetBehind // Will be overridden by the the following sets + change.maxZoomLevel = 1000.0 + change.minZoomLevel = 100.0 + change.target = Qt.vector3d(1.0, -1.0, 1.0) + change.wrapXRotation = false + change.wrapYRotation = true + change.xRotation = 30.0 + change.yRotation = 30.0 + change.zoomLevel = 500.0 + + compare(change.cameraPreset, Camera3D.CameraPresetNone) + compare(change.maxZoomLevel, 1000.0) + compare(change.minZoomLevel, 100.0) + compare(change.target, Qt.vector3d(1.0, -1.0, 1.0)) + compare(change.wrapXRotation, false) + compare(change.wrapYRotation, true) + compare(change.xRotation, 30.0) + compare(change.yRotation, 30.0) + compare(change.zoomLevel, 500.0) + } + + function test_2_change_preset() { + change.cameraPreset = Camera3D.CameraPresetBehind // Sets target and rotations + + compare(change.cameraPreset, Camera3D.CameraPresetBehind) + compare(change.maxZoomLevel, 1000.0) + compare(change.minZoomLevel, 100.0) + compare(change.target, Qt.vector3d(0.0, 0.0, 0.0)) + compare(change.wrapXRotation, false) + compare(change.wrapYRotation, true) + compare(change.xRotation, 180.0) + compare(change.yRotation, 22.5) + compare(change.zoomLevel, 500.0) + } + } + + TestCase { + name: "Camera3D Invalid" + + function test_invalid() { + invalid.target = Qt.vector3d(-1.5, -1.5, -1.5) + compare(invalid.target, Qt.vector3d(-1.0, -1.0, -1.0)) + invalid.target = Qt.vector3d(1.5, 1.5, 1.5) + compare(invalid.target, Qt.vector3d(1.0, 1.0, 1.0)) + invalid.minZoomLevel = 0.1 + compare(invalid.minZoomLevel, 1.0) + } + } +} diff --git a/tests/auto/qmltest/scene3d/tst_light.qml b/tests/auto/qmltest/scene3d/tst_light.qml new file mode 100644 index 00000000..d9fa282b --- /dev/null +++ b/tests/auto/qmltest/scene3d/tst_light.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + // TODO: Has no adjustable properties yet. + // Keeping this as a placeholder for future implementations (QTRD-2406) + /* + Light3D { + id: initial + } + + Light3D { + id: initialized + } + + + Light3D { + id: change + } + + TestCase { + name: "Light3D Initial" + + function test_initial() { + } + } + + TestCase { + name: "Light3D Initialized" + + function test_initialized() { + } + } + + TestCase { + name: "Light3D Change" + + function test_change() { + } + } + */ +} diff --git a/tests/auto/qmltest/scene3d/tst_scene.qml b/tests/auto/qmltest/scene3d/tst_scene.qml new file mode 100644 index 00000000..d53042ca --- /dev/null +++ b/tests/auto/qmltest/scene3d/tst_scene.qml @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + // Scene3D is uncreatable, so it needs to be accessed via a graph + Bars3D { + id: initial + } + + Bars3D { + id: initialized + scene.activeCamera: Camera3D { zoomLevel: 200 } + scene.devicePixelRatio: 2.0 + //scene.graphPositionQuery: Qt.point(0, 0) // TODO: Unusable until QTBUG-40043 is fixed + scene.primarySubViewport: Qt.rect(0, 0, 50, 50) + scene.secondarySubViewport: Qt.rect(50, 50, 100, 100) + scene.secondarySubviewOnTop: false + scene.selectionQueryPosition: Qt.point(0, 0) + scene.slicingActive: true + } + + Bars3D { + id: change + } + + Bars3D { + id: invalid + } + + TestCase { + name: "Scene3D Initial" + + function test_initial() { + verify(initial.scene.activeCamera) + verify(initial.scene.activeLight) + compare(initial.scene.devicePixelRatio, 1.0) + compare(initial.scene.graphPositionQuery, Qt.point(-1, -1)) + compare(initial.scene.invalidSelectionPoint, Qt.point(-1, -1)) + compare(initial.scene.primarySubViewport, Qt.rect(0, 0, 0, 0)) + compare(initial.scene.secondarySubViewport, Qt.rect(0, 0, 0, 0)) + compare(initial.scene.secondarySubviewOnTop, true) + compare(initial.scene.selectionQueryPosition, Qt.point(-1, -1)) + compare(initial.scene.slicingActive, false) + compare(initial.scene.viewport, Qt.rect(0, 0, 0, 0)) + } + } + + TestCase { + name: "Scene3D Initialized" + + function test_initialized() { + compare(initialized.scene.activeCamera.zoomLevel, 200) + compare(initialized.scene.devicePixelRatio, 2.0) + //compare(initialized.scene.graphPositionQuery, Qt.point(0, 0)) // TODO: Unusable until QTBUG-40043 is fixed + compare(initialized.scene.primarySubViewport, Qt.rect(0, 0, 50, 50)) + compare(initialized.scene.secondarySubViewport, Qt.rect(50, 50, 100, 100)) + compare(initialized.scene.secondarySubviewOnTop, false) + compare(initialized.scene.selectionQueryPosition, Qt.point(0, 0)) + compare(initialized.scene.slicingActive, true) + compare(initialized.scene.viewport, Qt.rect(0, 0, 100, 100)) + } + } + + TestCase { + name: "Scene3D Change" + + Camera3D { + id: camera1 + zoomLevel: 200 + } + + function test_change() { + change.scene.activeCamera = camera1 + change.scene.devicePixelRatio = 2.0 + change.scene.graphPositionQuery = Qt.point(0, 0) + change.scene.primarySubViewport = Qt.rect(0, 0, 50, 50) + change.scene.secondarySubViewport = Qt.rect(50, 50, 100, 100) + change.scene.secondarySubviewOnTop = false + change.scene.selectionQueryPosition = Qt.point(0, 0) // TODO: When doing signal checks, add tests to check that queries return something (asynchronously) + change.scene.slicingActive = true + + compare(change.scene.activeCamera.zoomLevel, 200) + compare(change.scene.devicePixelRatio, 2.0) + compare(change.scene.graphPositionQuery, Qt.point(0, 0)) + compare(change.scene.primarySubViewport, Qt.rect(0, 0, 50, 50)) + compare(change.scene.secondarySubViewport, Qt.rect(50, 50, 100, 100)) + compare(change.scene.secondarySubviewOnTop, false) + compare(change.scene.selectionQueryPosition, Qt.point(0, 0)) + compare(change.scene.slicingActive, true) + compare(change.scene.viewport, Qt.rect(0, 0, 100, 100)) + } + } + + TestCase { + name: "Scene3D Invalid" + + function test_invalid() { + invalid.scene.primarySubViewport = Qt.rect(0, 0, -50, -50) + compare(invalid.scene.primarySubViewport, Qt.rect(0, 0, 0, 0)) + } + } +} diff --git a/tests/auto/qmltest/surface3d/tst_basic.qml b/tests/auto/qmltest/surface3d/tst_basic.qml new file mode 100644 index 00000000..dfcc4542 --- /dev/null +++ b/tests/auto/qmltest/surface3d/tst_basic.qml @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Surface3D { + id: empty + } + + Surface3D { + id: basic + anchors.fill: parent + flipHorizontalGrid: true + } + + Surface3D { + id: common + anchors.fill: parent + } + + Surface3D { + id: common_init + anchors.fill: parent + selectionMode: AbstractGraph3D.SelectionNone + shadowQuality: AbstractGraph3D.ShadowQualityLow + msaaSamples: 2 + theme: Theme3D { } + renderingMode: AbstractGraph3D.RenderIndirect + measureFps: true + orthoProjection: false + aspectRatio: 3.0 + optimizationHints: AbstractGraph3D.OptimizationStatic + polar: false + radialLabelOffset: 2 + horizontalAspectRatio: 0.2 + reflection: true + reflectivity: 0.1 + locale: Qt.locale("UK") + margin: 0.2 + } + + TestCase { + name: "Surface3D Empty" + + function test_empty() { + compare(empty.width, 0, "width") + compare(empty.height, 0, "height") + compare(empty.seriesList.length, 0, "seriesList") + compare(empty.selectedSeries, null, "selectedSeries") + compare(empty.flipHorizontalGrid, false, "flipHorizontalGrid") + compare(empty.axisX.orientation, AbstractAxis3D.AxisOrientationX) + compare(empty.axisZ.orientation, AbstractAxis3D.AxisOrientationZ) + compare(empty.axisY.orientation, AbstractAxis3D.AxisOrientationY) + compare(empty.axisX.type, AbstractAxis3D.AxisTypeValue) + compare(empty.axisZ.type, AbstractAxis3D.AxisTypeValue) + compare(empty.axisY.type, AbstractAxis3D.AxisTypeValue) + } + } + + TestCase { + name: "Surface3D Basic" + when: windowShown + + function test_basic() { + compare(basic.width, 150, "width") + compare(basic.height, 150, "height") + compare(basic.flipHorizontalGrid, true, "flipHorizontalGrid") + } + + function test_change_basic() { + basic.flipHorizontalGrid = false + compare(basic.flipHorizontalGrid, false, "flipHorizontalGrid") + } + } + + TestCase { + name: "Surface3D Common" + when: windowShown + + function test_1_common() { + compare(common.selectionMode, AbstractGraph3D.SelectionItem, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityMedium, "shadowQuality") + if (common.shadowsSupported === true) + compare(common.msaaSamples, 4, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + compare(common.theme.type, Theme3D.ThemeQt, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common.measureFps, false, "measureFps") + compare(common.customItemList.length, 0, "customItemList") + compare(common.orthoProjection, false, "orthoProjection") + compare(common.selectedElement, AbstractGraph3D.ElementNone, "selectedElement") + compare(common.aspectRatio, 2.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationDefault, "optimizationHints") + compare(common.polar, false, "polar") + compare(common.radialLabelOffset, 1, "radialLabelOffset") + compare(common.horizontalAspectRatio, 0, "horizontalAspectRatio") + compare(common.reflection, false, "reflection") + compare(common.reflectivity, 0.5, "reflectivity") + compare(common.locale, Qt.locale("C"), "locale") + compare(common.queriedGraphPosition, Qt.vector3d(0, 0, 0), "queriedGraphPosition") + compare(common.margin, -1, "margin") + } + + function test_2_change_common() { + common.selectionMode = AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice + common.shadowQuality = AbstractGraph3D.ShadowQualitySoftHigh + compare(common.shadowQuality, AbstractGraph3D.ShadowQualitySoftHigh, "shadowQuality") + common.msaaSamples = 8 + if (common.shadowsSupported === true) + compare(common.msaaSamples, 8, "msaaSamples") + else + compare(common.msaaSamples, 0, "msaaSamples") + common.theme.type = Theme3D.ThemeRetro + common.renderingMode = AbstractGraph3D.RenderDirectToBackground_NoClear + common.measureFps = true + common.orthoProjection = true + common.aspectRatio = 1.0 + common.optimizationHints = AbstractGraph3D.OptimizationStatic + common.polar = true + common.radialLabelOffset = 2 + common.horizontalAspectRatio = 1 + common.reflection = true + common.reflectivity = 1.0 + common.locale = Qt.locale("FI") + common.margin = 1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode") + compare(common.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") // Ortho disables shadows + compare(common.msaaSamples, 0, "msaaSamples") // Rendering mode changes this to zero + compare(common.theme.type, Theme3D.ThemeRetro, "theme") + compare(common.renderingMode, AbstractGraph3D.RenderDirectToBackground_NoClear, "renderingMode") + compare(common.measureFps, true, "measureFps") + compare(common.orthoProjection, true, "orthoProjection") + compare(common.aspectRatio, 1.0, "aspectRatio") + compare(common.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common.polar, true, "polar") + compare(common.radialLabelOffset, 2, "radialLabelOffset") + compare(common.horizontalAspectRatio, 1, "horizontalAspectRatio") + compare(common.reflection, true, "reflection") + compare(common.reflectivity, 1.0, "reflectivity") + compare(common.locale, Qt.locale("FI"), "locale") + compare(common.margin, 1.0, "margin") + } + + function test_3_change_invalid_common() { + common.selectionMode = AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionColumn | AbstractGraph3D.SelectionSlice + common.theme.type = -2 + common.renderingMode = -1 + common.measureFps = false + common.orthoProjection = false + common.aspectRatio = -1.0 + common.polar = false + common.horizontalAspectRatio = -2 + common.reflection = false + common.reflectivity = -1.0 + compare(common.selectionMode, AbstractGraph3D.SelectionItem | AbstractGraph3D.SelectionRow | AbstractGraph3D.SelectionSlice, "selectionMode") + compare(common.theme.type, -2/*Theme3D.ThemeRetro*/, "theme") // TODO: Fix once QTRD-3367 is done + compare(common.renderingMode, -1/*AbstractGraph3D.RenderDirectToBackground_NoClear*/, "renderingMode") // TODO: Fix once QTRD-3367 is done + compare(common.aspectRatio, -1.0/*1.0*/, "aspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.horizontalAspectRatio, -2/*1*/, "horizontalAspectRatio") // TODO: Fix once QTRD-3367 is done + compare(common.reflectivity, -1.0/*1.0*/, "reflectivity") // TODO: Fix once QTRD-3367 is done + } + + function test_4_common_initialized() { + compare(common_init.selectionMode, AbstractGraph3D.SelectionNone, "selectionMode") + if (common_init.shadowsSupported === true) { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityLow, "shadowQuality") + compare(common_init.msaaSamples, 2, "msaaSamples") + } else { + compare(common_init.shadowQuality, AbstractGraph3D.ShadowQualityNone, "shadowQuality") + compare(common_init.msaaSamples, 0, "msaaSamples") + } + compare(common_init.theme.type, Theme3D.ThemeUserDefined, "theme") + compare(common_init.renderingMode, AbstractGraph3D.RenderIndirect, "renderingMode") + compare(common_init.measureFps, true, "measureFps") + compare(common_init.customItemList.length, 0, "customItemList") + compare(common_init.orthoProjection, false, "orthoProjection") + compare(common_init.aspectRatio, 3.0, "aspectRatio") + compare(common_init.optimizationHints, AbstractGraph3D.OptimizationStatic, "optimizationHints") + compare(common_init.polar, false, "polar") + compare(common_init.radialLabelOffset, 2, "radialLabelOffset") + compare(common_init.horizontalAspectRatio, 0.2, "horizontalAspectRatio") + compare(common_init.reflection, true, "reflection") + compare(common_init.reflectivity, 0.1, "reflectivity") + compare(common_init.locale, Qt.locale("UK"), "locale") + compare(common_init.margin, 0.2, "margin") + } + } +} diff --git a/tests/auto/qmltest/surface3d/tst_heightproxy.qml b/tests/auto/qmltest/surface3d/tst_heightproxy.qml new file mode 100644 index 00000000..29772451 --- /dev/null +++ b/tests/auto/qmltest/surface3d/tst_heightproxy.qml @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + HeightMapSurfaceDataProxy { + id: initial + } + + HeightMapSurfaceDataProxy { + id: initialized + heightMapFile: ":/customtexture.jpg" + maxXValue: 10.0 + maxZValue: 10.0 + minXValue: -10.0 + minZValue: -10.0 + } + + HeightMapSurfaceDataProxy { + id: change + } + + HeightMapSurfaceDataProxy { + id: invalid + } + + TestCase { + name: "HeightMapSurfaceDataProxy Initial" + + function test_initial() { + compare(initial.heightMapFile, "") + compare(initial.maxXValue, 10.0) + compare(initial.maxZValue, 10.0) + compare(initial.minXValue, 0) + compare(initial.minZValue, 0) + + compare(initial.columnCount, 0) + compare(initial.rowCount, 0) + verify(!initial.series) + + compare(initial.type, AbstractDataProxy.DataTypeSurface) + } + } + + TestCase { + name: "HeightMapSurfaceDataProxy Initialized" + + function test_initialized() { + compare(initialized.heightMapFile, ":/customtexture.jpg") + compare(initialized.maxXValue, 10.0) + compare(initialized.maxZValue, 10.0) + compare(initialized.minXValue, -10.0) + compare(initialized.minZValue, -10.0) + + compare(initialized.columnCount, 24) + compare(initialized.rowCount, 24) + } + } + + TestCase { + name: "HeightMapSurfaceDataProxy Change" + + function test_1_change() { + change.heightMapFile = ":/customtexture.jpg" + change.maxXValue = 10.0 + change.maxZValue = 10.0 + change.minXValue = -10.0 + change.minZValue = -10.0 + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.heightMapFile, ":/customtexture.jpg") + compare(change.maxXValue, 10.0) + compare(change.maxZValue, 10.0) + compare(change.minXValue, -10.0) + compare(change.minZValue, -10.0) + + compare(change.columnCount, 24) + compare(change.rowCount, 24) + } + } + + TestCase { + name: "HeightMapSurfaceDataProxy Invalid" + + function test_invalid() { + invalid.maxXValue = -10 + compare(invalid.minXValue, -11) + invalid.minZValue = 20 + compare(invalid.maxZValue, 21) + } + } +} diff --git a/tests/auto/qmltest/surface3d/tst_proxy.qml b/tests/auto/qmltest/surface3d/tst_proxy.qml new file mode 100644 index 00000000..8f353153 --- /dev/null +++ b/tests/auto/qmltest/surface3d/tst_proxy.qml @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + ItemModelSurfaceDataProxy { + id: initial + } + + ItemModelSurfaceDataProxy { + id: initialized + + autoColumnCategories: false + autoRowCategories: false + columnCategories: ["colcat1", "colcat2"] + columnRole: "col" + columnRolePattern: /^.*-(\d\d)$/ + columnRoleReplace: "\\1" + itemModel: ListModel { objectName: "model1" } + multiMatchBehavior: ItemModelSurfaceDataProxy.MMBAverage + rowCategories: ["rowcat1", "rowcat2"] + rowRole: "row" + rowRolePattern: /^(\d\d\d\d).*$/ + rowRoleReplace: "\\1" + xPosRole: "x" + xPosRolePattern: /^.*-(\d\d)$/ + xPosRoleReplace: "\\1" + yPosRole: "y" + yPosRolePattern: /^(\d\d\d\d).*$/ + yPosRoleReplace: "\\1" + zPosRole: "z" + zPosRolePattern: /-/ + zPosRoleReplace: "\\1" + } + + ItemModelSurfaceDataProxy { + id: change + } + + TestCase { + name: "ItemModelSurfaceDataProxy Initial" + + function test_initial() { + compare(initial.autoColumnCategories, true) + compare(initial.autoRowCategories, true) + compare(initial.columnCategories, []) + compare(initial.columnRole, "") + verify(initial.columnRolePattern) + compare(initial.columnRoleReplace, "") + verify(!initial.itemModel) + compare(initial.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBLast) + compare(initial.rowCategories, []) + compare(initial.rowRole, "") + verify(initial.rowRolePattern) + compare(initial.rowRoleReplace, "") + compare(initial.useModelCategories, false) + compare(initial.xPosRole, "") + verify(initial.xPosRolePattern) + compare(initial.xPosRoleReplace, "") + compare(initial.yPosRole, "") + verify(initial.yPosRolePattern) + compare(initial.yPosRoleReplace, "") + compare(initial.zPosRole, "") + verify(initial.zPosRolePattern) + compare(initial.zPosRoleReplace, "") + + compare(initial.columnCount, 0) + compare(initial.rowCount, 0) + verify(!initial.series) + + compare(initial.type, AbstractDataProxy.DataTypeSurface) + } + } + + TestCase { + name: "ItemModelSurfaceDataProxy Initialized" + + function test_initialized() { + compare(initialized.autoColumnCategories, false) + compare(initialized.autoRowCategories, false) + compare(initialized.columnCategories.length, 2) + compare(initialized.columnCategories[0], "colcat1") + compare(initialized.columnCategories[1], "colcat2") + compare(initialized.columnRole, "col") + compare(initialized.columnRolePattern, /^.*-(\d\d)$/) + compare(initialized.columnRoleReplace, "\\1") + compare(initialized.itemModel.objectName, "model1") + compare(initialized.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBAverage) + compare(initialized.rowCategories.length, 2) + compare(initialized.rowCategories[0], "rowcat1") + compare(initialized.rowCategories[1], "rowcat2") + compare(initialized.rowRole, "row") + compare(initialized.rowRolePattern, /^(\d\d\d\d).*$/) + compare(initialized.rowRoleReplace, "\\1") + compare(initialized.xPosRole, "x") + compare(initialized.xPosRolePattern, /^.*-(\d\d)$/) + compare(initialized.xPosRoleReplace, "\\1") + compare(initialized.yPosRole, "y") + compare(initialized.yPosRolePattern, /^(\d\d\d\d).*$/) + compare(initialized.yPosRoleReplace, "\\1") + compare(initialized.zPosRole, "z") + compare(initialized.zPosRolePattern, /-/) + compare(initialized.zPosRoleReplace, "\\1") + + compare(initialized.columnCount, 2) + compare(initialized.rowCount, 2) + } + } + + TestCase { + name: "ItemModelSurfaceDataProxy Change" + + ListModel { id: model1; objectName: "model1" } + + function test_1_change() { + change.autoColumnCategories = false + change.autoRowCategories = false + change.columnCategories = ["colcat1", "colcat2"] + change.columnRole = "col" + change.columnRolePattern = /^.*-(\d\d)$/ + change.columnRoleReplace = "\\1" + change.itemModel = model1 + change.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBAverage + change.rowCategories = ["rowcat1", "rowcat2"] + change.rowRole = "row" + change.rowRolePattern = /^(\d\d\d\d).*$/ + change.rowRoleReplace = "\\1" + change.useModelCategories = true // Overwrites columnLabels and rowLabels + change.xPosRole = "x" + change.xPosRolePattern = /^.*-(\d\d)$/ + change.xPosRoleReplace = "\\1" + change.yPosRole = "y" + change.yPosRolePattern = /^(\d\d\d\d).*$/ + change.yPosRoleReplace = "\\1" + change.zPosRole = "z" + change.zPosRolePattern = /-/ + change.zPosRoleReplace = "\\1" + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.autoColumnCategories, false) + compare(change.autoRowCategories, false) + compare(change.columnCategories.length, 2) + compare(change.columnCategories[0], "colcat1") + compare(change.columnCategories[1], "colcat2") + compare(change.columnRole, "col") + compare(change.columnRolePattern, /^.*-(\d\d)$/) + compare(change.columnRoleReplace, "\\1") + compare(change.itemModel.objectName, "model1") + compare(change.multiMatchBehavior, ItemModelSurfaceDataProxy.MMBAverage) + compare(change.rowCategories.length, 2) + compare(change.rowCategories[0], "rowcat1") + compare(change.rowCategories[1], "rowcat2") + compare(change.rowRole, "row") + compare(change.rowRolePattern, /^(\d\d\d\d).*$/) + compare(change.rowRoleReplace, "\\1") + compare(change.useModelCategories, true) + compare(change.xPosRole, "x") + compare(change.xPosRolePattern, /^.*-(\d\d)$/) + compare(change.xPosRoleReplace, "\\1") + compare(change.yPosRole, "y") + compare(change.yPosRolePattern, /^(\d\d\d\d).*$/) + compare(change.yPosRoleReplace, "\\1") + compare(change.zPosRole, "z") + compare(change.zPosRolePattern, /-/) + compare(change.zPosRoleReplace, "\\1") + + compare(change.columnCount, 0) + compare(change.rowCount, 0) + } + } + + TestCase { + name: "ItemModelSurfaceDataProxy MultiMatchBehaviour" + + Surface3D { + id: surface1 + Surface3DSeries { + ItemModelSurfaceDataProxy { + id: surfaceProxy + itemModel: ListModel { + ListElement{ coords: "0,0"; data: "5"; } + ListElement{ coords: "0,0"; data: "15"; } + ListElement{ coords: "0,1"; data: "5"; } + ListElement{ coords: "0,1"; data: "15"; } + ListElement{ coords: "1,0"; data: "5"; } + ListElement{ coords: "1,0"; data: "15"; } + ListElement{ coords: "1,1"; data: "0"; } + } + rowRole: "coords" + columnRole: "coords" + yPosRole: "data" + rowRolePattern: /(\d),\d/ + columnRolePattern: /(\d),(\d)/ + rowRoleReplace: "\\1" + columnRoleReplace: "\\2" + } + } + } + + function test_0_async_dummy() { + } + + function test_1_test_multimatch() { + compare(surface1.axisY.max, 15) + } + + function test_2_multimatch() { + surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBFirst + } + + function test_3_test_multimatch() { + compare(surface1.axisY.max, 5) + } + + function test_4_multimatch() { + surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBLast + } + + function test_5_test_multimatch() { + compare(surface1.axisY.max, 15) + } + + function test_6_multimatch() { + surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBAverage + } + + function test_7_test_multimatch() { + compare(surface1.axisY.max, 10) + } + + function test_8_multimatch() { + surfaceProxy.multiMatchBehavior = ItemModelSurfaceDataProxy.MMBCumulativeY + } + + function test_9_test_multimatch() { + compare(surface1.axisY.max, 20) + } + } +} diff --git a/tests/auto/qmltest/surface3d/tst_surface.qml b/tests/auto/qmltest/surface3d/tst_surface.qml new file mode 100644 index 00000000..31c86da2 --- /dev/null +++ b/tests/auto/qmltest/surface3d/tst_surface.qml @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Surface3D { + id: series + anchors.fill: parent + } + + TestCase { + name: "Surface3D Series" + + Surface3DSeries { id: series1 } + Surface3DSeries { id: series2 } + + function test_1_add_series() { + series.seriesList = [series1, series2] + compare(series.seriesList.length, 2) + } + + function test_2_remove_series() { + series.seriesList = [series1] + compare(series.seriesList.length, 1) + } + + function test_3_remove_series() { + series.seriesList = [] + compare(series.seriesList.length, 0) + } + + function test_4_selected_series() { + series.seriesList = [series1, series2] + series.seriesList[0].selectedPoint = Qt.point(0, 0) + compare(series.selectedSeries, series1) + } + } +} diff --git a/tests/auto/qmltest/surface3d/tst_surfaceseries.qml b/tests/auto/qmltest/surface3d/tst_surfaceseries.qml new file mode 100644 index 00000000..dff2e4a8 --- /dev/null +++ b/tests/auto/qmltest/surface3d/tst_surfaceseries.qml @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + height: 150 + width: 150 + + Surface3DSeries { + id: initial + } + + ColorGradient { + id: gradient1; + stops: [ + ColorGradientStop { color: "red"; position: 0 }, + ColorGradientStop { color: "blue"; position: 1 } + ] + } + + ColorGradient { + id: gradient2; + stops: [ + ColorGradientStop { color: "green"; position: 0 }, + ColorGradientStop { color: "red"; position: 1 } + ] + } + + ColorGradient { + id: gradient3; + stops: [ + ColorGradientStop { color: "gray"; position: 0 }, + ColorGradientStop { color: "darkgray"; position: 1 } + ] + } + + Surface3DSeries { + id: initialized + dataProxy: ItemModelSurfaceDataProxy { + itemModel: ListModel { + ListElement{ longitude: "20"; latitude: "10"; pop_density: "4.75"; } + ListElement{ longitude: "21"; latitude: "10"; pop_density: "3.00"; } + } + rowRole: "longitude" + columnRole: "latitude" + yPosRole: "pop_density" + } + drawMode: Surface3DSeries.DrawSurface + flatShadingEnabled: false + selectedPoint: Qt.point(0, 0) + textureFile: ":\customtexture.jpg" + + baseColor: "blue" + baseGradient: gradient1 + colorStyle: Theme3D.ColorStyleObjectGradient + itemLabelFormat: "%f" + itemLabelVisible: false + mesh: Abstract3DSeries.MeshCube + meshRotation: Qt.quaternion(1, 1, 1, 1) + meshSmooth: true + multiHighlightColor: "green" + multiHighlightGradient: gradient2 + name: "series1" + singleHighlightColor: "red" + singleHighlightGradient: gradient3 + userDefinedMesh: ":/customitem.obj" + visible: false + } + + ItemModelSurfaceDataProxy { + id: proxy1 + itemModel: ListModel { + ListElement{ longitude: "20"; latitude: "10"; pop_density: "4.75"; } + ListElement{ longitude: "21"; latitude: "10"; pop_density: "3.00"; } + ListElement{ longitude: "22"; latitude: "10"; pop_density: "1.24"; } + } + rowRole: "longitude" + columnRole: "latitude" + yPosRole: "pop_density" + } + + Surface3DSeries { + id: change + } + + TestCase { + name: "Surface3DSeries Initial" + + function test_1_initial() { + compare(initial.dataProxy.rowCount, 0) + compare(initial.invalidSelectionPosition, Qt.point(-1, -1)) + compare(initial.drawMode, Surface3DSeries.DrawSurfaceAndWireframe) + compare(initial.flatShadingEnabled, true) + compare(initial.flatShadingSupported, true) + compare(initial.selectedPoint, Qt.point(-1, -1)) + } + + function test_2_initial_common() { + // Common properties + compare(initial.baseColor, "#000000") + compare(initial.baseGradient, null) + compare(initial.colorStyle, Theme3D.ColorStyleUniform) + compare(initial.itemLabel, "") + compare(initial.itemLabelFormat, "@xLabel, @yLabel, @zLabel") + compare(initial.itemLabelVisible, true) + compare(initial.mesh, Abstract3DSeries.MeshSphere) + compare(initial.meshRotation, Qt.quaternion(1, 0, 0, 0)) + compare(initial.meshSmooth, false) + compare(initial.multiHighlightColor, "#000000") + compare(initial.multiHighlightGradient, null) + compare(initial.name, "") + compare(initial.singleHighlightColor, "#000000") + compare(initial.singleHighlightGradient, null) + compare(initial.type, Abstract3DSeries.SeriesTypeSurface) + compare(initial.userDefinedMesh, "") + compare(initial.visible, true) + } + } + + TestCase { + name: "Surface3DSeries Initialized" + + function test_1_initialized() { + compare(initialized.dataProxy.rowCount, 2) + compare(initialized.drawMode, Surface3DSeries.DrawSurface) + compare(initialized.flatShadingEnabled, false) + compare(initialized.selectedPoint, Qt.point(0, 0)) + compare(initialized.textureFile, ":\customtexture.jpg") + } + + function test_2_initialized_common() { + // Common properties + compare(initialized.baseColor, "#0000ff") + compare(initialized.baseGradient, gradient1) + compare(initialized.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(initialized.itemLabelFormat, "%f") + compare(initialized.itemLabelVisible, false) + compare(initialized.mesh, Abstract3DSeries.MeshCube) + compare(initialized.meshRotation, Qt.quaternion(1, 1, 1, 1)) + compare(initialized.meshSmooth, true) + compare(initialized.multiHighlightColor, "#008000") + compare(initialized.multiHighlightGradient, gradient2) + compare(initialized.name, "series1") + compare(initialized.singleHighlightColor, "#ff0000") + compare(initialized.singleHighlightGradient, gradient3) + compare(initialized.userDefinedMesh, ":/customitem.obj") + compare(initialized.visible, false) + } + } + + TestCase { + name: "Surface3DSeries Change" + + function test_1_change() { + change.dataProxy = proxy1 + change.drawMode = Surface3DSeries.DrawSurface + change.flatShadingEnabled = false + change.selectedPoint = Qt.point(0, 0) + change.textureFile = ":\customtexture.jpg" + } + + function test_2_test_change() { + // This test has a dependency to the previous one due to asynchronous item model resolving + compare(change.dataProxy.rowCount, 3) + compare(change.drawMode, Surface3DSeries.DrawSurface) + compare(change.flatShadingEnabled, false) + compare(change.selectedPoint, Qt.point(0, 0)) + compare(change.textureFile, ":\customtexture.jpg") + } + + function test_3_change_common() { + change.baseColor = "blue" + change.baseGradient = gradient1 + change.colorStyle = Theme3D.ColorStyleObjectGradient + change.itemLabelFormat = "%f" + change.itemLabelVisible = false + change.mesh = Abstract3DSeries.MeshCube + change.meshRotation = Qt.quaternion(1, 1, 1, 1) + change.meshSmooth = true + change.multiHighlightColor = "green" + change.multiHighlightGradient = gradient2 + change.name = "series1" + change.singleHighlightColor = "red" + change.singleHighlightGradient = gradient3 + change.userDefinedMesh = ":/customitem.obj" + change.visible = false + + compare(change.baseColor, "#0000ff") + compare(change.baseGradient, gradient1) + compare(change.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(change.itemLabelFormat, "%f") + compare(change.itemLabelVisible, false) + compare(change.mesh, Abstract3DSeries.MeshCube) + compare(change.meshRotation, Qt.quaternion(1, 1, 1, 1)) + compare(change.meshSmooth, true) + compare(change.multiHighlightColor, "#008000") + compare(change.multiHighlightGradient, gradient2) + compare(change.name, "series1") + compare(change.singleHighlightColor, "#ff0000") + compare(change.singleHighlightGradient, gradient3) + compare(change.userDefinedMesh, ":/customitem.obj") + compare(change.visible, false) + } + + function test_4_change_gradient_stop() { + gradient1.stops[0].color = "yellow" + compare(change.baseGradient.stops[0].color, "#ffff00") + } + } +} diff --git a/tests/auto/qmltest/theme3d/tst_colorgradient.qml b/tests/auto/qmltest/theme3d/tst_colorgradient.qml new file mode 100644 index 00000000..395b6672 --- /dev/null +++ b/tests/auto/qmltest/theme3d/tst_colorgradient.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + ColorGradient { + id: initial + } + + ColorGradient { + id: initialized + stops: [ + ColorGradientStop { color: "blue"; position: 0 }, + ColorGradientStop { color: "white"; position: 0.5 }, + ColorGradientStop { color: "red"; position: 1 } + ] + } + + ColorGradient { + id: change + } + + TestCase { + name: "ColorGradient Initial" + + function test_initial() { + compare(initial.stops.length, 0) + } + } + + TestCase { + name: "ColorGradient Initialized" + + function test_initialized() { + compare(initialized.stops.length, 3) + compare(initialized.stops[0].color, "#0000ff") + compare(initialized.stops[1].color, "#ffffff") + compare(initialized.stops[2].color, "#ff0000") + } + } + + TestCase { + name: "ColorGradient Change" + + ColorGradientStop { id: stop1; color: "blue"; position: 0 } + ColorGradientStop { id: stop2; color: "red"; position: 1.0 } + ColorGradientStop { id: stop3; color: "white"; position: 0.5 } + + function test_change() { + change.stops = [stop1] + compare(change.stops.length, 1) + change.stops = [stop1, stop2] + compare(change.stops.length, 2) + compare(change.stops[0].color, "#0000ff") + change.stops[0].color = "red" + compare(change.stops[0].color, "#ff0000") + compare(change.stops[1].color, "#ff0000") + change.stops = [stop1, stop2, stop3] + compare(change.stops[2].color, "#ffffff") + compare(change.stops.length, 3) + stop2.position = 0.25 + stop3.position = 1.0 + compare(change.stops[1].position, 0.25) + compare(change.stops[2].position, 1.0) + } + } +} diff --git a/tests/auto/qmltest/theme3d/tst_theme.qml b/tests/auto/qmltest/theme3d/tst_theme.qml new file mode 100644 index 00000000..3e42b300 --- /dev/null +++ b/tests/auto/qmltest/theme3d/tst_theme.qml @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + Theme3D { + id: initial + } + + ColorGradient { + id: gradient1 + stops: [ + ColorGradientStop { color: "red"; position: 0 }, + ColorGradientStop { color: "blue"; position: 1 } + ] + } + + ColorGradient { + id: gradient2 + stops: [ + ColorGradientStop { color: "green"; position: 0 }, + ColorGradientStop { color: "red"; position: 1 } + ] + } + + ColorGradient { + id: gradient3 + stops: [ + ColorGradientStop { color: "gray"; position: 0 }, + ColorGradientStop { color: "darkgray"; position: 1 } + ] + } + + ThemeColor { + id: color1 + color: "red" + } + + ThemeColor { + id: color2 + color: "blue" + } + + Theme3D { + id: initialized + ambientLightStrength: 0.3 + backgroundColor: "#ff0000" + backgroundEnabled: false + baseColors: [color1, color2] + baseGradients: [gradient1, gradient2] + colorStyle: Theme3D.ColorStyleRangeGradient + font.family: "Arial" + gridEnabled: false + gridLineColor: "#00ff00" + highlightLightStrength: 5.0 + labelBackgroundColor: "#ff00ff" + labelBackgroundEnabled: false + labelBorderEnabled: false + labelTextColor: "#00ffff" + lightColor: "#ffff00" + lightStrength: 2.5 + multiHighlightColor: "#ff00ff" + multiHighlightGradient: gradient3 + singleHighlightColor: "#ff0000" + singleHighlightGradient: gradient3 + type: Theme3D.ThemeQt // Default values will be overwritten by initialized values + windowColor: "#fff00f" + } + + Theme3D { + id: change + } + + Theme3D { + id: invalid + } + + TestCase { + name: "Theme3D Initial" + + Text { id: dummy } + + function test_initial() { + compare(initial.ambientLightStrength, 0.25) + compare(initial.backgroundColor, "#000000") + compare(initial.backgroundEnabled, true) + compare(initial.baseColors.length, 1) + compare(initial.baseColors[0].color, "#000000") + compare(initial.baseGradients.length, 1) + compare(initial.baseGradients[0].stops[0].color, "#000000") + compare(initial.baseGradients[0].stops[1].color, "#ffffff") + compare(initial.colorStyle, Theme3D.ColorStyleUniform) + // Initial font needs to be tested like this, as different platforms have different default font (QFont()) + compare(initial.font.family, dummy.font.family) + compare(initial.gridEnabled, true) + compare(initial.gridLineColor, "#ffffff") + compare(initial.highlightLightStrength, 7.5) + compare(initial.labelBackgroundColor, "#a0a0a4") + compare(initial.labelBackgroundEnabled, true) + compare(initial.labelBorderEnabled, true) + compare(initial.labelTextColor, "#ffffff") + compare(initial.lightColor, "#ffffff") + compare(initial.lightStrength, 5) + compare(initial.multiHighlightColor, "#0000ff") + compare(initial.multiHighlightGradient, null) + compare(initial.singleHighlightColor, "#ff0000") + compare(initial.singleHighlightGradient, null) + compare(initial.type, Theme3D.ThemeUserDefined) + compare(initial.windowColor, "#000000") + } + } + + TestCase { + name: "Theme3D Initialized" + + function test_initialized() { + compare(initialized.ambientLightStrength, 0.3) + compare(initialized.backgroundColor, "#ff0000") + compare(initialized.backgroundEnabled, false) + compare(initialized.baseColors.length, 2) + compare(initialized.baseColors[0].color, "#ff0000") + compare(initialized.baseColors[1].color, "#0000ff") + compare(initialized.baseGradients.length, 2) + compare(initialized.baseGradients[0], gradient1) + compare(initialized.baseGradients[1], gradient2) + compare(initialized.colorStyle, Theme3D.ColorStyleRangeGradient) + compare(initialized.font.family, "Arial") + compare(initialized.gridEnabled, false) + compare(initialized.gridLineColor, "#00ff00") + compare(initialized.highlightLightStrength, 5.0) + compare(initialized.labelBackgroundColor, "#ff00ff") + compare(initialized.labelBackgroundEnabled, false) + compare(initialized.labelBorderEnabled, false) + compare(initialized.labelTextColor, "#00ffff") + compare(initialized.lightColor, "#ffff00") + compare(initialized.lightStrength, 2.5) + compare(initialized.multiHighlightColor, "#ff00ff") + compare(initialized.multiHighlightGradient, gradient3) + compare(initialized.singleHighlightColor, "#ff0000") + compare(initialized.singleHighlightGradient, gradient3) + compare(initialized.type, Theme3D.ThemeQt) + compare(initialized.windowColor, "#fff00f") + } + } + + TestCase { + name: "Theme3D Change" + + ThemeColor { + id: color3 + color: "red" + } + + ColorGradient { + id: gradient4 + stops: [ + ColorGradientStop { color: "red"; position: 0 }, + ColorGradientStop { color: "blue"; position: 1 } + ] + } + + function test_1_change() { + change.type = Theme3D.ThemeStoneMoss // Default values will be overwritten by the following sets + change.ambientLightStrength = 0.3 + change.backgroundColor = "#ff0000" + change.backgroundEnabled = false + change.baseColors = [color3, color2] + change.baseGradients = [gradient4, gradient2] + change.colorStyle = Theme3D.ColorStyleObjectGradient + change.font.family = "Arial" + change.gridEnabled = false + change.gridLineColor = "#00ff00" + change.highlightLightStrength = 5.0 + change.labelBackgroundColor = "#ff00ff" + change.labelBackgroundEnabled = false + change.labelBorderEnabled = false + change.labelTextColor = "#00ffff" + change.lightColor = "#ffff00" + change.lightStrength = 2.5 + change.multiHighlightColor = "#ff00ff" + change.multiHighlightGradient = gradient3 + change.singleHighlightColor = "#ff0000" + change.singleHighlightGradient = gradient3 + change.windowColor = "#fff00f" + + compare(change.ambientLightStrength, 0.3) + compare(change.backgroundColor, "#ff0000") + compare(change.backgroundEnabled, false) + compare(change.baseColors.length, 2) + compare(change.baseColors[0].color, "#ff0000") + compare(change.baseColors[1].color, "#0000ff") + compare(change.baseGradients.length, 2) + compare(change.baseGradients[0], gradient4) + compare(change.baseGradients[1], gradient2) + compare(change.colorStyle, Theme3D.ColorStyleObjectGradient) + compare(change.font.family, "Arial") + compare(change.gridEnabled, false) + compare(change.gridLineColor, "#00ff00") + compare(change.highlightLightStrength, 5.0) + compare(change.labelBackgroundColor, "#ff00ff") + compare(change.labelBackgroundEnabled, false) + compare(change.labelBorderEnabled, false) + compare(change.labelTextColor, "#00ffff") + compare(change.lightColor, "#ffff00") + compare(change.lightStrength, 2.5) + compare(change.multiHighlightColor, "#ff00ff") + compare(change.multiHighlightGradient, gradient3) + compare(change.singleHighlightColor, "#ff0000") + compare(change.singleHighlightGradient, gradient3) + compare(change.type, Theme3D.ThemeStoneMoss) + compare(change.windowColor, "#fff00f") + } + + function test_2_change_color() { + color3.color = "white" + compare(change.baseColors[0].color, "#ffffff") + } + + function test_3_change_gradient() { + gradient4.stops[0].color = "black" + compare(change.baseGradients[0].stops[0].color, "#000000") + } + } + + + TestCase { + name: "Theme3D Invalid" + + function test_invalid() { + invalid.ambientLightStrength = -1.0 + compare(invalid.ambientLightStrength, 0.25) + invalid.ambientLightStrength = 1.1 + compare(invalid.ambientLightStrength, 0.25) + invalid.highlightLightStrength = -1.0 + compare(invalid.highlightLightStrength, 7.5) + invalid.highlightLightStrength = 10.1 + compare(invalid.highlightLightStrength, 7.5) + invalid.lightStrength = -1.0 + compare(invalid.lightStrength, 5.0) + invalid.lightStrength = 10.1 + compare(invalid.lightStrength, 5.0) + } + } +} diff --git a/tests/auto/qmltest/theme3d/tst_themecolor.qml b/tests/auto/qmltest/theme3d/tst_themecolor.qml new file mode 100644 index 00000000..421a5775 --- /dev/null +++ b/tests/auto/qmltest/theme3d/tst_themecolor.qml @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.0 +import QtDataVisualization 1.2 +import QtTest 1.0 + +Item { + id: top + width: 150 + height: 150 + + ThemeColor { + id: initial + } + + ThemeColor { + id: initialized + color: "red" + } + + ThemeColor { + id: change + } + + TestCase { + name: "ThemeColor Initial" + + function test_initial() { + compare(initial.color, "#000000") + } + } + + TestCase { + name: "ThemeColor Initialized" + + function test_initialized() { + compare(initialized.color, "#ff0000") + } + } + + TestCase { + name: "ThemeColor Change" + + function test_change() { + change.color = "blue" + + compare(change.color, "#0000ff") + } + } +} diff --git a/tests/auto/qmltest/tst_qmltest.cpp b/tests/auto/qmltest/tst_qmltest.cpp new file mode 100644 index 00000000..569d9150 --- /dev/null +++ b/tests/auto/qmltest/tst_qmltest.cpp @@ -0,0 +1,20 @@ +/**************************************************************************** +** +** 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 <QtQuickTest/quicktest.h> +QUICK_TEST_MAIN(qmltest) diff --git a/tests/barstest/barstest.pro b/tests/barstest/barstest.pro index f213ea9e..56e24ef2 100644 --- a/tests/barstest/barstest.pro +++ b/tests/barstest/barstest.pro @@ -5,4 +5,6 @@ SOURCES += main.cpp chart.cpp custominputhandler.cpp HEADERS += chart.h custominputhandler.h +RESOURCES += barstest.qrc + QT += widgets diff --git a/tests/barstest/barstest.qrc b/tests/barstest/barstest.qrc new file mode 100644 index 00000000..f7237eb7 --- /dev/null +++ b/tests/barstest/barstest.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>shuttle.obj</file> + <file>shuttle.png</file> + </qresource> +</RCC> diff --git a/tests/barstest/chart.cpp b/tests/barstest/chart.cpp index d3c38d85..e468700e 100644 --- a/tests/barstest/chart.cpp +++ b/tests/barstest/chart.cpp @@ -26,6 +26,7 @@ #include <QtDataVisualization/q3dcamera.h> #include <QtDataVisualization/q3dtheme.h> #include <QtDataVisualization/q3dinputhandler.h> +#include <QtDataVisualization/qcustom3ditem.h> #include <QtCore/QTime> #include <QtCore/qmath.h> @@ -203,7 +204,7 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog) m_graph->activeTheme()->setFont(QFont("Times Roman", 20)); // Release and store the default input handler. - m_defaultInputHandler = m_graph->activeInputHandler(); + m_defaultInputHandler = static_cast<Q3DInputHandler *>(m_graph->activeInputHandler()); m_graph->releaseInputHandler(m_defaultInputHandler); m_graph->setActiveInputHandler(m_defaultInputHandler); @@ -241,7 +242,6 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog) QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, &GraphModifier::handleFpsChange); - resetTemperatureData(); } @@ -334,6 +334,9 @@ void GraphModifier::releaseSeries() void GraphModifier::flipViews() { m_graph->scene()->setSecondarySubviewOnTop(!m_graph->scene()->isSecondarySubviewOnTop()); + qDebug() << "secondary subview on top:" << m_graph->scene()->isSecondarySubviewOnTop(); + qDebug() << "point (50, 50) in primary subview:" << m_graph->scene()->isPointInPrimarySubView(QPoint(50, 50)); + qDebug() << "point (50, 50) in secondary subview:" << m_graph->scene()->isPointInSecondarySubView(QPoint(50, 50)); } void GraphModifier::createMassiveArray() @@ -626,33 +629,33 @@ void GraphModifier::changeTheme() m_graph->setActiveTheme(m_builtinTheme); switch (theme) { - case Q3DTheme::ThemeQt: - qDebug() << __FUNCTION__ << "ThemeQt"; - break; - case Q3DTheme::ThemePrimaryColors: - qDebug() << __FUNCTION__ << "ThemePrimaryColors"; - break; - case Q3DTheme::ThemeDigia: - qDebug() << __FUNCTION__ << "ThemeDigia"; - break; - case Q3DTheme::ThemeStoneMoss: - qDebug() << __FUNCTION__ << "ThemeStoneMoss"; - break; - case Q3DTheme::ThemeArmyBlue: - qDebug() << __FUNCTION__ << "ThemeArmyBlue"; - break; - case Q3DTheme::ThemeRetro: - qDebug() << __FUNCTION__ << "ThemeRetro"; - break; - case Q3DTheme::ThemeEbony: - qDebug() << __FUNCTION__ << "ThemeEbony"; - break; - case Q3DTheme::ThemeIsabelle: - qDebug() << __FUNCTION__ << "ThemeIsabelle"; - break; - default: - qDebug() << __FUNCTION__ << "Unknown theme"; - break; + case Q3DTheme::ThemeQt: + qDebug() << __FUNCTION__ << "ThemeQt"; + break; + case Q3DTheme::ThemePrimaryColors: + qDebug() << __FUNCTION__ << "ThemePrimaryColors"; + break; + case Q3DTheme::ThemeDigia: + qDebug() << __FUNCTION__ << "ThemeDigia"; + break; + case Q3DTheme::ThemeStoneMoss: + qDebug() << __FUNCTION__ << "ThemeStoneMoss"; + break; + case Q3DTheme::ThemeArmyBlue: + qDebug() << __FUNCTION__ << "ThemeArmyBlue"; + break; + case Q3DTheme::ThemeRetro: + qDebug() << __FUNCTION__ << "ThemeRetro"; + break; + case Q3DTheme::ThemeEbony: + qDebug() << __FUNCTION__ << "ThemeEbony"; + break; + case Q3DTheme::ThemeIsabelle: + qDebug() << __FUNCTION__ << "ThemeIsabelle"; + break; + default: + qDebug() << __FUNCTION__ << "Unknown theme"; + break; } if (++theme > Q3DTheme::ThemeIsabelle) @@ -1128,7 +1131,7 @@ void GraphModifier::changeValueAxisFormat(const QString & text) void GraphModifier::changeLogBase(const QString &text) { QLogValue3DAxisFormatter *formatter = - qobject_cast<QLogValue3DAxisFormatter *>(m_graph->valueAxis()->formatter()); + qobject_cast<QLogValue3DAxisFormatter *>(m_graph->valueAxis()->formatter()); if (formatter) formatter->setBase(qreal(text.toDouble())); } @@ -1406,6 +1409,26 @@ void GraphModifier::reverseValueAxis(int enabled) m_graph->valueAxis()->setReversed(enabled); } +void GraphModifier::setInputHandlerRotationEnabled(int enabled) +{ + m_defaultInputHandler->setRotationEnabled(enabled); +} + +void GraphModifier::setInputHandlerZoomEnabled(int enabled) +{ + m_defaultInputHandler->setZoomEnabled(enabled); +} + +void GraphModifier::setInputHandlerSelectionEnabled(int enabled) +{ + m_defaultInputHandler->setSelectionEnabled(enabled); +} + +void GraphModifier::setInputHandlerZoomAtTargetEnabled(int enabled) +{ + m_defaultInputHandler->setZoomAtTargetEnabled(enabled); +} + void GraphModifier::changeValueAxisSegments(int value) { qDebug() << __FUNCTION__ << value; @@ -1480,6 +1503,42 @@ void GraphModifier::handleFpsChange(qreal fps) m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps))); } +void GraphModifier::setCameraTargetX(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setX(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setCameraTargetY(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setY(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setCameraTargetZ(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setZ(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setFloorLevel(int value) +{ + m_graph->setFloorLevel(float(value)); + qDebug() << "Floor level:" << value; +} + +void GraphModifier::setGraphMargin(int value) +{ + m_graph->setMargin(qreal(value) / 100.0); + qDebug() << "Setting margin:" << m_graph->margin() << value; +} + void GraphModifier::populateFlatSeries(QBar3DSeries *series, int rows, int columns, float value) { QBarDataArray *dataArray = new QBarDataArray; @@ -1673,3 +1732,44 @@ void GraphModifier::toggleMultiseriesScaling() { m_graph->setMultiSeriesUniform(!m_graph->isMultiSeriesUniform()); } + +void GraphModifier::setReflection(bool enabled) +{ + m_graph->setReflection(enabled); +} + +void GraphModifier::setReflectivity(int value) +{ + qreal reflectivity = (qreal)value / 100.0; + m_graph->setReflectivity(reflectivity); +} + +void GraphModifier::toggleCustomItem() +{ + static int counter = 0; + int state = ++counter % 3; + + QVector3D positionOne = QVector3D(6.0f, -15.0f, 3.0f); + QVector3D positionTwo = QVector3D(2.0f, 18.0f, 3.0f); + + if (state == 0) { + m_graph->removeCustomItemAt(positionTwo); + } else if (state == 1) { + QCustom3DItem *item = new QCustom3DItem(); + item->setMeshFile(":/shuttle.obj"); + item->setPosition(positionOne); + item->setScaling(QVector3D(0.1f, 0.1f, 0.1f)); + item->setRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, rand())); + item->setTextureImage(QImage(":/shuttle.png")); + m_graph->addCustomItem(item); + } else { + m_graph->removeCustomItemAt(positionOne); + QCustom3DItem *item = new QCustom3DItem(); + item->setMeshFile(":/shuttle.obj"); + item->setPosition(positionTwo); + item->setScaling(QVector3D(0.1f, 0.1f, 0.1f)); + item->setRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, rand())); + item->setTextureImage(QImage(":/shuttle.png")); + m_graph->addCustomItem(item); + } +} diff --git a/tests/barstest/chart.h b/tests/barstest/chart.h index 47d29c25..9713d06c 100644 --- a/tests/barstest/chart.h +++ b/tests/barstest/chart.h @@ -20,7 +20,7 @@ #define CHARTMODIFIER_H #include <QtDataVisualization/q3dbars.h> -#include <QtDataVisualization/qabstract3dinputhandler.h> +#include <QtDataVisualization/q3dinputhandler.h> #include <QtDataVisualization/qbar3dseries.h> #include <QtDataVisualization/q3dtheme.h> #include <QFont> @@ -94,6 +94,13 @@ public: void addRemoveSeries(); void testItemAndRowChanges(); void reverseValueAxis(int enabled); + void setInputHandlerRotationEnabled(int enabled); + void setInputHandlerZoomEnabled(int enabled); + void setInputHandlerSelectionEnabled(int enabled); + void setInputHandlerZoomAtTargetEnabled(int enabled); + void setReflection(bool enabled); + void setReflectivity(int value); + void toggleCustomItem(); public slots: void flipViews(); @@ -115,6 +122,11 @@ public slots: void triggerRotation(); void handleValueAxisLabelsChanged(); void handleFpsChange(qreal fps); + void setCameraTargetX(int value); + void setCameraTargetY(int value); + void setCameraTargetZ(int value); + void setFloorLevel(int value); + void setGraphMargin(int value); signals: void shadowQualityChanged(int quality); @@ -159,7 +171,7 @@ private: QValue3DAxis *m_currentAxis; bool m_negativeValuesOn; bool m_useNullInputHandler; - QAbstract3DInputHandler *m_defaultInputHandler; + Q3DInputHandler *m_defaultInputHandler; Q3DTheme *m_ownTheme; Q3DTheme *m_builtinTheme; QTimer m_insertRemoveTimer; @@ -169,6 +181,7 @@ private: QTimer m_rotationTimer; QLabel *m_fpsLabel; QBar3DSeries *m_extraSeries; + QVector3D m_cameraTarget; }; #endif diff --git a/tests/barstest/main.cpp b/tests/barstest/main.cpp index 5ecf63a4..af033990 100644 --- a/tests/barstest/main.cpp +++ b/tests/barstest/main.cpp @@ -34,6 +34,18 @@ #include <QColorDialog> #include <QLineEdit> #include <QSpinBox> +#include <QtGui/QOpenGLContext> + +static bool isOpenGLES() +{ +#if defined(QT_OPENGL_ES_2) + return true; +#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) + return false; +#else + return QOpenGLContext::currentContext()->isOpenGLES(); +#endif +} int main(int argc, char **argv) { @@ -43,13 +55,13 @@ int main(int argc, char **argv) QHBoxLayout *hLayout = new QHBoxLayout(widget); QVBoxLayout *vLayout = new QVBoxLayout(); QVBoxLayout *vLayout2 = new QVBoxLayout(); + QVBoxLayout *vLayout3 = new QVBoxLayout(); // For testing custom surface format QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); -#if !defined(QT_OPENGL_ES_2) - surfaceFormat.setSamples(8); -#endif + if (!isOpenGLES()) + surfaceFormat.setSamples(8); Q3DBars *widgetchart = new Q3DBars(&surfaceFormat); QSize screenSize = widgetchart->screen()->size(); @@ -65,6 +77,7 @@ int main(int argc, char **argv) hLayout->addWidget(container, 1); hLayout->addLayout(vLayout); hLayout->addLayout(vLayout2); + hLayout->addLayout(vLayout3); QPushButton *addSeriesButton = new QPushButton(widget); addSeriesButton->setText(QStringLiteral("Add / Remove a series")); @@ -220,6 +233,22 @@ int main(int argc, char **argv) staticCheckBox->setText("Use dynamic data"); staticCheckBox->setChecked(false); + QCheckBox *inputHandlerRotationCheckBox = new QCheckBox(widget); + inputHandlerRotationCheckBox->setText("IH: Allow rotation"); + inputHandlerRotationCheckBox->setChecked(true); + + QCheckBox *inputHandlerZoomCheckBox = new QCheckBox(widget); + inputHandlerZoomCheckBox->setText("IH: Allow zoom"); + inputHandlerZoomCheckBox->setChecked(true); + + QCheckBox *inputHandlerSelectionCheckBox = new QCheckBox(widget); + inputHandlerSelectionCheckBox->setText("IH: Allow selection"); + inputHandlerSelectionCheckBox->setChecked(true); + + QCheckBox *inputHandlerZoomAtTargetCheckBox = new QCheckBox(widget); + inputHandlerZoomAtTargetCheckBox->setText("IH: setZoomAtTarget"); + inputHandlerZoomAtTargetCheckBox->setChecked(true); + QSlider *rotationSliderX = new QSlider(Qt::Horizontal, widget); rotationSliderX->setTickInterval(1); rotationSliderX->setMinimum(-180); @@ -239,6 +268,23 @@ int main(int argc, char **argv) ratioSlider->setValue(30); ratioSlider->setMaximum(100); + QCheckBox *reflectionCheckBox = new QCheckBox(widget); + reflectionCheckBox->setText(QStringLiteral("Show reflections")); + reflectionCheckBox->setChecked(false); + + QSlider *reflectivitySlider = new QSlider(Qt::Horizontal, widget); + reflectivitySlider->setMinimum(0); + reflectivitySlider->setValue(50); + reflectivitySlider->setMaximum(100); + + QSlider *floorLevelSlider = new QSlider(Qt::Horizontal, widget); + floorLevelSlider->setMinimum(-50); + floorLevelSlider->setValue(0); + floorLevelSlider->setMaximum(50); + + QPushButton *toggleCustomItemButton = new QPushButton(widget); + toggleCustomItemButton->setText(QStringLiteral("Toggle Custom Item")); + QSlider *spacingSliderX = new QSlider(Qt::Horizontal, widget); spacingSliderX->setTickInterval(1); spacingSliderX->setMinimum(0); @@ -317,6 +363,27 @@ int main(int argc, char **argv) valueAxisSegmentsSpin->setMaximum(100); valueAxisSegmentsSpin->setValue(10); + QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderX->setTickInterval(1); + cameraTargetSliderX->setMinimum(-100); + cameraTargetSliderX->setValue(0); + cameraTargetSliderX->setMaximum(100); + QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderY->setTickInterval(1); + cameraTargetSliderY->setMinimum(-100); + cameraTargetSliderY->setValue(0); + cameraTargetSliderY->setMaximum(100); + QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderZ->setTickInterval(1); + cameraTargetSliderZ->setMinimum(-100); + cameraTargetSliderZ->setValue(0); + cameraTargetSliderZ->setMaximum(100); + + QSlider *marginSlider = new QSlider(Qt::Horizontal, widget); + marginSlider->setMinimum(-1); + marginSlider->setValue(-1); + marginSlider->setMaximum(100); + vLayout->addWidget(addSeriesButton, 0, Qt::AlignTop); vLayout->addWidget(addDataButton, 0, Qt::AlignTop); vLayout->addWidget(addMultiDataButton, 0, Qt::AlignTop); @@ -340,15 +407,15 @@ int main(int argc, char **argv) vLayout->addWidget(insertRemoveTestButton, 0, Qt::AlignTop); vLayout->addWidget(releaseAxesButton, 0, Qt::AlignTop); vLayout->addWidget(releaseProxiesButton, 1, Qt::AlignTop); - vLayout->addWidget(flipViewsButton, 0, Qt::AlignTop); - vLayout->addWidget(changeColorStyleButton, 0, Qt::AlignTop); - vLayout->addWidget(ownThemeButton, 0, Qt::AlignTop); - vLayout->addWidget(primarySeriesTestsButton, 0, Qt::AlignTop); - vLayout->addWidget(toggleRotationButton, 0, Qt::AlignTop); - vLayout->addWidget(gradientBtoYPB, 0, Qt::AlignTop); - vLayout->addWidget(logAxisButton, 0, Qt::AlignTop); - vLayout->addWidget(testItemAndRowChangesButton, 1, Qt::AlignTop); + vLayout2->addWidget(flipViewsButton, 0, Qt::AlignTop); + vLayout2->addWidget(changeColorStyleButton, 0, Qt::AlignTop); + vLayout2->addWidget(ownThemeButton, 0, Qt::AlignTop); + vLayout2->addWidget(primarySeriesTestsButton, 0, Qt::AlignTop); + vLayout2->addWidget(toggleRotationButton, 0, Qt::AlignTop); + vLayout2->addWidget(gradientBtoYPB, 0, Qt::AlignTop); + vLayout2->addWidget(logAxisButton, 0, Qt::AlignTop); + vLayout2->addWidget(testItemAndRowChangesButton, 0, Qt::AlignTop); vLayout2->addWidget(staticCheckBox, 0, Qt::AlignTop); vLayout2->addWidget(rotationCheckBox, 0, Qt::AlignTop); vLayout2->addWidget(rotationSliderX, 0, Qt::AlignTop); @@ -365,25 +432,40 @@ int main(int argc, char **argv) vLayout2->addWidget(minSliderX, 0, Qt::AlignTop); vLayout2->addWidget(minSliderZ, 0, Qt::AlignTop); vLayout2->addWidget(minSliderY, 0, Qt::AlignTop); - vLayout2->addWidget(maxSliderY, 0, Qt::AlignTop); - vLayout2->addWidget(fpsLabel, 0, Qt::AlignTop); - vLayout2->addWidget(fpsCheckBox, 0, Qt::AlignTop); - vLayout2->addWidget(reverseValueAxisCheckBox, 0, Qt::AlignTop); - vLayout2->addWidget(backgroundCheckBox, 0, Qt::AlignTop); - vLayout2->addWidget(gridCheckBox, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")), 0, Qt::AlignTop); - vLayout2->addWidget(shadowQuality, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Change font")), 0, Qt::AlignTop); - vLayout2->addWidget(fontList, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Adjust font size")), 0, Qt::AlignTop); - vLayout2->addWidget(fontSizeSlider, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Value axis format")), 0, Qt::AlignTop); - vLayout2->addWidget(valueAxisFormatEdit, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Log axis base")), 0, Qt::AlignTop); - vLayout2->addWidget(logBaseEdit, 0, Qt::AlignTop); - vLayout2->addWidget(new QLabel(QStringLiteral("Value axis segments")), 0, Qt::AlignTop); - vLayout2->addWidget(valueAxisSegmentsSpin, 0, Qt::AlignTop); - // TODO: Add example for setMeshFileName + vLayout2->addWidget(maxSliderY, 1, Qt::AlignTop); + + vLayout3->addWidget(fpsLabel, 0, Qt::AlignTop); + vLayout3->addWidget(fpsCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(reverseValueAxisCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(backgroundCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(gridCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(inputHandlerRotationCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(inputHandlerZoomCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(inputHandlerSelectionCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(inputHandlerZoomAtTargetCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")), 0, Qt::AlignTop); + vLayout3->addWidget(shadowQuality, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Change font")), 0, Qt::AlignTop); + vLayout3->addWidget(fontList, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Adjust font size")), 0, Qt::AlignTop); + vLayout3->addWidget(fontSizeSlider, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Value axis format")), 0, Qt::AlignTop); + vLayout3->addWidget(valueAxisFormatEdit, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Log axis base")), 0, Qt::AlignTop); + vLayout3->addWidget(logBaseEdit, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Value axis segments")), 0, Qt::AlignTop); + vLayout3->addWidget(valueAxisSegmentsSpin, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Camera target")), 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderX, 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderY, 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderZ, 0, Qt::AlignTop); + vLayout3->addWidget(reflectionCheckBox, 0, Qt::AlignTop); + vLayout3->addWidget(reflectivitySlider, 0, Qt::AlignTop); + vLayout3->addWidget(toggleCustomItemButton, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Adjust floor level")), 0, Qt::AlignTop); + vLayout3->addWidget(floorLevelSlider, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop); + vLayout3->addWidget(marginSlider, 1, Qt::AlignTop); widget->show(); @@ -411,6 +493,12 @@ int main(int argc, char **argv) &GraphModifier::setMinY); QObject::connect(maxSliderY, &QSlider::valueChanged, modifier, &GraphModifier::setMaxY); + QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetX); + QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetY); + QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetZ); QObject::connect(shadowQuality, SIGNAL(currentIndexChanged(int)), modifier, SLOT(changeShadowQuality(int))); @@ -488,7 +576,14 @@ int main(int argc, char **argv) &GraphModifier::setBackgroundEnabled); QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier, &GraphModifier::setGridEnabled); - + QObject::connect(inputHandlerRotationCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setInputHandlerRotationEnabled); + QObject::connect(inputHandlerZoomCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setInputHandlerZoomEnabled); + QObject::connect(inputHandlerSelectionCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setInputHandlerSelectionEnabled); + QObject::connect(inputHandlerZoomAtTargetCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setInputHandlerZoomAtTargetEnabled); QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, modifier, &GraphModifier::setUseNullInputHandler); @@ -501,6 +596,17 @@ int main(int argc, char **argv) QObject::connect(rotationCheckBox, &QCheckBox::stateChanged, rotationSliderY, &QSlider::setValue); + QObject::connect(reflectionCheckBox, &QCheckBox::stateChanged, modifier, + &GraphModifier::setReflection); + QObject::connect(reflectivitySlider, &QSlider::valueChanged, modifier, + &GraphModifier::setReflectivity); + QObject::connect(floorLevelSlider, &QSlider::valueChanged, modifier, + &GraphModifier::setFloorLevel); + QObject::connect(marginSlider, &QSlider::valueChanged, modifier, + &GraphModifier::setGraphMargin); + QObject::connect(toggleCustomItemButton, &QPushButton::clicked, modifier, + &GraphModifier::toggleCustomItem); + QObject::connect(staticCheckBox, &QCheckBox::stateChanged, addDataButton, &QPushButton::setEnabled); QObject::connect(staticCheckBox, &QCheckBox::stateChanged, addMultiDataButton, diff --git a/tests/barstest/shuttle.obj b/tests/barstest/shuttle.obj new file mode 100644 index 00000000..e872228d --- /dev/null +++ b/tests/barstest/shuttle.obj @@ -0,0 +1,6349 @@ +# Blender v2.66 (sub 0) OBJ File: '' +# www.blender.org +v -0.290924 -0.416888 -3.733797 +v -0.033253 -0.399965 -3.916942 +v 0.098892 -0.335648 -4.014968 +v 0.110144 -0.419894 -4.016005 +v -0.014627 -0.351984 -3.919619 +v -0.003375 -0.436229 -3.920655 +v 0.077510 -0.338845 -3.987319 +v 0.088762 -0.423090 -3.988356 +v 0.268419 -0.311615 -4.127978 +v 0.279672 -0.395861 -4.129015 +v -0.216945 -0.357152 -3.636585 +v -0.442480 -0.421494 -3.440589 +v 0.040557 -0.350366 -3.819523 +v -0.366883 -0.356351 -3.352865 +v -0.059636 -0.414591 -3.440213 +v 0.195164 -0.388461 -3.624492 +v -0.508658 -0.419445 -3.066802 +v -0.205255 -0.426666 -3.173799 +v -0.446409 -0.327120 -2.985436 +v -0.301084 -0.444031 -2.817294 +v 0.151449 -0.262081 -3.165398 +v 0.405315 -0.264481 -3.348743 +v 0.818570 -0.244023 -3.649532 +v 0.829822 -0.328268 -3.650568 +v 0.438945 -0.298561 -3.337861 +v 0.450197 -0.382806 -3.338897 +v 0.010751 -0.257630 -2.926187 +v 0.476024 -0.294278 -3.283418 +v 0.487276 -0.378524 -3.284455 +v 0.893196 -0.234640 -3.602006 +v 0.904448 -0.318886 -3.603042 +v -0.575146 -0.405155 -2.358083 +v -0.526846 -0.291701 -2.296914 +v -0.123567 -0.205514 -2.586394 +v -0.401501 -0.443667 -2.166967 +v 0.438519 -0.375468 -2.805563 +v 0.688118 -0.342410 -2.991890 +v 0.304996 -0.393955 -2.597466 +v -0.651696 -0.393273 -1.762192 +v -0.615966 -0.265853 -1.716287 +v -0.263638 -0.153966 -1.992210 +v 0.143186 -0.430595 -2.279088 +v -0.512839 -0.438185 -1.613670 +v -0.710028 -0.388098 -1.448538 +v -0.679965 -0.261349 -1.410043 +v -0.813314 -0.388657 -1.206769 +v 0.751287 -0.156495 -2.401505 +v 0.998622 -0.165467 -2.585768 +v -0.031714 -0.447371 -1.755604 +v -0.583894 -0.434779 -1.325088 +v -0.408965 -0.117462 -1.482493 +v -0.790573 -0.268054 -1.169135 +v 0.625851 -0.150158 -2.234187 +v -1.051885 -0.397310 -0.885646 +v -1.031232 -0.284379 -0.851625 +v -0.707410 -0.437077 -1.088477 +v -1.334166 -0.408930 -0.570468 +v 0.404917 -0.087146 -1.938298 +v -0.498689 -0.116097 -1.212783 +v -1.314745 -0.307881 -0.538558 +v -0.959224 -0.446984 -0.774490 +v 1.798825 0.967755 -3.015114 +v 2.046406 0.986555 -3.209870 +v 1.795686 1.006585 -3.015357 +v 2.043267 1.025386 -3.210112 +v 1.806945 0.929656 -3.008470 +v 2.054526 0.948456 -3.203225 +v 1.797665 1.044451 -3.009186 +v 2.045245 1.063251 -3.203941 +v -0.218636 -0.452499 -1.294639 +v -0.644958 -0.132219 -0.977335 +v 1.804675 1.079696 -2.996872 +v 2.052256 1.098496 -3.191627 +v -1.248682 -0.457011 -0.466167 +v 1.840314 0.837931 -2.974904 +v 2.087895 0.856731 -3.169660 +v 1.646588 -0.144702 -2.733368 +v 1.657840 -0.228948 -2.734405 +v 1.737599 -0.131659 -2.805524 +v 1.748852 -0.215905 -2.806561 +v 1.819258 1.148577 -2.971684 +v 1.033279 0.836881 -2.319959 +v 1.031777 0.970096 -2.298396 +v 1.060381 0.758531 -2.296534 +v -0.899523 -0.158764 -0.676483 +v 0.945538 0.814187 -2.200939 +v 0.170080 -0.031614 -1.496071 +v 1.043881 1.032762 -2.270612 +v 0.966443 0.743790 -2.180778 +v 0.952423 0.936921 -2.181507 +v 1.858652 1.239977 -2.912781 +v -0.317238 -0.452926 -1.060344 +v -1.192492 -0.196285 -0.374663 +v 1.856681 0.583228 -2.822182 +v 2.018811 -0.094129 -2.886386 +v 0.967854 0.995533 -2.156932 +v 1.272977 -0.200327 -2.268122 +v 1.284228 -0.284573 -2.269158 +v 1.076506 1.117039 -2.211863 +v -0.484562 -0.458987 -0.834904 +v 1.848687 0.788896 -2.757349 +v 2.078178 -0.164712 -2.848704 +v 1.967059 0.229003 -2.794663 +v 1.967832 0.643840 -2.831535 +v 2.215413 0.662641 -3.026290 +v 0.936685 0.816251 -2.042840 +v 2.039912 0.250063 -2.839813 +v 1.003257 1.074974 -2.104949 +v 1.164321 0.598239 -2.181823 +v 0.950019 0.768391 -2.028775 +v 0.943810 0.900014 -2.031005 +v 2.521226 0.390336 -3.213021 +v 1.936351 1.129229 -2.824698 +v 2.183932 1.148030 -3.019454 +v 2.568967 0.404915 -3.250601 +v 1.055366 0.603082 -2.081103 +v 0.869869 0.465058 -1.923758 +v 0.936491 -0.173930 -1.917023 +v 2.705161 0.067033 -3.318110 +v 2.096944 0.264363 -2.850901 +v 1.957182 0.386704 -2.747152 +v 2.029313 0.077472 -2.774992 +v 0.955724 0.940103 -2.015164 +v 1.928438 1.372002 -2.811322 +v -0.765050 -0.471609 -0.536370 +v 2.030367 0.402476 -2.793896 +v 2.100080 0.103611 -2.820803 +v 2.514476 0.522913 -3.173254 +v 2.574949 0.263657 -3.196595 +v 2.562289 0.536088 -3.211256 +v 2.622122 0.279579 -3.234349 +v 2.598488 0.409527 -3.214962 +v -0.064938 0.006053 -1.100592 +v 2.088244 0.403277 -2.809050 +v 2.151782 0.130884 -2.833575 +v 2.199769 1.159790 -2.964559 +v 2.193588 1.233527 -2.965299 +v 2.221596 1.161935 -2.979915 +v 2.215481 1.234891 -2.980646 +v 2.136538 0.271664 -2.829406 +v 1.137830 1.220917 -2.137315 +v 0.872108 0.654248 -1.875077 +v 0.961190 -0.247342 -1.861737 +v 2.739614 0.051854 -3.275776 +v -2.850674 0.163164 1.082772 +v -2.774223 -0.444295 1.078949 +v -2.508742 -0.433973 0.871016 +v -2.566129 0.205717 0.857016 +v 0.982043 0.994775 -1.981001 +v -1.069794 -0.481435 -0.242789 +v 2.592936 0.518564 -3.182256 +v 2.642673 0.305340 -3.201453 +v -2.926099 0.145051 1.155134 +v -2.869277 -0.451273 1.165592 +v 1.009718 0.673075 -1.961110 +v 2.235091 1.170649 -2.961918 +v 1.045386 1.162465 -2.033586 +v 2.230007 1.231294 -2.962527 +v 2.325998 0.322675 -2.951759 +v 1.972627 1.185781 -2.753756 +v 2.129131 0.389939 -2.793774 +v 2.183229 0.158016 -2.814654 +v 2.227766 1.099078 -2.934829 +v 1.117407 -0.306283 -1.939579 +v 1.981589 1.144577 -2.746340 +v 1.973910 1.228192 -2.748031 +v 2.249297 1.101867 -2.950499 +v 2.210880 1.300532 -2.936848 +v 1.886303 0.951739 -2.651532 +v 2.165588 -0.207364 -2.762840 +v 1.360371 -0.269775 -2.128519 +v 2.232590 1.301186 -2.952497 +v 2.160973 0.274542 -2.798066 +v 0.937019 0.410104 -1.852872 +v 0.960798 0.265444 -1.858128 +v 2.041237 1.191461 -2.784518 +v 2.048705 1.157124 -2.778339 +v 2.042305 1.226803 -2.779748 +v 2.258118 1.120717 -2.937466 +v 2.320933 0.422137 -2.921925 +v 2.366302 0.227636 -2.939436 +v 1.976683 1.431691 -2.744228 +v 2.244230 1.286403 -2.939127 +v -3.086442 -0.474834 1.379359 +v 1.999431 1.110853 -2.726914 +v 1.985242 1.265353 -2.730038 +v -3.023720 -0.458462 1.334241 +v -3.024019 0.109883 1.282283 +v -2.844489 0.344693 1.122684 +v -2.737176 -0.511595 1.117650 +v -2.469550 -0.506414 0.908517 +v -2.551327 0.395732 0.889431 +v 1.000046 -0.329019 -1.811242 +v 2.154800 0.373104 -2.768372 +v 2.199881 0.179835 -2.785773 +v -2.914064 0.322285 1.189905 +v -2.834171 -0.518740 1.204847 +v 1.179604 1.267508 -2.087104 +v 2.051748 1.257770 -2.764754 +v 2.063573 1.129020 -2.762150 +v 2.080589 1.195258 -2.775463 +v -0.196284 0.002337 -0.889996 +v 1.013546 1.060481 -1.927888 +v -3.072142 -0.445996 1.396327 +v 2.603875 0.637948 -3.126857 +v 2.707508 0.193661 -3.166857 +v 2.085482 1.172761 -2.771414 +v 2.081289 1.218414 -2.772337 +v 1.075555 1.201415 -1.986324 +v 2.002330 0.508317 -2.645192 +v 2.127263 -0.027287 -2.693413 +v 2.556508 0.625865 -3.087951 +v 2.661251 0.176820 -3.128379 +v 2.074001 0.520013 -2.695353 +v 2.194747 0.002364 -2.741958 +v 1.000951 0.830916 -1.880131 +v 2.095224 1.154349 -2.760807 +v 2.087476 1.238703 -2.762513 +v 2.023438 1.089743 -2.698434 +v 2.004898 1.291606 -2.702516 +v -3.122983 -0.461461 1.459483 +v -3.158215 -0.041663 1.448402 +v -2.497997 0.004146 0.929450 +v -2.509563 0.149734 0.925099 +v 1.003036 0.858653 -1.877204 +v 2.270079 1.067661 -2.884072 +v 1.005703 0.814744 -1.874867 +v 2.291162 1.070783 -2.900280 +v 2.128013 0.510403 -2.719236 +v 2.238064 0.038604 -2.761713 +v 2.068128 1.279648 -2.741818 +v 2.083579 1.111428 -2.738417 +v 2.247011 1.342852 -2.886831 +v 2.268338 1.343057 -2.903010 +v -2.993610 -0.524459 1.373357 +v -2.994892 0.277606 1.300703 +v 2.627505 0.603237 -3.112099 +v 2.713651 0.233920 -3.145348 +v 2.292918 1.094878 -2.895721 +v 1.006938 0.871768 -1.872413 +v 2.105035 1.197866 -2.757530 +v 2.093582 1.187722 -2.747088 +v 2.092232 1.203828 -2.747250 +v 2.273946 1.321208 -2.897990 +v 0.912103 0.803812 -1.788658 +v 1.019079 -0.292614 -1.771378 +v 2.804159 0.046327 -3.194257 +v 2.107537 1.186366 -2.755460 +v 2.105394 1.209702 -2.755932 +v 2.245164 0.292374 -2.778011 +v 0.747334 -0.381459 -1.545246 +v 2.108331 1.142823 -2.745258 +v 2.098208 1.253036 -2.747486 +v 2.099697 1.174463 -2.740594 +v 2.096009 1.218463 -2.741035 +v 2.112516 1.176954 -2.750038 +v 2.108556 1.220074 -2.750911 +v 1.015678 0.889476 -1.861661 +v 2.162992 0.481149 -2.717304 +v 2.256692 0.079446 -2.753470 +v 1.035607 1.090416 -1.893256 +v 2.119216 1.171063 -2.742090 +v 2.114042 1.227400 -2.743229 +v -3.036469 -0.488219 1.432582 +v 1.026486 0.781917 -1.851175 +v 2.241119 0.356951 -2.758555 +v 2.270656 0.230325 -2.769956 +v -0.400720 -0.022989 -0.663568 +v 2.108939 1.167601 -2.729508 +v 2.352467 0.499374 -2.857929 +v 2.431048 0.162488 -2.888259 +v 2.103901 1.227706 -2.730110 +v 2.078094 0.869479 -2.669583 +v 2.325675 0.888279 -2.864338 +v 2.049954 1.084459 -2.665236 +v 2.029886 1.302955 -2.669655 +v 2.105674 1.107026 -2.710752 +v 2.088951 1.289105 -2.714434 +v 2.107442 1.198835 -2.720560 +v 2.122808 1.139938 -2.727132 +v 2.111851 1.259233 -2.729544 +v 2.126617 1.169588 -2.732825 +v 2.121016 1.230568 -2.734058 +v 2.126010 1.200245 -2.735167 +v 2.126854 1.200309 -2.735831 +v -3.255541 -0.463337 1.614298 +v -3.256979 -0.068785 1.579187 +v -3.090151 -0.525402 1.492390 +v -3.140838 0.071873 1.477060 +v 2.183017 0.449113 -2.704647 +v 2.261101 0.114361 -2.734786 +v 1.025626 0.910940 -1.843900 +v 2.118830 1.168976 -2.716800 +v 2.113792 1.229081 -2.717403 +v 2.133590 1.172756 -2.723654 +v 2.128416 1.229093 -2.724793 +v -2.405642 -0.551479 0.981773 +v -2.505313 0.545503 0.958747 +v 1.032741 0.920593 -1.832253 +v 2.139077 1.180082 -2.715972 +v 2.135116 1.223202 -2.716844 +v 2.126722 1.178219 -2.705876 +v 2.123034 1.222219 -2.706317 +v 2.136451 1.146135 -2.709190 +v 2.126328 1.256348 -2.711419 +v 2.142239 1.190453 -2.710951 +v 2.140096 1.213790 -2.711423 +v 2.330166 1.100055 -2.847870 +v 2.130499 1.192853 -2.699662 +v 2.129149 1.208959 -2.699824 +v 2.142597 1.202290 -2.709353 +v 2.311194 1.326385 -2.850139 +v -0.678415 -0.058034 -0.389918 +v 2.126498 1.116483 -2.683367 +v 2.111047 1.284703 -2.686769 +v 2.335972 1.077011 -2.842714 +v 2.315368 1.073955 -2.825890 +v 2.297923 0.301154 -2.738834 +v 2.313149 1.349285 -2.845444 +v -3.224716 -0.523658 1.644234 +v -3.228977 0.037748 1.596002 +v 2.259607 0.406750 -2.716804 +v 2.310765 0.187427 -2.736550 +v 2.292301 1.349146 -2.828649 +v 1.051190 0.836263 -1.810548 +v 2.074942 1.095809 -2.632374 +v 2.056402 1.297672 -2.636457 +v 1.051411 0.836718 -1.810222 +v 1.051764 0.833314 -1.810103 +v 2.147183 1.160469 -2.694164 +v 2.139435 1.244822 -2.695870 +v 1.051937 0.833724 -1.809843 +v 1.051832 0.835286 -1.809826 +v 1.052011 0.834070 -1.809716 +v 1.052027 0.833906 -1.809712 +v 1.051999 0.834825 -1.809658 +v 1.051961 0.835391 -1.809651 +v 1.052122 0.834297 -1.809553 +v 2.289008 0.296555 -2.724424 +v 1.946327 1.084103 -2.529010 +v 2.264997 -0.237532 -2.656099 +v -3.146048 -0.468059 1.586396 +v 2.295856 0.334163 -2.728890 +v 2.310954 0.269436 -2.734717 +v 2.287534 0.325511 -2.715739 +v 2.300741 0.268886 -2.720837 +v 2.153370 1.180757 -2.684340 +v 2.149177 1.226410 -2.685263 +v -2.995660 -0.411552 1.480706 +v 1.055648 0.923143 -1.800017 +v 2.154070 1.203913 -2.681214 +v -0.987628 -0.106173 -0.106669 +v -3.134648 -0.466717 1.601017 +v 2.142878 1.138361 -2.660432 +v 2.131053 1.267111 -2.663036 +v 2.305306 0.359619 -2.707547 +v 2.331457 0.247507 -2.717641 +v 2.296714 0.347997 -2.697108 +v 2.319591 0.249920 -2.705938 +v 2.094598 1.122062 -2.604852 +v 2.080408 1.276562 -2.607976 +v 0.470274 -0.415629 -1.195124 +v 2.682583 0.683203 -3.020018 +v 2.802250 0.170184 -3.066206 +v 2.692932 0.640855 -3.023288 +v 2.792405 0.214405 -3.061682 +v 2.636060 0.671604 -2.979968 +v 2.757006 0.153092 -3.026651 +v 2.359883 1.134861 -2.806733 +v 2.152321 1.169328 -2.645438 +v 2.145921 1.239007 -2.646847 +v 2.345995 1.300547 -2.808394 +v 2.153390 1.204670 -2.640668 +v 2.412149 0.533689 -2.776916 +v 2.502886 0.144687 -2.811939 +v 0.970224 0.925229 -1.686331 +v 1.091354 -0.325301 -1.665935 +v 2.881699 0.045075 -3.095805 +v 2.371721 1.118882 -2.793227 +v 2.351501 1.116275 -2.775872 +v -3.112347 -0.464090 1.629621 +v 2.105930 1.159222 -2.586859 +v 2.098250 1.242837 -2.588550 +v 2.355013 1.318201 -2.795225 +v 2.334614 1.317729 -2.777893 +v 2.314089 0.357987 -2.673523 +v 2.340505 0.244738 -2.683719 +v 1.104303 1.101391 -1.798523 +v 2.090404 0.561258 -2.516102 +v 2.159124 0.571179 -2.570591 +v 2.234664 -0.057203 -2.571783 +v 2.205596 0.557038 -2.605525 +v 2.229047 0.520855 -2.620486 +v 2.238063 0.482200 -2.623966 +v 2.298549 -0.026551 -2.624406 +v 2.332671 0.012252 -2.654572 +v 2.295672 0.428428 -2.663943 +v 2.328226 0.095662 -2.658767 +v 2.337243 0.057009 -2.662247 +v 2.319328 0.300003 -2.670594 +v 2.323741 0.370700 -2.680527 +v 2.354745 0.175176 -2.686744 +v 2.343450 0.307384 -2.690079 +v 2.353938 0.241245 -2.692182 +v 1.085896 0.905799 -1.761274 +v 2.107213 1.201633 -2.581135 +v 1.172914 1.208536 -1.853034 +v -3.524472 -0.463065 1.962955 +v -3.542270 -0.167984 1.949735 +v 2.374104 1.189970 -2.783334 +v 2.369021 1.250615 -2.783942 +v 1.303689 1.278820 -1.940955 +v -2.749923 0.604098 1.287890 +v -2.601352 -0.583194 1.281085 +v -2.331104 -0.584296 1.070484 +v -2.444929 0.666788 1.044342 +v 2.335002 0.352805 -2.651304 +v 2.357880 0.254727 -2.660134 +v -2.812832 0.575628 1.346300 +v -2.702155 -0.590712 1.367115 +v 2.388829 1.185178 -2.765078 +v 2.382714 1.258133 -2.765809 +v 2.346222 0.364438 -2.655067 +v 2.372374 0.252327 -2.665161 +v 2.368793 1.183281 -2.747422 +v 2.362612 1.257019 -2.748161 +v -3.502416 -0.514638 1.987533 +v -3.528153 -0.092777 1.968860 +v -2.930290 -0.491180 1.547024 +v 2.066685 0.851305 -2.470213 +v -3.080119 -0.460294 1.670958 +v 2.353852 0.333838 -2.636405 +v 2.367060 0.277213 -2.641503 +v 2.140192 0.869385 -2.515599 +v -2.873451 -0.595312 1.528083 +v -2.875654 0.517249 1.427634 +v 2.123721 1.449962 -2.555544 +v 2.366726 0.342510 -2.637991 +v 2.381824 0.277783 -2.643818 +v 2.365586 0.306170 -2.632818 +v 0.180044 -0.433739 -0.860263 +v 2.390810 0.196854 -2.633883 +v 2.339653 0.416178 -2.614137 +v 2.379757 0.310792 -2.633873 +v -2.972414 -0.594417 1.632153 +v -3.043199 0.236465 1.611041 +v 2.197619 0.881876 -2.526829 +v 1.155619 0.140929 -1.644002 +v 2.061041 1.010198 -2.426111 +v 2.124709 0.698580 -2.447134 +v 2.629037 1.066507 -2.869623 +v 2.771686 0.621339 -2.939622 +v 2.857832 0.252023 -2.972872 +v 2.676305 1.087418 -2.904726 +v 2.483987 0.515887 -2.700598 +v 2.562568 0.179001 -2.730927 +v 2.134736 1.022953 -2.472975 +v 2.196270 0.721780 -2.493294 +v -3.115630 -0.588979 1.772415 +v -3.122642 0.192029 1.706162 +v 2.305190 0.463503 -2.547947 +v 2.383274 0.128751 -2.578085 +v 2.026136 1.180204 -2.395136 +v 2.372061 -0.253901 -2.533144 +v 0.043832 -0.439397 -0.700086 +v -2.331525 -0.123197 1.132486 +v 2.610039 1.166097 -2.837585 +v 2.673477 0.969915 -2.869029 +v 2.777325 0.659725 -2.919367 +v 2.880958 0.215439 -2.959367 +v 2.657509 1.185952 -2.873027 +v 2.720273 0.991850 -2.904138 +v 2.379762 0.373280 -2.580731 +v 2.409298 0.246654 -2.592131 +v 2.731814 0.647876 -2.878239 +v 2.836558 0.198831 -2.918667 +v -3.039373 -0.455495 1.723220 +v 2.192646 1.021841 -2.487980 +v 2.248729 0.747345 -2.506499 +v 2.309599 0.498417 -2.529263 +v 2.403300 0.096714 -2.565429 +v 2.237351 0.888547 -2.505383 +v -0.183302 -0.450597 -0.489896 +v 2.405254 0.311230 -2.572676 +v 1.043932 1.013192 -1.572568 +v 1.174857 -0.343976 -1.550015 +v 2.968846 0.048153 -2.984724 +v 2.704206 1.090705 -2.868940 +v 1.193313 1.053879 -1.685613 +v 2.300203 0.530684 -2.498384 +v 2.410253 0.058885 -2.540860 +v -2.870028 -0.379944 1.617982 +v -0.502937 -0.466927 -0.212460 +v 2.262925 0.542265 -2.453038 +v 2.383671 0.024616 -2.499642 +v 2.688581 1.172611 -2.842590 +v 2.740755 1.011263 -2.868450 +v 2.233117 1.007717 -2.472307 +v 2.280868 0.774003 -2.488075 +v -2.681424 0.688036 1.389168 +v -2.372814 0.754289 1.142473 +v -2.249194 -0.603433 1.170773 +v 2.433319 0.963376 -2.621027 +v -2.742923 0.657642 1.444079 +v 2.197806 0.531341 -2.394472 +v 2.322739 -0.004263 -2.442693 +v -3.420844 -0.571247 2.087621 +v -3.456852 0.016609 2.061712 +v 2.261785 0.891425 -2.474043 +v -0.828435 -0.477984 0.061043 +v 2.548733 0.450740 -2.649420 +v 2.594102 0.256238 -2.666930 +v 2.619376 1.253520 -2.777794 +v 2.736592 0.891025 -2.835893 +v 2.666746 1.272449 -2.813868 +v 2.782720 0.913795 -2.871353 +v -2.802757 0.594778 1.513184 +v 2.842664 0.549920 -2.883518 +v 2.892401 0.336695 -2.902714 +v 2.419066 1.038090 -2.596991 +v 2.466659 0.890910 -2.620581 +v 2.366409 0.398028 -2.496960 +v 2.411491 0.204759 -2.514360 +v -3.768498 -0.285858 2.351028 +v -3.754395 -0.436849 2.353898 +v -2.979417 0.290761 1.695520 +v 2.258257 0.990733 -2.446479 +v 2.298049 0.795972 -2.459619 +v -3.744207 -0.468465 2.368351 +v -3.763825 -0.250991 2.363676 +v -2.991889 -0.449902 1.784124 +v 2.589037 0.355701 -2.637096 +v -3.058914 0.242857 1.779885 +v 2.405318 0.303321 -2.484666 +v 2.886850 0.445733 -2.870009 +v 2.109286 1.132686 -2.326646 +v 2.219562 0.592947 -2.363060 +v 2.696261 1.244513 -2.793414 +v 2.792664 0.946380 -2.841198 +v 1.301631 1.132067 -1.690738 +v 2.383062 0.419848 -2.468079 +v 2.437160 0.187925 -2.488959 +v 2.181364 1.141334 -2.376844 +v 2.287943 0.619687 -2.412037 +v 2.862712 0.573808 -2.851874 +v 2.922544 0.317299 -2.874968 +v 2.818116 0.561038 -2.810024 +v 2.878590 0.301782 -2.833364 +v 2.426071 1.103677 -2.552133 +v 2.514010 0.831724 -2.595722 +v 2.235144 1.129736 -2.400364 +v 2.332283 0.654296 -2.432440 +v 2.346390 0.907367 -2.454136 +v -3.411176 0.052851 2.120724 +v 2.429752 0.306199 -2.453326 +v 2.269301 1.099582 -2.397708 +v 2.352008 0.694778 -2.425019 +v 2.386485 0.438404 -2.426523 +v 2.450023 0.166012 -2.451047 +v 2.915866 0.448472 -2.835623 +v 2.344078 0.972432 -2.436077 +v 2.370150 0.844828 -2.444686 +v 2.871840 0.434359 -2.793598 +v -2.786263 -0.483209 1.702896 +v 1.459973 1.193063 -1.759240 +v -3.703011 -0.503725 2.422886 +v -3.730082 -0.199896 2.416091 +v 2.655625 1.315469 -2.699349 +v 2.808775 0.841848 -2.775261 +v 2.702611 1.333742 -2.736255 +v 2.854139 0.865139 -2.811362 +v 2.288410 1.067288 -2.384314 +v 2.357333 0.729951 -2.407073 +v 2.357592 0.441017 -2.374193 +v 2.427305 0.142152 -2.401100 +v 1.807099 1.188967 -2.008747 +v -3.783568 -0.334437 2.490638 +v -3.776477 -0.407414 2.491810 +v 2.122243 1.235844 -2.255761 +v 2.482100 -0.255753 -2.399353 +v 2.726073 1.295463 -2.728897 +v 2.852031 0.905933 -2.791331 +v 2.295756 0.426582 -2.312892 +v 2.367887 0.117350 -2.340733 +v -2.939741 -0.443760 1.851008 +v -3.781106 -0.318610 2.496646 +v -3.771103 -0.424172 2.498541 +v 2.441322 0.304925 -2.409196 +v 2.453266 1.150154 -2.493283 +v 2.568163 0.794829 -2.550234 +v 1.130007 1.063856 -1.452342 +v 1.265938 -0.347821 -1.428686 +v 3.061789 0.055427 -2.865868 +v -2.602579 0.736106 1.497157 +v -2.292120 0.804178 1.248855 +v -2.434953 -0.604050 1.489534 +v -2.163494 -0.608053 1.278256 +v -2.664054 0.704644 1.549387 +v -2.539160 -0.611934 1.572914 +v 2.399283 0.915534 -2.415008 +v -3.707198 -0.182800 2.448233 +v 2.363834 1.022590 -2.395347 +v 2.408991 0.801573 -2.410258 +v -2.722515 -0.616791 1.721958 +v -2.725142 0.639161 1.608672 +v -2.829179 -0.615824 1.810674 +v -2.909245 0.322942 1.786891 +v 2.392228 0.914706 -2.400276 +v 2.398101 0.948794 -2.405777 +v 2.411428 0.883567 -2.410178 +v -2.983486 -0.609514 1.936485 +v -2.991769 0.272910 1.861910 +v 2.388079 0.936458 -2.393279 +v 2.401934 0.893610 -2.400146 +v 1.266405 1.148829 -1.537915 +v 1.276485 1.330618 -1.562468 +v 1.301959 1.399716 -1.588675 +v 1.829089 1.191557 -1.980541 +v 2.385908 3.043540 -2.584738 +v 2.432531 3.084452 -2.624845 +v 2.484531 3.138930 -2.670390 +v 2.555058 3.180480 -2.729192 +v 2.587869 1.699016 -2.618729 +v 2.897153 3.208163 -2.998451 +v 2.950947 3.055532 -3.026375 +v 2.946595 3.189529 -3.035288 +v 2.959600 3.134547 -3.040378 +v 2.417759 0.294566 -2.355182 +v -3.764574 -0.295328 2.521762 +v -3.750702 -0.443027 2.524510 +v 2.358011 0.275051 -2.293223 +v -3.320640 -0.590025 2.214050 +v -3.361390 0.074490 2.184798 +v 2.390117 0.955552 -2.380219 +v 2.415719 0.876379 -2.392909 +v 2.408200 0.974432 -2.384957 +v 2.431282 0.861455 -2.392579 +v -3.753472 -0.287501 2.537197 +v 2.398036 0.969083 -2.363085 +v 2.431485 0.865638 -2.379665 +v -3.651074 -0.516133 2.490283 +v -3.681587 -0.172421 2.482510 +v 2.311849 1.347830 -2.326247 +v -3.741101 -0.282712 2.553676 +v -3.725441 -0.449869 2.556817 +v 2.175858 1.240742 -2.187508 +v 1.176857 1.068079 -1.392566 +v 1.312589 -0.342230 -1.368881 +v -2.560177 0.739860 1.551322 +v -2.121265 -0.602981 1.332435 +v -2.249778 0.807875 1.303072 +v -2.622366 0.708349 1.602724 +v -2.686267 0.642614 1.658664 +v -2.872984 0.326449 1.833357 +v -2.958486 0.276123 1.904730 +v -3.335886 0.076990 2.217503 +v -3.668144 -0.171074 2.499706 +v -3.734634 -0.282056 2.561952 +v -3.734566 -0.369723 2.570150 +v -2.150712 -0.249451 1.324240 +v 1.267079 0.010084 -1.364303 +v 1.704978 -0.191159 -1.687236 +v 2.198494 1.185947 -2.198470 +v 2.267581 1.192809 -2.252964 +v 2.325830 0.562710 -2.240516 +v 2.313725 1.176653 -2.287457 +v 2.336207 1.139528 -2.301576 +v 2.434129 0.098705 -2.282343 +v 2.390649 0.590464 -2.293602 +v 2.344165 1.100576 -2.304204 +v 2.425891 0.627661 -2.324495 +v 2.423750 0.711053 -2.330483 +v 2.431708 0.672101 -2.333111 +v 2.400364 1.044399 -2.342861 +v 2.410625 0.974989 -2.344487 +v 2.420496 0.915260 -2.346699 +v 2.426872 0.985581 -2.358127 +v 2.452506 0.789191 -2.360079 +v 2.446831 0.863022 -2.362433 +v 2.444856 0.921557 -2.366269 +v 2.453526 0.855127 -2.366929 +v 2.496511 1.170443 -2.429398 +v 2.620876 0.785842 -2.491042 +v 2.713268 1.342514 -2.614195 +v 2.759644 1.360499 -2.652004 +v 2.773482 1.317705 -2.658864 +v 2.879037 0.829869 -2.696361 +v 2.909818 0.896081 -2.726441 +v 2.923656 0.853286 -2.733300 +v 2.434469 0.098745 -2.281906 +v -2.120000 -0.602832 1.334057 +v 1.313986 -0.342065 -1.367090 +v 2.435135 0.098824 -2.281052 +v 2.435757 0.098897 -2.280254 +v 2.436307 0.098962 -2.279548 +v 2.436763 0.099016 -2.278964 +v 2.436999 0.099043 -2.278662 +v -2.885212 -0.437337 1.920949 +v -2.717870 -0.356682 1.785125 +v -3.728000 -0.281169 2.570479 +v -3.712341 -0.448326 2.573620 +v -0.735546 -0.053125 0.219066 +v -0.406216 0.000394 -0.041418 +v -0.100273 0.040739 -0.281944 +v 0.172417 0.073341 -0.495491 +v 2.335518 1.350617 -2.295889 +v 0.354290 0.083209 -0.634777 +v -3.623852 -0.512926 2.525198 +v -3.654365 -0.169215 2.517424 +v 2.425971 0.972373 -2.327255 +v 2.459421 0.868929 -2.343834 +v 0.696977 0.054076 -0.892441 +v 2.948120 3.214165 -2.933081 +v 2.606057 3.186486 -2.663780 +v 2.997989 3.195582 -2.969370 +v 2.536222 3.145018 -2.604090 +v 3.012064 3.140727 -2.973088 +v 2.485188 3.090654 -2.557307 +v 1.046458 0.007453 -1.152257 +v -3.714744 -0.282939 2.586870 +v 2.439279 3.049827 -2.516283 +v 3.004908 3.061887 -2.957164 +v 2.449116 0.979252 -2.332477 +v 2.472199 0.866275 -2.340099 +v 1.368491 -0.052711 -1.393705 +v 1.717894 -0.070445 -1.662515 +v 1.478573 -0.058668 -1.476762 +v 2.441737 0.961632 -2.314012 +v 2.467338 0.882459 -2.326701 +v -3.309670 0.080582 2.251135 +v -3.268919 -0.583933 2.280387 +v 2.410137 0.281190 -2.226363 +v -3.701911 -0.287948 2.602134 +v -3.688039 -0.435647 2.604882 +v 2.482991 0.302250 -2.271514 +v 2.455522 0.944402 -2.306774 +v 2.469378 0.901553 -2.313642 +v -2.915858 -0.601549 2.023225 +v -2.924141 0.280876 1.948650 +v 2.468971 0.957141 -2.314878 +v 2.482299 0.891914 -2.319279 +v 2.465229 0.923305 -2.306645 +v -2.755682 -0.607167 1.904943 +v -2.835748 0.331599 1.881160 +v -2.646201 0.648459 1.709923 +v -2.643573 -0.607493 1.823210 +v 2.443879 1.032018 -2.292681 +v 2.489036 0.811001 -2.307592 +v -3.626724 -0.173321 2.551450 +v 2.481117 0.925173 -2.310048 +v -2.579668 0.714584 1.657621 +v -2.454774 -0.601995 1.681149 +v -2.516832 0.746206 1.607137 +v -2.349206 -0.593950 1.599513 +v -2.206373 0.814278 1.358835 +v -2.077747 -0.597953 1.388236 +v 2.667233 1.708364 -2.516935 +v 1.224686 1.075007 -1.330905 +v 1.360617 -0.336670 -1.307250 +v 3.156468 0.066579 -2.744432 +v 2.549224 1.161456 -2.370206 +v 2.664121 0.806131 -2.427157 +v 2.540023 0.316550 -2.282602 +v 1.385180 1.409518 -1.481935 +v -3.679715 -0.306668 2.626691 +v -3.669712 -0.412230 2.628586 +v 1.360989 1.340571 -1.454082 +v 2.400261 0.438891 -2.178853 +v 2.472391 0.129659 -2.206693 +v 2.831269 1.307854 -2.593973 +v 2.957226 0.918323 -2.656406 +v 2.230447 1.248588 -2.116977 +v 2.590304 -0.243009 -2.260568 +v 1.354370 1.159190 -1.425091 +v 1.917054 1.201918 -1.867717 +v -3.673127 -0.321429 2.632291 +v -3.666035 -0.394406 2.633464 +v -2.830682 -0.430915 1.990889 +v 2.473447 0.454663 -2.225597 +v 2.543159 0.155798 -2.252504 +v 2.410583 1.081677 -2.227614 +v 2.479506 0.744341 -2.250373 +v 3.001074 0.449623 -2.628313 +v 2.829160 1.348647 -2.573941 +v 2.980689 0.880044 -2.649048 +v 2.783530 1.330534 -2.535297 +v 2.936681 0.856913 -2.611208 +v 3.051332 0.464469 -2.662340 +v -3.572802 -0.488389 2.589894 +v -3.599872 -0.184558 2.583099 +v 1.591093 1.208507 -1.591064 +v 2.482720 0.988763 -2.258252 +v 2.508792 0.861158 -2.266861 +v 2.531323 0.455464 -2.240752 +v 2.594861 0.183071 -2.265276 +v 2.415908 1.116850 -2.209669 +v 2.498616 0.712046 -2.236978 +v 2.579618 0.323851 -2.261107 +v -2.645182 -0.466535 1.885712 +v 1.939045 1.204508 -1.839511 +v -3.258276 0.070860 2.316835 +v 2.506480 0.926224 -2.248802 +v 2.992383 0.581606 -2.586979 +v 3.052857 0.322350 -2.610320 +v 2.407334 1.150018 -2.179512 +v 2.504473 0.674577 -2.211588 +v 3.042733 0.595054 -2.621444 +v 3.102565 0.338545 -2.644537 +v 2.603378 1.124561 -2.324717 +v 2.691316 0.852608 -2.368305 +v 3.078396 0.468329 -2.624718 +v 2.370287 1.163586 -2.134528 +v 2.476867 0.641940 -2.169721 +v 2.572210 0.442126 -2.225475 +v 2.626308 0.210203 -2.246356 +v 1.491168 1.154391 -1.447635 +v 2.890636 1.267406 -2.544106 +v 2.987040 0.969274 -2.591890 +v 2.788228 0.379194 -2.381967 +v 2.304762 1.155709 -2.075926 +v 2.415038 0.615971 -2.112340 +v 2.604052 0.326729 -2.229767 +v -2.858987 0.266405 2.036315 +v -3.533523 -0.443650 2.638577 +v -3.553141 -0.226176 2.633901 +v 2.469866 1.015657 -2.175067 +v 2.509659 0.820896 -2.188207 +v -2.762138 0.316353 1.974206 +v -2.778535 -0.424773 2.057773 +v 3.071249 0.576878 -2.590723 +v 3.120985 0.363654 -2.609920 +v 2.781708 0.478212 -2.350957 +v 2.827077 0.283710 -2.368468 +v -3.539008 -0.258828 2.645375 +v -3.524905 -0.409819 2.648245 +v 2.597879 0.425291 -2.200073 +v 2.642961 0.232022 -2.217474 +v 2.650729 1.065375 -2.299858 +v 2.698322 0.918196 -2.323448 +v -2.569382 0.622266 1.812512 +v 2.900579 1.299991 -2.513951 +v 3.016554 0.941337 -2.571435 +v 2.855713 1.281357 -2.474664 +v 2.972929 0.918862 -2.532765 +v 2.506130 0.920204 -2.160643 +v -3.173446 -0.542107 2.404937 +v -3.209453 0.045748 2.379028 +v 2.445409 0.560504 -2.076893 +v 2.570342 0.024900 -2.125114 +v -2.493455 0.687025 1.764050 +v 2.684069 0.992910 -2.299412 +v -2.427931 0.717893 1.714301 +v -2.119321 0.784146 1.467606 +v -1.995701 -0.573576 1.495906 +v 2.487048 1.037626 -2.146613 +v 2.534799 0.803912 -2.162379 +v 2.942544 1.202524 -2.516853 +v 2.994718 1.041175 -2.542714 +v 2.517081 0.572200 -2.127054 +v 2.637826 0.054551 -2.173658 +v 2.571093 0.562590 -2.150938 +v 2.681143 0.090791 -2.193414 +v 1.466306 1.086034 -1.335468 +v 2.979094 1.123082 -2.516365 +v -0.568604 -0.447372 0.394570 +v 1.323830 1.046159 -1.213567 +v 1.454755 -0.311009 -1.191014 +v 3.248744 0.081120 -2.625723 +v 2.688243 0.344561 -2.209711 +v -2.593169 -0.347277 1.974947 +v 3.029114 0.682935 -2.497393 +v 3.133857 0.233890 -2.537821 +v 2.530565 0.923082 -2.129303 +v -0.224107 -0.434065 0.145862 +v 3.079074 0.695309 -2.532807 +v 3.182707 0.251022 -2.572807 +v 2.606071 0.533336 -2.149005 +v 2.699770 0.131633 -2.185170 +v 2.490887 1.056969 -2.105453 +v 2.546971 0.782472 -2.123972 +v 2.684199 0.409138 -2.190257 +v 2.713735 0.282512 -2.201657 +v 2.963027 1.221936 -2.481166 +v 3.025790 1.027834 -2.512276 +v 2.918829 1.202466 -2.441528 +v 2.982266 1.006286 -2.472972 +v -2.731051 -0.419180 2.118678 +v 2.346020 1.217882 -1.984849 +v 2.691945 -0.216224 -2.122858 +v 2.626097 0.501300 -2.136348 +v 2.704180 0.166548 -2.166486 +v 2.809263 0.554231 -2.283747 +v 2.887846 0.217345 -2.314077 +v -2.799153 0.230131 2.121073 +v -2.792140 -0.550877 2.187326 +v 0.122037 -0.414552 -0.095656 +v 3.101457 0.660216 -2.517043 +v 3.187603 0.290900 -2.550293 +v 2.461960 1.061494 -2.053272 +v 2.523494 0.760321 -2.073591 +v 3.006995 1.126368 -2.480578 +v 2.963268 1.105875 -2.440934 +v 2.399616 1.050076 -1.991851 +v 2.463283 0.738458 -2.012874 +v 2.541997 0.922437 -2.085123 +v -2.620849 -0.553009 2.083075 +v -2.691633 0.277873 2.061963 +v 0.380138 -0.399580 -0.262083 +v 2.741003 0.353341 -2.170536 +v 2.702686 0.458937 -2.148505 +v 2.753845 0.239614 -2.168251 +v 2.735783 0.349782 -2.158101 +v 2.738935 0.386350 -2.160591 +v 2.754033 0.321623 -2.166418 +v 2.501059 1.494406 -2.071566 +v -2.498046 0.561724 1.911958 +v -2.495843 -0.550836 2.012409 +v 2.518039 0.913888 -2.030968 +v 2.733885 0.378609 -2.149074 +v 2.747093 0.321983 -2.154171 +v 2.457638 0.897352 -1.968773 +v -2.690304 -0.414381 2.170940 +v -3.127855 -0.045628 2.482288 +v -3.102117 -0.467490 2.500962 +v 2.770798 1.227046 -2.232150 +v 2.748385 0.411806 -2.139249 +v 2.774536 0.299695 -2.149342 +v 2.764617 1.300784 -2.232889 +v 2.792626 1.229192 -2.247504 +v -2.409183 0.623172 1.864025 +v -2.298506 -0.543169 1.884841 +v 2.786510 1.302148 -2.248235 +v 2.741907 0.400740 -2.129507 +v 2.764784 0.302663 -2.138337 +v -2.529794 -0.443952 2.062569 +v -2.339763 0.652407 1.813967 +v -2.191192 -0.534884 1.807161 +v -1.920944 -0.535987 1.596561 +v -2.034769 0.715099 1.570418 +v -1.970078 -0.080625 1.596081 +v 1.720073 1.327862 -1.406896 +v 0.572047 -0.387414 -0.352526 +v 2.806120 1.237906 -2.229508 +v 2.801037 1.298551 -2.230116 +v -3.088440 -0.411708 2.522214 +v -3.106239 -0.116627 2.508994 +v 1.609069 1.259907 -1.293617 +v 2.543657 1.253039 -2.021345 +v 1.525799 0.957611 -1.197050 +v 2.757700 0.410246 -2.104644 +v 2.784117 0.296996 -2.114841 +v 2.533483 0.613445 -1.947803 +v 2.602202 0.623366 -2.002292 +v 2.677744 -0.005017 -2.003484 +v 2.648675 0.609224 -2.037226 +v 2.672127 0.573042 -2.052187 +v 2.681143 0.534388 -2.055668 +v 2.741627 0.025636 -2.056106 +v 2.775750 0.064439 -2.086273 +v 2.738751 0.480616 -2.095645 +v 2.771306 0.147849 -2.090468 +v 2.780322 0.109196 -2.093948 +v 2.762407 0.352190 -2.102295 +v 2.766821 0.422887 -2.112228 +v 2.797824 0.227363 -2.118445 +v 2.786530 0.359571 -2.121780 +v 2.797017 0.293432 -2.123883 +v 1.548968 1.153765 -1.228190 +v 2.798795 1.166335 -2.202419 +v 2.552618 1.211834 -2.013930 +v 2.544939 1.295449 -2.015621 +v 2.863513 0.586883 -2.198347 +v 2.954250 0.197881 -2.233369 +v 2.820327 1.169125 -2.218088 +v 2.781909 1.367789 -2.204438 +v 2.803619 1.368443 -2.220086 +v 1.423109 0.978571 -1.105456 +v 1.544240 -0.271959 -1.085059 +v 3.334585 0.098417 -2.514929 +v 1.554716 0.187936 -1.132116 +v 2.612265 1.258718 -2.052108 +v 3.101424 0.726458 -2.383560 +v 3.222370 0.207946 -2.430242 +v -2.658075 -0.410585 2.212276 +v 3.150617 0.738370 -2.420182 +v 3.270283 0.225352 -2.466369 +v 3.160928 0.696012 -2.423422 +v 3.260400 0.269561 -2.461816 +v 2.619734 1.224381 -2.045928 +v 2.613334 1.294060 -2.047338 +v 2.829147 1.187974 -2.205056 +v 2.815259 1.353660 -2.206717 +v 2.570461 1.178110 -1.994503 +v 2.556271 1.332609 -1.997628 +v 2.777033 0.404579 -2.081148 +v 2.799910 0.306502 -2.089978 +v 2.789302 0.416625 -2.086768 +v 2.815452 0.304514 -2.096862 +v 2.634603 1.196277 -2.029740 +v 2.622778 1.325027 -2.032343 +v 2.651619 1.262515 -2.043053 +v 1.554142 0.981857 -1.160642 +v 2.656512 1.240018 -2.039004 +v 2.652319 1.285671 -2.039927 +v -2.635774 -0.407958 2.240880 +v 2.794724 0.385259 -2.065313 +v 2.807933 0.328634 -2.070411 +v 2.809805 0.394697 -2.069692 +v 2.824903 0.329970 -2.075520 +v 2.806035 0.357461 -2.061383 +v -2.489068 -0.351828 2.132329 +v 2.463909 1.145066 -1.865152 +v 2.782581 -0.176570 -1.992240 +v 1.570340 0.895334 -1.144881 +v 1.570297 0.896443 -1.144827 +v 1.570363 0.895879 -1.144798 +v 1.570504 0.894974 -1.144707 +v 1.570489 0.895138 -1.144709 +v 1.570444 0.896371 -1.144649 +v 1.570624 0.894816 -1.144569 +v 2.666254 1.221606 -2.028398 +v 2.658506 1.305960 -2.030103 +v 1.570869 0.894455 -1.144293 +v 1.570594 0.897869 -1.144312 +v 2.594467 1.157000 -1.966023 +v 2.575927 1.358863 -1.970106 +v 1.570894 0.897475 -1.143969 +v 2.833890 0.249041 -2.065584 +v 2.782731 0.468365 -2.045837 +v -2.701300 -0.462008 2.315575 +v -2.705561 0.099398 2.267342 +v 2.841108 1.134918 -2.151662 +v -0.479044 -0.046272 0.545593 +v 2.862191 1.138039 -2.167869 +v 2.822836 0.362979 -2.065575 +v 2.654608 1.178685 -2.006007 +v 2.639158 1.346905 -2.009409 +v -2.624374 -0.406615 2.255502 +v 2.818040 1.410109 -2.154421 +v 2.839368 1.410315 -2.170599 +v 2.863947 1.162135 -2.163311 +v 2.676065 1.265123 -2.025119 +v 2.664612 1.254980 -2.014677 +v 2.663261 1.271085 -2.014839 +v 0.971816 -0.356390 -0.546463 +v 2.844975 1.388465 -2.165580 +v 2.678566 1.253623 -2.023050 +v 2.676423 1.276959 -2.023522 +v 2.679361 1.210080 -2.012848 +v 2.669238 1.320294 -2.015076 +v 2.670727 1.241720 -2.008183 +v 2.667038 1.285720 -2.008625 +v 2.683546 1.244211 -2.017628 +v 2.679586 1.287331 -2.018500 +v 1.579791 0.985026 -1.130600 +v -1.956412 0.610154 1.662774 +v -1.856740 -0.486828 1.685802 +v 2.690246 1.238319 -2.009680 +v 2.685071 1.294657 -2.010819 +v 2.679968 1.234858 -1.997098 +v 2.674930 1.294963 -1.997700 +v -0.132362 0.006287 0.310615 +v 1.590120 0.977428 -1.119874 +v 2.748270 0.515690 -1.979648 +v 2.826352 0.180938 -2.009786 +v -2.571993 0.138873 2.206668 +v -2.521306 -0.458402 2.221998 +v -2.685403 -0.396185 2.345565 +v -2.686840 -0.001633 2.310453 +v 2.620983 1.151716 -1.932826 +v 2.600915 1.370211 -1.937245 +v 2.676704 1.174282 -1.978342 +v 2.659981 1.356362 -1.982024 +v 2.678470 1.266092 -1.988150 +v 2.693838 1.207195 -1.994723 +v 2.682881 1.326489 -1.997135 +v 2.697646 1.236845 -2.000415 +v 2.692045 1.297825 -2.001648 +v 2.697040 1.267502 -2.002758 +v 2.651196 0.936979 -1.934514 +v 2.898778 0.955779 -2.129270 +v 2.929918 0.567420 -2.117638 +v 3.008498 0.230534 -2.147968 +v 2.689859 1.236233 -1.984390 +v 2.684821 1.296338 -1.984993 +v 2.822841 0.425467 -2.012432 +v 2.852378 0.298841 -2.023833 +v 1.608781 0.850501 -1.104316 +v 2.704620 1.240012 -1.991243 +v 2.699446 1.296350 -1.992383 +v 1.624309 1.159754 -1.138181 +v 2.752678 0.550604 -1.960964 +v 2.846378 0.148901 -1.997130 +v 1.606690 0.959087 -1.103622 +v 2.710106 1.247339 -1.983562 +v 2.706145 1.290459 -1.984434 +v 2.697752 1.245476 -1.973465 +v 2.694063 1.289476 -1.973907 +v 2.707481 1.213392 -1.976781 +v 2.697358 1.323606 -1.979010 +v -2.445962 -0.418610 2.191836 +v 3.233725 0.674674 -2.334944 +v 3.319870 0.305357 -2.368193 +v 2.848333 0.363417 -2.004377 +v 2.713269 1.257710 -1.978540 +v 2.711125 1.281046 -1.979012 +v 1.518181 0.875199 -1.011295 +v 1.625158 -0.221229 -0.994015 +v 3.410238 0.117713 -2.416893 +v 2.901195 1.167312 -2.115460 +v 2.701529 1.260111 -1.967251 +v 2.700178 1.276216 -1.967413 +v 2.713626 1.269547 -1.976943 +v 1.616466 0.943560 -1.090626 +v 2.882224 1.393643 -2.117728 +v -2.383910 0.349570 2.084356 +v -2.382627 -0.452496 2.157011 +v 2.697528 1.183740 -1.950957 +v 2.682078 1.351960 -1.954359 +v 2.907001 1.144267 -2.110304 +v 2.886398 1.141212 -2.093480 +v 2.743282 0.582871 -1.930085 +v 2.853333 0.111072 -1.972562 +v 1.623579 0.887518 -1.082372 +v 0.198902 0.047648 0.105888 +v 1.621282 0.931471 -1.084234 +v 2.884178 1.416542 -2.113034 +v 2.863331 1.416404 -2.096239 +v -2.503361 -0.388481 2.254219 +v -2.538594 0.031317 2.243138 +v -1.878374 0.077127 1.724185 +v -1.889940 0.222714 1.719833 +v 2.645971 1.163066 -1.899965 +v 2.627431 1.364929 -1.904047 +v 2.718213 1.227726 -1.961754 +v 2.710466 1.312080 -1.963460 +v 1.626047 0.904541 -1.078374 +v 3.189935 0.700514 -2.275981 +v 3.294679 0.251469 -2.316409 +v 3.238191 0.712702 -2.313742 +v 3.341825 0.268415 -2.353742 +v 2.706004 0.594452 -1.884739 +v 2.826750 0.076803 -1.931343 +v 2.640884 0.583528 -1.826173 +v 2.765818 0.047924 -1.874394 +v 1.714162 1.276631 -1.167239 +v 2.724400 1.248014 -1.951930 +v 2.720207 1.293667 -1.952853 +v 1.654904 1.136021 -1.105274 +v 2.725100 1.271170 -1.948805 +v 2.713907 1.205618 -1.928023 +v 2.702083 1.334368 -1.930626 +v 1.831320 1.344269 -1.251206 +v -2.260945 0.399211 2.027604 +v -2.181051 -0.441814 2.042546 +v 2.809488 0.450216 -1.928661 +v 2.854570 0.256946 -1.946061 +v -2.423567 -0.369548 2.230060 +v -2.180836 0.422861 1.973894 +v -2.073524 -0.433428 1.968859 +v -1.805897 -0.428247 1.759726 +v -1.887675 0.473899 1.740641 +v -2.358199 -0.380076 2.187846 +v -2.358499 0.188271 2.135889 +v 2.990686 0.501054 -2.063247 +v 3.036054 0.306554 -2.080758 +v 2.665627 1.189319 -1.872442 +v 2.651437 1.343818 -1.875567 +v 2.648270 1.510792 -1.882843 +v 2.930912 1.202118 -2.074323 +v 2.723351 1.236585 -1.913028 +v 2.716951 1.306264 -1.914438 +v 2.917024 1.367804 -2.075984 +v 1.384653 -0.306314 -0.725204 +v 2.724420 1.271928 -1.908258 +v 1.621186 0.490687 -0.975352 +v 1.644965 0.346027 -0.980608 +v 0.465380 0.080433 -0.036070 +v 2.848397 0.355508 -1.916368 +v -2.408185 -0.394890 2.251162 +v 2.942750 1.186138 -2.060817 +v 2.578963 1.033323 -1.763117 +v 2.858250 -0.125781 -1.874424 +v 2.922529 1.183532 -2.043463 +v 2.676959 1.226479 -1.854450 +v 2.669279 1.310094 -1.856141 +v 2.926042 1.385458 -2.062815 +v 2.826142 0.472035 -1.899780 +v 2.880239 0.240111 -1.920660 +v 2.905643 1.384987 -2.045482 +v 3.029535 0.405571 -2.049749 +v 3.300344 0.601920 -2.275317 +v 3.350079 0.388695 -2.294513 +v 2.678242 1.268890 -1.848725 +v 2.945134 1.257227 -2.050923 +v 1.755731 1.246131 -1.122488 +v 1.720327 0.756772 -1.049674 +v 2.940050 1.317871 -2.051532 +v -2.214680 0.228844 2.067608 +v -2.157859 -0.367480 2.078067 +v 1.704158 1.079827 -1.054807 +v -2.127781 0.248308 2.009962 +v -2.051331 -0.359151 2.006140 +v -1.843236 0.290860 1.784206 +v -1.785848 -0.348829 1.798208 +v 1.604892 0.740558 -0.935201 +v 1.693974 -0.161033 -0.921860 +v 3.472397 0.138163 -2.335899 +v 1.873536 1.307570 -1.193691 +v 2.872832 0.358386 -1.885027 +v 2.959859 1.252435 -2.032667 +v 2.953743 1.325390 -2.033399 +v 3.342932 0.497244 -2.260519 +v 2.939822 1.250538 -2.015012 +v 2.829564 0.490591 -1.858224 +v 2.893102 0.218199 -1.882748 +v 2.933641 1.324275 -2.015751 +v 1.701154 -0.246228 -0.905127 +v -0.334995 -0.394882 0.699940 +v 3.318333 0.625178 -2.242012 +v 3.378166 0.368670 -2.265106 +v 3.270936 0.612055 -2.203483 +v 3.331410 0.352798 -2.226824 +v 2.870384 0.194339 -1.832802 +v 2.800672 0.493204 -1.805894 +v 2.706330 1.463624 -1.813588 +v 1.735438 1.031940 -1.015094 +v 2.096574 -0.183066 -1.184359 +v 2.738836 0.478769 -1.744593 +v 2.810966 0.169537 -1.772433 +v 1.857375 -0.219119 -0.990222 +v 2.884402 0.357112 -1.840897 +v 3.369567 0.499255 -2.224210 +v 0.704277 0.096803 -0.109143 +v 3.322719 0.484781 -2.185490 +v 1.668063 0.559071 -0.899985 +v 1.734685 -0.079917 -0.893249 +v 3.503355 0.161047 -2.294336 +v 1.853604 0.697100 -1.057274 +v 0.023839 -0.378671 0.476160 +v 2.742703 1.224204 -1.790460 +v 2.990285 1.243004 -1.985215 +v 1.751320 0.995124 -0.995284 +v 1.762367 0.864072 -0.986848 +v 1.978204 0.694100 -1.137927 +v 1.821889 1.171394 -1.054962 +v 2.860838 0.346753 -1.786883 +v 1.768391 0.914212 -0.976082 +v 2.800102 0.741867 -1.764054 +v 3.047683 0.760667 -1.958810 +v 2.801089 0.327237 -1.724923 +v 2.686153 0.887537 -1.683203 +v 2.915644 -0.066074 -1.774558 +v 1.939461 1.218681 -1.105028 +v 0.380965 -0.356962 0.277840 +v 2.157984 -0.096145 -1.130609 +v 2.171539 -0.180007 -1.133476 +v 1.870303 1.101825 -0.999439 +v 2.768902 0.690672 -1.652155 +v 2.931033 0.013315 -1.716360 +v 2.798614 1.350688 -1.707175 +v 1.895380 1.047986 -0.972059 +v -0.263286 -0.086843 0.817096 +v 1.915261 0.855545 -0.963812 +v 1.995518 1.144847 -1.050030 +v 1.923295 0.929349 -0.946858 +v 0.639411 -0.340042 0.173318 +v 2.048910 0.874962 -1.028633 +v 2.024876 1.087065 -1.024636 +v 0.098227 -0.041243 0.603403 +v 1.153992 0.084424 -0.229250 +v 2.057853 0.957558 -1.005829 +v 2.853774 1.270425 -1.644800 +v 2.691514 -0.001160 -1.386453 +v 2.705070 -0.085024 -1.389320 +v 2.781930 0.015918 -1.458515 +v 2.795485 -0.067945 -1.461382 +v 2.889620 0.961520 -1.629051 +v 3.137201 0.980321 -1.823807 +v 2.880562 1.206416 -1.616926 +v 3.128144 1.225217 -1.811681 +v 2.893827 1.173559 -1.603234 +v 3.141408 1.192360 -1.797990 +v -0.165990 -0.329480 0.922774 +v 2.905606 1.059058 -1.599313 +v 3.153188 1.077859 -1.794069 +v 2.902644 1.136966 -1.595558 +v 3.150226 1.155766 -1.790314 +v 2.906628 1.098234 -1.594232 +v 3.154210 1.117035 -1.788988 +v 0.450756 -0.003152 0.428413 +v 0.893967 -0.321301 0.137348 +v 0.203261 -0.310043 0.717218 +v -0.125580 -0.167821 0.986626 +v 0.712336 0.026704 0.345760 +v -0.090811 -0.262476 1.024542 +v 1.652963 0.059929 -0.335038 +v 0.245682 -0.133976 0.786321 +v 0.568430 -0.286725 0.550539 +v 0.283133 -0.240046 0.827356 +v 2.000346 0.011962 -0.463845 +v 0.611741 -0.102875 0.629863 +v 0.998399 0.048451 0.327453 +v 0.827128 -0.268380 0.491359 +v 1.390944 -0.279641 0.074489 +v 2.438757 0.004155 -0.738633 +v 0.652016 -0.215986 0.675288 +v 2.198852 0.014020 -0.544254 +v 0.870584 -0.078559 0.583979 +v 0.910831 -0.196982 0.637049 +v 1.127571 -0.244822 0.495289 +v 1.185795 -0.053488 0.599517 +v 1.535993 0.058149 0.320863 +v 1.947252 -0.218027 0.037437 +v 1.232465 -0.171199 0.659405 +v 2.293159 -0.159573 -0.040558 +v 2.769338 -0.097283 -0.322594 +v 1.695832 -0.196471 0.528473 +v 2.530709 -0.129037 -0.121828 +v 2.157723 0.063260 0.342121 +v 1.776927 -0.020207 0.662777 +v 1.833764 -0.121263 0.736990 +v 3.351091 0.073201 -0.443243 +v 3.364646 -0.010661 -0.446110 +v 2.948324 -0.003291 -0.110055 +v 2.961879 -0.087154 -0.112922 +v 2.992015 0.002089 -0.060870 +v 3.005570 -0.081773 -0.063737 +v 2.525032 0.038738 0.306061 +v 3.379430 0.074900 -0.358922 +v 3.392986 -0.008964 -0.361790 +v 3.036570 0.045434 0.026135 +v 2.796513 0.049478 0.227779 +v 2.357571 -0.130808 0.595337 +v 2.473462 0.016867 0.762129 +v 2.725617 -0.081249 0.592239 +v 2.544662 -0.059737 0.852031 +v 3.261550 -0.027300 0.308389 +v 3.022995 -0.051504 0.513873 +v 2.851826 0.022984 0.782891 +v 3.407963 0.046255 0.499554 +v 2.923753 -0.024800 0.883847 +v 3.168214 0.041578 0.705848 +v 3.711371 0.106264 0.292980 +v 3.724926 0.022401 0.290113 +v 3.531985 0.072015 0.446671 +v 3.545540 -0.011849 0.443804 +v 3.445588 0.055512 0.520889 +v 3.459143 -0.028351 0.518022 +v 3.563503 0.077601 0.432271 +v 3.577058 -0.006262 0.429404 +v 3.488101 0.014784 0.599486 +v 3.249111 0.000073 0.806963 +vt 0.592295 0.226200 +vt 0.597451 0.733993 +vt 0.582117 0.735152 +vt 0.578489 0.226600 +vt 0.583926 0.737917 +vt 0.580317 0.227579 +vt 0.553525 0.740642 +vt 0.440791 0.740508 +vt 0.450875 0.228401 +vt 0.552974 0.228523 +vt 0.390668 0.737686 +vt 0.405291 0.227370 +vt 0.388859 0.734922 +vt 0.403463 0.226391 +vt 0.371983 0.733724 +vt 0.388098 0.225956 +vt 0.619772 0.907170 +vt 0.592331 0.999817 +vt 0.601330 0.999666 +vt 0.610146 0.909999 +vt 0.574598 0.999907 +vt 0.590330 0.911915 +vt 0.553091 0.999969 +vt 0.566105 0.913419 +vt 0.528748 1.000000 +vt 0.538531 0.914447 +vt 0.502634 1.000000 +vt 0.478247 0.914918 +vt 0.475889 0.999968 +vt 0.508812 0.914955 +vt 0.449684 0.999906 +vt 0.448173 0.914340 +vt 0.425163 0.999816 +vt 0.419902 0.913245 +vt 0.403398 0.999703 +vt 0.394672 0.911681 +vt 0.385339 0.999570 +vt 0.373584 0.909717 +vt 0.375862 0.999397 +vt 0.362094 0.906863 +vt 0.605465 0.740602 +vt 0.597205 0.739049 +vt 0.491046 0.731144 +vt 0.490016 0.901745 +vt 0.489949 0.901745 +vt 0.579978 0.737974 +vt 0.489821 0.901745 +vt 0.558870 0.737116 +vt 0.489665 0.901745 +vt 0.534804 0.736510 +vt 0.489489 0.901745 +vt 0.508831 0.736184 +vt 0.489301 0.901744 +vt 0.482087 0.736152 +vt 0.489205 0.901744 +vt 0.455740 0.736416 +vt 0.430942 0.736963 +vt 0.408777 0.737770 +vt 0.390214 0.738801 +vt 0.379997 0.740333 +vt 0.353656 0.886514 +vt 0.378904 0.730193 +vt 0.361705 0.880889 +vt 0.395023 0.727499 +vt 0.380234 0.876591 +vt 0.415254 0.725377 +vt 0.403440 0.873201 +vt 0.438713 0.723920 +vt 0.430309 0.870869 +vt 0.464376 0.723192 +vt 0.459668 0.869695 +vt 0.477566 0.723232 +vt 0.474742 0.869751 +vt 0.491120 0.723224 +vt 0.490232 0.869731 +vt 0.517777 0.724015 +vt 0.520667 0.870977 +vt 0.543181 0.725530 +vt 0.549643 0.873375 +vt 0.566223 0.727704 +vt 0.575892 0.876824 +vt 0.585895 0.730440 +vt 0.598267 0.881171 +vt 0.611334 0.886821 +vt 0.514919 0.001808 +vt 0.498936 0.000000 +vt 0.513858 0.001846 +vt 0.529721 0.013570 +vt 0.531965 0.013618 +vt 0.556734 0.062512 +vt 0.561107 0.062168 +vt 0.573935 0.111992 +vt 0.579743 0.111218 +vt 0.580242 0.134938 +vt 0.586574 0.134186 +vt 0.586077 0.152110 +vt 0.592909 0.152041 +vt 0.591813 0.178477 +vt 0.599157 0.177963 +vt 0.593139 0.193428 +vt 0.600618 0.192699 +vt 0.592908 0.232476 +vt 0.600324 0.231459 +vt 0.496437 0.231529 +vt 0.508654 0.001888 +vt 0.518859 0.013502 +vt 0.535963 0.062914 +vt 0.546664 0.112914 +vt 0.550579 0.135830 +vt 0.554180 0.152166 +vt 0.557666 0.179076 +vt 0.558422 0.194288 +vt 0.558268 0.233688 +vt 0.501777 0.001902 +vt 0.504546 0.013460 +vt 0.508707 0.063079 +vt 0.510973 0.113312 +vt 0.511780 0.136212 +vt 0.512489 0.152160 +vt 0.513077 0.179318 +vt 0.513104 0.194651 +vt 0.512986 0.234213 +vt 0.498076 0.001898 +vt 0.496856 0.013450 +vt 0.494097 0.063062 +vt 0.491870 0.113289 +vt 0.491019 0.136187 +vt 0.490190 0.152133 +vt 0.489240 0.179289 +vt 0.488883 0.194622 +vt 0.488765 0.234184 +vt 0.490954 0.001867 +vt 0.482078 0.013458 +vt 0.466079 0.062831 +vt 0.455287 0.112805 +vt 0.451272 0.135711 +vt 0.447516 0.152039 +vt 0.443646 0.178940 +vt 0.442563 0.194150 +vt 0.442409 0.233550 +vt 0.485218 0.001812 +vt 0.470208 0.013499 +vt 0.443661 0.062377 +vt 0.426084 0.111816 +vt 0.419558 0.134746 +vt 0.413491 0.151904 +vt 0.407324 0.178257 +vt 0.405675 0.193204 +vt 0.405444 0.232252 +vt 0.483722 0.001771 +vt 0.467140 0.013541 +vt 0.437940 0.062021 +vt 0.418694 0.111026 +vt 0.411548 0.133977 +vt 0.404917 0.151817 +vt 0.398200 0.177723 +vt 0.396421 0.192455 +vt 0.396126 0.231215 +vt 0.482807 0.001091 +vt 0.465246 0.012218 +vt 0.434231 0.060860 +vt 0.413722 0.112734 +vt 0.406278 0.130880 +vt 0.397753 0.154535 +vt 0.390718 0.172336 +vt 0.388811 0.184259 +vt 0.483894 0.000866 +vt 0.467479 0.011696 +vt 0.438359 0.060674 +vt 0.419010 0.113922 +vt 0.412060 0.130257 +vt 0.403381 0.155615 +vt 0.396772 0.170695 +vt 0.394947 0.181718 +vt 0.394122 0.224712 +vt 0.489100 0.000562 +vt 0.478265 0.010987 +vt 0.458692 0.060437 +vt 0.445446 0.115578 +vt 0.440842 0.129428 +vt 0.433493 0.157124 +vt 0.429012 0.168469 +vt 0.427688 0.178256 +vt 0.426709 0.223032 +vt 0.492401 0.000471 +vt 0.485112 0.010775 +vt 0.471652 0.060373 +vt 0.462347 0.116094 +vt 0.459225 0.129185 +vt 0.452993 0.157597 +vt 0.449873 0.167802 +vt 0.448880 0.177211 +vt 0.447853 0.222532 +vt 0.495979 0.000427 +vt 0.492542 0.010672 +vt 0.485745 0.060349 +vt 0.480752 0.116370 +vt 0.479235 0.129074 +vt 0.474359 0.157853 +vt 0.472721 0.167476 +vt 0.472093 0.176692 +vt 0.471042 0.222291 +vt 0.497803 0.000430 +vt 0.496333 0.010680 +vt 0.492949 0.060359 +vt 0.490171 0.116373 +vt 0.489471 0.129091 +vt 0.485358 0.157859 +vt 0.484479 0.167501 +vt 0.484040 0.176724 +vt 0.482990 0.222314 +vt 0.499680 0.000431 +vt 0.500232 0.010681 +vt 0.500355 0.060366 +vt 0.499854 0.116392 +vt 0.499996 0.129099 +vt 0.496658 0.157879 +vt 0.496558 0.167505 +vt 0.496314 0.176721 +vt 0.495264 0.222320 +vt 0.503340 0.000484 +vt 0.507844 0.010802 +vt 0.514842 0.060424 +vt 0.518821 0.116161 +vt 0.520601 0.129259 +vt 0.518915 0.157676 +vt 0.520341 0.167887 +vt 0.520484 0.177297 +vt 0.519458 0.222618 +vt 0.506801 0.000583 +vt 0.515045 0.011031 +vt 0.528575 0.060520 +vt 0.536823 0.115687 +vt 0.540149 0.129546 +vt 0.540157 0.157251 +vt 0.543032 0.168605 +vt 0.543547 0.178394 +vt 0.542568 0.223170 +vt 0.512534 0.000900 +vt 0.526992 0.011767 +vt 0.551432 0.060809 +vt 0.566860 0.114099 +vt 0.572744 0.130449 +vt 0.575967 0.155821 +vt 0.581260 0.170915 +vt 0.582411 0.181941 +vt 0.581586 0.224936 +vt 0.514004 0.001128 +vt 0.530070 0.012295 +vt 0.557398 0.061007 +vt 0.574770 0.112926 +vt 0.581304 0.131089 +vt 0.585745 0.154759 +vt 0.591675 0.172575 +vt 0.593008 0.184503 +vt 0.500194 0.123038 +vt 0.538682 0.145632 +vt 0.499355 0.147686 +vt 0.567425 0.437987 +vt 0.495309 0.437689 +vt 0.572058 0.485965 +vt 0.494594 0.485885 +vt 0.579203 0.530428 +vt 0.494130 0.530571 +vt 0.588071 0.564595 +vt 0.493832 0.570346 +vt 0.603460 0.591429 +vt 0.494064 0.596672 +vt 0.634825 0.640783 +vt 0.495158 0.646017 +vt 0.673396 0.693624 +vt 0.496679 0.696159 +vt 0.692678 0.734348 +vt 0.497801 0.742533 +vt 0.702762 0.758051 +vt 0.498113 0.758399 +vt 0.701606 0.793564 +vt 0.498079 0.793255 +vt 0.493311 0.794017 +vt 0.530328 0.123074 +vt 0.571363 0.144013 +vt 0.627653 0.438535 +vt 0.636786 0.486372 +vt 0.650275 0.530678 +vt 0.666763 0.560227 +vt 0.694688 0.587480 +vt 0.750987 0.636789 +vt 0.820060 0.691789 +vt 0.854194 0.727752 +vt 0.872335 0.757923 +vt 0.870198 0.793953 +vt 0.555251 0.123104 +vt 0.591748 0.143109 +vt 0.665577 0.439237 +vt 0.677585 0.487036 +vt 0.695058 0.531278 +vt 0.716302 0.557996 +vt 0.751975 0.585509 +vt 0.823559 0.634725 +vt 0.911312 0.690971 +vt 0.954422 0.723886 +vt 0.977512 0.758039 +vt 0.974704 0.794354 +vt 0.570653 0.123123 +vt 0.596282 0.143062 +vt 0.674482 0.439878 +vt 0.687203 0.487720 +vt 0.705580 0.531920 +vt 0.727870 0.558106 +vt 0.765162 0.585722 +vt 0.839794 0.634825 +vt 0.931229 0.691232 +vt 0.975934 0.723388 +vt 1.000000 0.758335 +vt 0.996949 0.794648 +vt 0.404437 0.142964 +vt 0.426515 0.122951 +vt 0.573874 0.123126 +vt 0.323231 0.439478 +vt 0.310002 0.487319 +vt 0.291306 0.531610 +vt 0.269108 0.558029 +vt 0.232259 0.585436 +vt 0.158605 0.634392 +vt 0.068392 0.690386 +vt 0.024123 0.722737 +vt 0.000000 0.757160 +vt 0.002277 0.793454 +vt 0.408288 0.143022 +vt 0.329678 0.438833 +vt 0.316870 0.486617 +vt 0.298892 0.530832 +vt 0.277591 0.557845 +vt 0.242362 0.585244 +vt 0.172141 0.634290 +vt 0.086185 0.690163 +vt 0.044209 0.723323 +vt 0.021216 0.756939 +vt 0.023503 0.793219 +vt 0.429735 0.122954 +vt 0.428009 0.143973 +vt 0.365185 0.438218 +vt 0.354927 0.486047 +vt 0.340715 0.530335 +vt 0.323958 0.560190 +vt 0.296482 0.587348 +vt 0.241975 0.636524 +vt 0.175315 0.691197 +vt 0.142962 0.727426 +vt 0.125096 0.757073 +vt 0.126941 0.793066 +vt 0.445138 0.122973 +vt 0.460221 0.145670 +vt 0.423771 0.437812 +vt 0.417791 0.485792 +vt 0.409775 0.530252 +vt 0.400446 0.564744 +vt 0.385513 0.591511 +vt 0.356231 0.640793 +vt 0.320514 0.693380 +vt 0.303405 0.734406 +vt 0.293784 0.757604 +vt 0.294807 0.793079 +vt 0.470061 0.123002 +vt 0.584118 0.143846 +vt 0.652512 0.440323 +vt 0.663585 0.488333 +vt 0.679561 0.533226 +vt 0.698964 0.560878 +vt 0.731472 0.587869 +vt 0.796494 0.637100 +vt 0.876085 0.692426 +vt 0.914822 0.726015 +vt 0.935707 0.758515 +vt 0.932883 0.794633 +vt 0.564354 0.123115 +vt 0.557453 0.145374 +vt 0.603941 0.440767 +vt 0.611404 0.489119 +vt 0.622180 0.535513 +vt 0.635332 0.566338 +vt 0.657480 0.591977 +vt 0.701732 0.641506 +vt 0.755840 0.694578 +vt 0.781940 0.731416 +vt 0.796061 0.758682 +vt 0.793893 0.794463 +vt 0.543740 0.123090 +vt 0.520898 0.147383 +vt 0.537164 0.441133 +vt 0.539680 0.489942 +vt 0.543358 0.538385 +vt 0.547975 0.573543 +vt 0.555980 0.597337 +vt 0.571895 0.647283 +vt 0.591287 0.697315 +vt 0.600266 0.738659 +vt 0.605208 0.758809 +vt 0.604010 0.794167 +vt 0.515597 0.123057 +vt 0.480790 0.147467 +vt 0.463733 0.441064 +vt 0.460823 0.489897 +vt 0.456750 0.538466 +vt 0.452065 0.573899 +vt 0.444570 0.597554 +vt 0.429485 0.647493 +vt 0.410904 0.697284 +vt 0.401280 0.738907 +vt 0.396151 0.758578 +vt 0.396067 0.793912 +vt 0.484791 0.123020 +vt 0.444068 0.145371 +vt 0.396343 0.440538 +vt 0.388469 0.488901 +vt 0.377334 0.535405 +vt 0.364191 0.566484 +vt 0.342521 0.591951 +vt 0.299133 0.641406 +vt 0.245883 0.694154 +vt 0.219396 0.731230 +vt 0.205038 0.757996 +vt 0.206019 0.793754 +vt 0.456648 0.122986 +vt 0.417060 0.143778 +vt 0.346645 0.439977 +vt 0.335120 0.487990 +vt 0.318813 0.532980 +vt 0.299476 0.560871 +vt 0.267422 0.587665 +vt 0.203316 0.636772 +vt 0.124729 0.691714 +vt 0.085988 0.725511 +vt 0.064911 0.757495 +vt 0.066727 0.793592 +vt 0.436035 0.122962 +vt 0.495260 0.736170 +vt 0.500696 0.234197 +vt 0.536496 0.234045 +vt 0.577350 0.233157 +vt 0.495655 0.736171 +vt 0.501053 0.234197 +vt 0.464891 0.233960 +vt 0.422300 0.232972 +vt 0.408534 0.223769 +vt 0.563583 0.223955 +vt 0.367604 0.795692 +vt 0.344548 0.863566 +vt 0.343493 0.862334 +vt 0.368659 0.796923 +vt 0.344327 0.876900 +vt 0.343272 0.875668 +vt 0.145143 0.856433 +vt 0.144088 0.855201 +vt 0.144487 0.796170 +vt 0.143432 0.794939 +vt 0.132013 0.795411 +vt 0.130679 0.853014 +vt 0.129623 0.851782 +vt 0.133068 0.796642 +vt 0.006462 0.836828 +vt 0.005407 0.835597 +vt 0.003936 0.813497 +vt 0.002882 0.812266 +vt 0.005457 0.809612 +vt 0.004402 0.808380 +vt 0.004946 0.796489 +vt 0.003891 0.795258 +vt 0.854563 0.797672 +vt 0.853823 0.852194 +vt 0.854872 0.853763 +vt 0.853514 0.796103 +vt 0.978631 0.836386 +vt 0.979680 0.837955 +vt 0.982018 0.813180 +vt 0.983067 0.814749 +vt 0.980641 0.809310 +vt 0.981690 0.810879 +vt 0.981636 0.796256 +vt 0.982685 0.797825 +vt 0.618972 0.797391 +vt 0.639573 0.862183 +vt 0.640622 0.863752 +vt 0.617923 0.795822 +vt 0.639302 0.875450 +vt 0.640350 0.877019 +vt 0.839233 0.855561 +vt 0.840281 0.857130 +vt 0.842114 0.795606 +vt 0.843162 0.797175 +vt 0.341429 0.892018 +vt 0.354422 0.771751 +vt 0.371180 0.771906 +vt 0.361304 0.757630 +vt 0.375583 0.756290 +vt 0.376061 0.747099 +vt 0.386704 0.746114 +vt 0.396022 0.741918 +vt 0.399519 0.741536 +vt 0.406939 0.741656 +vt 0.407021 0.741651 +vt 0.362783 0.891001 +vt 0.382518 0.772090 +vt 0.385281 0.755640 +vt 0.393806 0.745632 +vt 0.401875 0.741347 +vt 0.407053 0.741648 +vt 0.377126 0.890562 +vt 0.415783 0.773053 +vt 0.413937 0.755087 +vt 0.414119 0.745184 +vt 0.408737 0.741153 +vt 0.407022 0.741639 +vt 0.418673 0.890604 +vt 0.457357 0.774594 +vt 0.449912 0.755475 +vt 0.439090 0.745395 +vt 0.417274 0.741201 +vt 0.406885 0.741627 +vt 0.367889 0.778452 +vt 0.364200 0.897177 +vt 0.470167 0.891713 +vt 0.370122 0.761446 +vt 0.381736 0.748731 +vt 0.398612 0.742335 +vt 0.406817 0.741630 +vt 0.341083 0.776828 +vt 0.346977 0.760159 +vt 0.366096 0.747919 +vt 0.393148 0.742062 +vt 0.406985 0.741639 +vt 0.330950 0.895375 +vt 0.334983 0.775889 +vt 0.341985 0.759566 +vt 0.362746 0.747620 +vt 0.391920 0.741975 +vt 0.407026 0.741643 +vt 0.322792 0.894560 +vt 0.337783 0.774019 +vt 0.345386 0.758625 +vt 0.365129 0.747299 +vt 0.392543 0.741914 +vt 0.407019 0.741650 +vt 0.324143 0.893295 +vt 0.342886 0.773061 +vt 0.350401 0.758202 +vt 0.368571 0.747202 +vt 0.393616 0.741910 +vt 0.406994 0.741653 +vt 0.329152 0.892735 +vt 0.606943 0.892335 +vt 0.578997 0.772154 +vt 0.598183 0.772042 +vt 0.576236 0.756530 +vt 0.592545 0.757906 +vt 0.567870 0.746331 +vt 0.580039 0.747342 +vt 0.558973 0.741727 +vt 0.562967 0.742117 +vt 0.553477 0.741826 +vt 0.553572 0.741831 +vt 0.582516 0.891263 +vt 0.566609 0.772310 +vt 0.565670 0.755855 +vt 0.560098 0.745830 +vt 0.556401 0.741531 +vt 0.553436 0.741823 +vt 0.566831 0.890789 +vt 0.533400 0.773194 +vt 0.537139 0.755234 +vt 0.539725 0.745333 +vt 0.549548 0.741322 +vt 0.553438 0.741813 +vt 0.525261 0.890731 +vt 0.494395 0.774638 +vt 0.503451 0.755539 +vt 0.516203 0.745487 +vt 0.541534 0.741350 +vt 0.553540 0.741802 +vt 0.599293 0.897457 +vt 0.597789 0.778726 +vt 0.476853 0.891721 +vt 0.595602 0.761715 +vt 0.582464 0.748970 +vt 0.563094 0.742531 +vt 0.553620 0.741805 +vt 0.620315 0.777161 +vt 0.614992 0.760479 +vt 0.595561 0.748192 +vt 0.567681 0.742270 +vt 0.553478 0.741814 +vt 0.627351 0.895729 +vt 0.624397 0.776234 +vt 0.618175 0.759896 +vt 0.597681 0.747900 +vt 0.568493 0.742185 +vt 0.553449 0.741818 +vt 0.633134 0.894930 +vt 0.618307 0.774354 +vt 0.611745 0.758943 +vt 0.593228 0.747572 +vt 0.567181 0.742122 +vt 0.553475 0.741825 +vt 0.628051 0.893657 +vt 0.611697 0.773381 +vt 0.605318 0.758506 +vt 0.588819 0.747464 +vt 0.565791 0.742115 +vt 0.553508 0.741827 +vt 0.621374 0.893083 +vt 0.467142 0.741480 +vt 0.460043 0.823727 +vt 0.466255 0.823734 +vt 0.491990 0.741510 +vt 0.491102 0.823764 +vt 0.497314 0.823771 +vt 0.459387 0.936839 +vt 0.481806 0.936865 +vt 0.442062 0.996371 +vt 0.457305 0.996390 +vt 0.441061 0.998017 +vt 0.455880 0.998035 +vt 0.440376 0.996388 +vt 0.454893 0.996405 +vt 0.440171 0.989270 +vt 0.454568 0.989287 +vt 0.440732 0.939254 +vt 0.455138 0.939271 +vt 0.441300 0.928768 +vt 0.455901 0.928785 +vt 0.442019 0.920921 +vt 0.456893 0.920939 +vt 0.442564 0.913924 +vt 0.457640 0.913942 +vt 0.463957 0.747884 +vt 0.487465 0.747912 +vt 0.464844 0.743833 +vt 0.488714 0.743862 +vt 0.549439 0.921434 +vt 0.548851 0.918488 +vt 0.544157 0.921323 +vt 0.551271 0.945406 +vt 0.533127 0.945025 +vt 0.552776 0.983628 +vt 0.528590 0.983120 +vt 0.552915 0.990499 +vt 0.528985 0.989997 +vt 0.552488 0.989209 +vt 0.532597 0.988792 +vt 0.540100 0.920799 +vt 0.519194 0.943227 +vt 0.510019 0.980723 +vt 0.510611 0.987626 +vt 0.517323 0.986820 +vt 0.538358 0.920003 +vt 0.513207 0.940494 +vt 0.502039 0.977080 +vt 0.502715 0.984021 +vt 0.510760 0.983824 +vt 0.539395 0.919149 +vt 0.516770 0.937557 +vt 0.506788 0.973165 +vt 0.507415 0.980148 +vt 0.514666 0.980605 +vt 0.542934 0.918463 +vt 0.528929 0.935204 +vt 0.522994 0.970029 +vt 0.523449 0.977045 +vt 0.527995 0.978025 +vt 0.548028 0.918132 +vt 0.546424 0.934066 +vt 0.546314 0.968512 +vt 0.546522 0.975543 +vt 0.547174 0.976777 +vt 0.553310 0.918243 +vt 0.564569 0.934446 +vt 0.570500 0.969019 +vt 0.570451 0.976045 +vt 0.567066 0.977194 +vt 0.557366 0.918766 +vt 0.578501 0.936245 +vt 0.589071 0.971416 +vt 0.588825 0.978417 +vt 0.582340 0.979166 +vt 0.559110 0.919562 +vt 0.584488 0.938978 +vt 0.597051 0.975059 +vt 0.596721 0.982022 +vt 0.588903 0.982162 +vt 0.558072 0.920417 +vt 0.580925 0.941915 +vt 0.592302 0.978974 +vt 0.592022 0.985895 +vt 0.584996 0.985382 +vt 0.554533 0.921102 +vt 0.568767 0.944267 +vt 0.576096 0.982110 +vt 0.575988 0.988998 +vt 0.571668 0.987961 +vt 0.424145 0.921277 +vt 0.423693 0.918338 +vt 0.418870 0.920933 +vt 0.423822 0.945229 +vt 0.405702 0.944048 +vt 0.421388 0.983437 +vt 0.397235 0.981863 +vt 0.420772 0.990308 +vt 0.396875 0.988751 +vt 0.420346 0.989024 +vt 0.400481 0.987729 +vt 0.414819 0.920239 +vt 0.391787 0.941664 +vt 0.378687 0.978685 +vt 0.378524 0.985607 +vt 0.385227 0.985116 +vt 0.413078 0.919381 +vt 0.385807 0.938716 +vt 0.370716 0.974756 +vt 0.370638 0.981719 +vt 0.378671 0.981884 +vt 0.414113 0.918588 +vt 0.389363 0.935994 +vt 0.375457 0.971128 +vt 0.375328 0.978129 +vt 0.382570 0.978900 +vt 0.417648 0.918074 +vt 0.401504 0.934227 +vt 0.391639 0.968773 +vt 0.391339 0.975799 +vt 0.395879 0.976963 +vt 0.422734 0.917975 +vt 0.418975 0.933889 +vt 0.414927 0.968321 +vt 0.414380 0.975353 +vt 0.415032 0.976592 +vt 0.428009 0.918319 +vt 0.437095 0.935070 +vt 0.439080 0.969896 +vt 0.438277 0.976910 +vt 0.434897 0.977887 +vt 0.432060 0.919013 +vt 0.451010 0.937454 +vt 0.457627 0.973073 +vt 0.456628 0.980054 +vt 0.450151 0.980500 +vt 0.433801 0.919872 +vt 0.456990 0.940402 +vt 0.465599 0.977003 +vt 0.464515 0.983942 +vt 0.456707 0.983732 +vt 0.432766 0.920664 +vt 0.453434 0.943124 +vt 0.460858 0.980631 +vt 0.459824 0.987532 +vt 0.452808 0.986716 +vt 0.429231 0.921178 +vt 0.441293 0.944891 +vt 0.444676 0.982987 +vt 0.443813 0.989862 +vt 0.439499 0.988653 +vt 0.489178 0.904955 +vt 0.478920 0.903724 +vt 0.488659 0.905860 +vt 0.513457 0.924223 +vt 0.511674 0.927332 +vt 0.523738 0.959386 +vt 0.521362 0.963531 +vt 0.522949 0.966121 +vt 0.520598 0.970220 +vt 0.515067 0.966111 +vt 0.513113 0.969519 +vt 0.486649 0.906626 +vt 0.504771 0.929961 +vt 0.512160 0.967035 +vt 0.511494 0.973688 +vt 0.505545 0.972402 +vt 0.483455 0.907135 +vt 0.493798 0.931710 +vt 0.497535 0.969367 +vt 0.497023 0.975995 +vt 0.493517 0.974319 +vt 0.479562 0.907310 +vt 0.480427 0.932313 +vt 0.479712 0.970170 +vt 0.479389 0.976790 +vt 0.478859 0.974980 +vt 0.475564 0.907126 +vt 0.466693 0.931678 +vt 0.461405 0.969323 +vt 0.461277 0.975952 +vt 0.463802 0.974284 +vt 0.472068 0.906608 +vt 0.454687 0.929901 +vt 0.445402 0.966955 +vt 0.445442 0.973609 +vt 0.450640 0.972336 +vt 0.469608 0.905838 +vt 0.446236 0.927254 +vt 0.434137 0.963426 +vt 0.434297 0.970118 +vt 0.441375 0.969434 +vt 0.468558 0.904931 +vt 0.442627 0.924138 +vt 0.429327 0.959274 +vt 0.429538 0.966009 +vt 0.437419 0.966018 +vt 0.469077 0.904026 +vt 0.444410 0.921030 +vt 0.431703 0.955130 +vt 0.431889 0.961909 +vt 0.439374 0.962610 +vt 0.471086 0.903260 +vt 0.451313 0.918400 +vt 0.440904 0.951625 +vt 0.440993 0.958442 +vt 0.446941 0.959728 +vt 0.474281 0.902751 +vt 0.462285 0.916651 +vt 0.455530 0.949294 +vt 0.455463 0.956135 +vt 0.458970 0.957810 +vt 0.478174 0.902575 +vt 0.475656 0.916048 +vt 0.473353 0.948490 +vt 0.473097 0.955340 +vt 0.473628 0.957150 +vt 0.482172 0.902760 +vt 0.489391 0.916684 +vt 0.491660 0.949337 +vt 0.491210 0.956177 +vt 0.488685 0.957846 +vt 0.485667 0.903278 +vt 0.501397 0.918460 +vt 0.507663 0.951705 +vt 0.507044 0.958521 +vt 0.501847 0.959794 +vt 0.488128 0.904048 +vt 0.509848 0.921108 +vt 0.518928 0.955234 +vt 0.518190 0.962012 +vt 0.511111 0.962696 +vt 0.490402 0.906636 +vt 0.478826 0.907307 +vt 0.488446 0.905821 +vt 0.501508 0.904625 +vt 0.497682 0.903031 +vt 0.513572 0.898392 +vt 0.507734 0.895959 +vt 0.520474 0.898400 +vt 0.513468 0.895480 +vt 0.527756 0.896328 +vt 0.519527 0.892898 +vt 0.532641 0.890349 +vt 0.523612 0.886586 +vt 0.534751 0.880504 +vt 0.525409 0.876611 +vt 0.483918 0.905221 +vt 0.488823 0.901856 +vt 0.494212 0.894166 +vt 0.497242 0.893329 +vt 0.500470 0.890372 +vt 0.502703 0.883814 +vt 0.503775 0.873743 +vt 0.478030 0.904996 +vt 0.477305 0.901416 +vt 0.476632 0.893495 +vt 0.476145 0.892524 +vt 0.475691 0.889426 +vt 0.475516 0.882776 +vt 0.475646 0.872669 +vt 0.472360 0.905207 +vt 0.466213 0.901829 +vt 0.459702 0.894125 +vt 0.455830 0.893280 +vt 0.451831 0.890314 +vt 0.449337 0.883750 +vt 0.448558 0.873677 +vt 0.468427 0.905797 +vt 0.458520 0.902984 +vt 0.447960 0.895887 +vt 0.441739 0.895394 +vt 0.435282 0.892798 +vt 0.431179 0.886476 +vt 0.429771 0.876497 +vt 0.467286 0.906609 +vt 0.456287 0.904571 +vt 0.444552 0.898310 +vt 0.437650 0.898301 +vt 0.430478 0.896212 +vt 0.425909 0.890222 +vt 0.424318 0.880373 +vt 0.469241 0.907423 +vt 0.460112 0.906165 +vt 0.450390 0.900743 +vt 0.444656 0.901221 +vt 0.438707 0.899641 +vt 0.434938 0.893984 +vt 0.433660 0.884266 +vt 0.473770 0.908024 +vt 0.468971 0.907340 +vt 0.463912 0.902535 +vt 0.460882 0.903372 +vt 0.457764 0.902168 +vt 0.455847 0.896756 +vt 0.455294 0.887134 +vt 0.479658 0.908248 +vt 0.480490 0.907779 +vt 0.481492 0.903207 +vt 0.481979 0.904178 +vt 0.482542 0.903114 +vt 0.483033 0.897794 +vt 0.483423 0.888208 +vt 0.485328 0.908037 +vt 0.491581 0.907367 +vt 0.498422 0.902577 +vt 0.502294 0.903422 +vt 0.506403 0.902226 +vt 0.509212 0.896820 +vt 0.510510 0.887200 +vt 0.489260 0.907447 +vt 0.499275 0.906212 +vt 0.510164 0.900814 +vt 0.516385 0.901307 +vt 0.522952 0.899742 +vt 0.527370 0.894095 +vt 0.529298 0.884380 +vt 0.435164 0.921226 +vt 0.423586 0.921892 +vt 0.433212 0.920281 +vt 0.446278 0.919232 +vt 0.442459 0.917382 +vt 0.458366 0.913050 +vt 0.452538 0.910227 +vt 0.465268 0.913058 +vt 0.458275 0.909671 +vt 0.472557 0.911003 +vt 0.464343 0.907025 +vt 0.477464 0.905073 +vt 0.468453 0.900708 +vt 0.479612 0.895309 +vt 0.470288 0.890792 +vt 0.428687 0.919585 +vt 0.433606 0.916021 +vt 0.439025 0.908150 +vt 0.442059 0.907178 +vt 0.445298 0.904097 +vt 0.447556 0.897496 +vt 0.448667 0.887469 +vt 0.422799 0.919325 +vt 0.422089 0.915513 +vt 0.421447 0.907374 +vt 0.420965 0.906248 +vt 0.420524 0.903004 +vt 0.420375 0.896296 +vt 0.420542 0.886228 +vt 0.417129 0.919572 +vt 0.410995 0.915994 +vt 0.404515 0.908109 +vt 0.400647 0.907129 +vt 0.396659 0.904039 +vt 0.394191 0.897432 +vt 0.393450 0.887403 +vt 0.413194 0.920257 +vt 0.403297 0.917335 +vt 0.392764 0.910156 +vt 0.386546 0.909585 +vt 0.380099 0.906924 +vt 0.376020 0.900598 +vt 0.374650 0.890678 +vt 0.412048 0.921199 +vt 0.401056 0.919178 +vt 0.389345 0.912967 +vt 0.382443 0.912959 +vt 0.375279 0.910887 +vt 0.370733 0.904945 +vt 0.369179 0.895177 +vt 0.414000 0.922144 +vt 0.404875 0.921027 +vt 0.395173 0.915790 +vt 0.389436 0.916346 +vt 0.383493 0.914865 +vt 0.379745 0.909310 +vt 0.378503 0.899693 +vt 0.418526 0.922840 +vt 0.413728 0.922388 +vt 0.408686 0.917867 +vt 0.405652 0.918839 +vt 0.402538 0.917793 +vt 0.400641 0.912523 +vt 0.400124 0.903017 +vt 0.424413 0.923099 +vt 0.425245 0.922896 +vt 0.426264 0.918643 +vt 0.426745 0.919770 +vt 0.427312 0.918886 +vt 0.427823 0.913722 +vt 0.428249 0.904258 +vt 0.430084 0.922854 +vt 0.436339 0.922415 +vt 0.443196 0.917909 +vt 0.447065 0.918889 +vt 0.451177 0.917851 +vt 0.454007 0.912586 +vt 0.455341 0.903083 +vt 0.434019 0.922168 +vt 0.444037 0.921074 +vt 0.454947 0.915862 +vt 0.461165 0.916432 +vt 0.467738 0.914966 +vt 0.472177 0.909421 +vt 0.474141 0.899807 +vt 0.560322 0.921376 +vt 0.548743 0.922041 +vt 0.558370 0.920430 +vt 0.571436 0.919381 +vt 0.567617 0.917532 +vt 0.583524 0.913199 +vt 0.577696 0.910376 +vt 0.590426 0.913208 +vt 0.583432 0.909820 +vt 0.597715 0.911152 +vt 0.589501 0.907174 +vt 0.602622 0.905222 +vt 0.593610 0.900857 +vt 0.604770 0.895458 +vt 0.595446 0.890942 +vt 0.553844 0.919735 +vt 0.558764 0.916170 +vt 0.564183 0.908299 +vt 0.567217 0.907327 +vt 0.570456 0.904246 +vt 0.572714 0.897645 +vt 0.573824 0.887618 +vt 0.547957 0.919475 +vt 0.547247 0.915662 +vt 0.546605 0.907524 +vt 0.546123 0.906397 +vt 0.545682 0.903153 +vt 0.545532 0.896446 +vt 0.545700 0.886378 +vt 0.542286 0.919721 +vt 0.536153 0.916144 +vt 0.529672 0.908258 +vt 0.525804 0.907278 +vt 0.521817 0.904188 +vt 0.519348 0.897581 +vt 0.518608 0.887552 +vt 0.538351 0.920406 +vt 0.528455 0.917485 +vt 0.517922 0.910305 +vt 0.511704 0.909735 +vt 0.505256 0.907073 +vt 0.501178 0.900747 +vt 0.499808 0.890828 +vt 0.537206 0.921348 +vt 0.526215 0.919327 +vt 0.514503 0.913117 +vt 0.507601 0.913109 +vt 0.500437 0.911036 +vt 0.495891 0.905095 +vt 0.494337 0.895326 +vt 0.539158 0.922293 +vt 0.530033 0.921176 +vt 0.520331 0.915940 +vt 0.514594 0.916496 +vt 0.508651 0.915014 +vt 0.504902 0.909460 +vt 0.503661 0.899843 +vt 0.543684 0.922989 +vt 0.538886 0.922537 +vt 0.533844 0.918017 +vt 0.530810 0.918989 +vt 0.527696 0.917942 +vt 0.525799 0.912672 +vt 0.525282 0.903166 +vt 0.549571 0.923249 +vt 0.550403 0.923045 +vt 0.551422 0.918792 +vt 0.551903 0.919919 +vt 0.552470 0.919035 +vt 0.552980 0.913871 +vt 0.553407 0.904407 +vt 0.555241 0.923003 +vt 0.561497 0.922564 +vt 0.568354 0.918058 +vt 0.572222 0.919038 +vt 0.576335 0.918000 +vt 0.579165 0.912735 +vt 0.580499 0.903232 +vt 0.559177 0.922317 +vt 0.569195 0.921223 +vt 0.580104 0.916011 +vt 0.586323 0.916581 +vt 0.592896 0.915115 +vt 0.597334 0.909570 +vt 0.599299 0.899957 +vt 0.559044 0.903560 +vt 0.555560 0.904061 +vt 0.557240 0.903502 +vt 0.562383 0.902441 +vt 0.558853 0.902327 +vt 0.565998 0.899330 +vt 0.560611 0.899155 +vt 0.568167 0.890833 +vt 0.561703 0.890623 +vt 0.555180 0.903480 +vt 0.554825 0.902283 +vt 0.554462 0.899089 +vt 0.554324 0.890544 +vt 0.553180 0.903497 +vt 0.550910 0.902317 +vt 0.548487 0.899141 +vt 0.547155 0.890606 +vt 0.551541 0.903552 +vt 0.547705 0.902423 +vt 0.543596 0.899303 +vt 0.541285 0.890801 +vt 0.550515 0.903635 +vt 0.545699 0.902586 +vt 0.540533 0.899551 +vt 0.537609 0.891098 +vt 0.550258 0.903734 +vt 0.545195 0.902780 +vt 0.539764 0.899847 +vt 0.536687 0.891453 +vt 0.550808 0.903834 +vt 0.546272 0.902975 +vt 0.541408 0.900146 +vt 0.538658 0.891812 +vt 0.552082 0.903920 +vt 0.548764 0.903143 +vt 0.545212 0.900402 +vt 0.543224 0.892119 +vt 0.553886 0.903978 +vt 0.552293 0.903257 +vt 0.550598 0.900576 +vt 0.549688 0.892329 +vt 0.555946 0.904000 +vt 0.556322 0.903301 +vt 0.556747 0.900643 +vt 0.557066 0.892408 +vt 0.557947 0.903983 +vt 0.560237 0.903267 +vt 0.562722 0.900591 +vt 0.564236 0.892346 +vt 0.559585 0.903929 +vt 0.563441 0.903161 +vt 0.567613 0.900428 +vt 0.570106 0.892151 +vt 0.560611 0.903845 +vt 0.565448 0.902998 +vt 0.570676 0.900181 +vt 0.573781 0.891854 +vt 0.560868 0.903746 +vt 0.565951 0.902804 +vt 0.571445 0.899885 +vt 0.574704 0.891499 +vt 0.560318 0.903646 +vt 0.564875 0.902609 +vt 0.569802 0.899586 +vt 0.572732 0.891140 +vt 0.397743 0.903368 +vt 0.394259 0.903868 +vt 0.395939 0.903310 +vt 0.401082 0.902248 +vt 0.397553 0.902134 +vt 0.404697 0.899137 +vt 0.399311 0.898963 +vt 0.406867 0.890641 +vt 0.400403 0.890431 +vt 0.393880 0.903288 +vt 0.393524 0.902090 +vt 0.393162 0.898897 +vt 0.393024 0.890351 +vt 0.391879 0.903305 +vt 0.389610 0.902124 +vt 0.387187 0.898948 +vt 0.385854 0.890414 +vt 0.390241 0.903359 +vt 0.386405 0.902231 +vt 0.382296 0.899111 +vt 0.379985 0.890608 +vt 0.389215 0.903442 +vt 0.384398 0.902393 +vt 0.379233 0.899359 +vt 0.376309 0.890906 +vt 0.388958 0.903541 +vt 0.383895 0.902587 +vt 0.378464 0.899655 +vt 0.375387 0.891261 +vt 0.389508 0.903641 +vt 0.384971 0.902783 +vt 0.380107 0.899953 +vt 0.377358 0.891619 +vt 0.390782 0.903727 +vt 0.387464 0.902951 +vt 0.383912 0.900209 +vt 0.381923 0.891927 +vt 0.392586 0.903786 +vt 0.390993 0.903065 +vt 0.389298 0.900384 +vt 0.388387 0.892136 +vt 0.394645 0.903808 +vt 0.395022 0.903108 +vt 0.395447 0.900450 +vt 0.395766 0.892216 +vt 0.396646 0.903790 +vt 0.398936 0.903074 +vt 0.401422 0.900398 +vt 0.402936 0.892153 +vt 0.398285 0.903736 +vt 0.402141 0.902968 +vt 0.406313 0.900236 +vt 0.408806 0.891959 +vt 0.399310 0.903653 +vt 0.404148 0.902806 +vt 0.409376 0.899988 +vt 0.412481 0.891661 +vt 0.399568 0.903554 +vt 0.404651 0.902612 +vt 0.410145 0.899692 +vt 0.413404 0.891306 +vt 0.399017 0.903454 +vt 0.403575 0.902416 +vt 0.408502 0.899393 +vt 0.411432 0.890948 +vt 0.560050 0.895619 +vt 0.598903 0.933649 +vt 0.559659 0.931810 +vt 0.626960 0.931921 +vt 0.632743 0.931121 +vt 0.633550 0.930793 +vt 0.633940 0.894602 +vt 0.632944 0.930462 +vt 0.633334 0.894271 +vt 0.630952 0.930143 +vt 0.631342 0.893951 +vt 0.627661 0.929849 +vt 0.589170 0.929516 +vt 0.589561 0.893325 +vt 0.398164 0.895426 +vt 0.363809 0.933369 +vt 0.397773 0.931617 +vt 0.330560 0.931567 +vt 0.322401 0.930751 +vt 0.321016 0.894228 +vt 0.320625 0.930420 +vt 0.320649 0.893898 +vt 0.320258 0.930089 +vt 0.321706 0.893582 +vt 0.321316 0.929773 +vt 0.323752 0.929486 +vt 0.361788 0.893053 +vt 0.361398 0.929244 +vt 0.559656 0.902097 +vt 0.555589 0.901346 +vt 0.557317 0.902165 +vt 0.573965 0.927557 +vt 0.563255 0.927865 +vt 0.573734 0.930613 +vt 0.563137 0.930918 +vt 0.570621 0.930463 +vt 0.561812 0.930717 +vt 0.554520 0.902163 +vt 0.550449 0.927858 +vt 0.550467 0.930911 +vt 0.551280 0.930711 +vt 0.552015 0.902093 +vt 0.538979 0.927538 +vt 0.539119 0.930594 +vt 0.541846 0.930447 +vt 0.550473 0.901973 +vt 0.531919 0.926990 +vt 0.532133 0.930052 +vt 0.536040 0.929997 +vt 0.550307 0.901836 +vt 0.531159 0.926362 +vt 0.531382 0.929430 +vt 0.535415 0.929480 +vt 0.551562 0.901718 +vt 0.536905 0.925821 +vt 0.537067 0.928895 +vt 0.540141 0.929035 +vt 0.553901 0.901651 +vt 0.547615 0.925513 +vt 0.547664 0.928591 +vt 0.548949 0.928782 +vt 0.556698 0.901652 +vt 0.560422 0.925520 +vt 0.560334 0.928598 +vt 0.559482 0.928788 +vt 0.559203 0.901723 +vt 0.571891 0.925841 +vt 0.571682 0.928915 +vt 0.568915 0.929052 +vt 0.560745 0.901842 +vt 0.578951 0.926388 +vt 0.578668 0.929457 +vt 0.574722 0.929502 +vt 0.560911 0.901979 +vt 0.579711 0.927017 +vt 0.579419 0.930079 +vt 0.575346 0.930019 +vt 0.398356 0.901905 +vt 0.394289 0.901154 +vt 0.396017 0.901972 +vt 0.412665 0.927365 +vt 0.401954 0.927673 +vt 0.412434 0.930421 +vt 0.401837 0.930726 +vt 0.409321 0.930271 +vt 0.400512 0.930524 +vt 0.394258 0.903992 +vt 0.393220 0.901971 +vt 0.389148 0.927666 +vt 0.389167 0.930719 +vt 0.389979 0.930518 +vt 0.390714 0.901901 +vt 0.377679 0.927345 +vt 0.377819 0.930402 +vt 0.380546 0.930255 +vt 0.389172 0.901781 +vt 0.370618 0.926798 +vt 0.370833 0.929860 +vt 0.374739 0.929804 +vt 0.389006 0.901644 +vt 0.369859 0.926169 +vt 0.370082 0.929238 +vt 0.374115 0.929287 +vt 0.390261 0.901526 +vt 0.375605 0.925629 +vt 0.375767 0.928703 +vt 0.378840 0.928843 +vt 0.392601 0.901458 +vt 0.386315 0.925321 +vt 0.386364 0.928398 +vt 0.387649 0.928590 +vt 0.395398 0.901460 +vt 0.399121 0.925328 +vt 0.399034 0.928406 +vt 0.398181 0.928596 +vt 0.397903 0.901530 +vt 0.410591 0.925648 +vt 0.410382 0.928723 +vt 0.407615 0.928859 +vt 0.399445 0.901650 +vt 0.417651 0.926196 +vt 0.417368 0.929265 +vt 0.413422 0.929310 +vt 0.399611 0.901787 +vt 0.418410 0.926824 +vt 0.418119 0.929886 +vt 0.414046 0.929826 +vn 0.011353 0.401349 0.915830 +vn 0.382275 0.411969 -0.827082 +vn -0.579608 0.385968 -0.717673 +vn -0.581500 0.379101 -0.719810 +vn -0.578936 0.391400 -0.715262 +vn -0.587512 0.353526 -0.727866 +vn -0.281625 0.919126 -0.275399 +vn 0.057253 0.984527 0.165563 +vn 0.071383 0.980926 0.180639 +vn -0.296457 0.908780 -0.293558 +vn 0.474380 0.596789 0.647114 +vn 0.491165 0.565630 0.662404 +vn 0.513932 0.515061 0.685965 +vn 0.517106 0.508774 0.688223 +vn 0.275857 0.401013 -0.873531 +vn -0.191260 0.288827 -0.938047 +vn -0.394971 -0.383221 0.834925 +vn 0.387310 -0.917112 0.094089 +vn 0.623280 0.340129 0.704123 +vn 0.167821 -0.764855 0.621906 +vn 0.346049 -0.937437 -0.037507 +vn 0.028108 -0.899045 0.436933 +vn 0.351848 -0.931425 -0.092898 +vn -0.021699 -0.935636 0.352214 +vn 0.354595 -0.925047 -0.135960 +vn -0.066134 -0.957091 0.282022 +vn 0.350780 -0.921049 -0.169012 +vn 0.518418 -0.764580 -0.382855 +vn 0.338420 -0.920835 -0.193701 +vn 0.202460 -0.979156 -0.015046 +vn 0.316080 -0.924802 -0.211646 +vn 0.010743 -0.996918 -0.077364 +vn 0.282449 -0.932432 -0.225257 +vn -0.263527 -0.963652 0.043672 +vn 0.235603 -0.942106 -0.238563 +vn -0.338664 -0.940489 -0.026948 +vn 0.130589 -0.939299 -0.317209 +vn -0.497787 -0.838588 -0.221198 +vn -0.419324 0.243233 -0.874599 +vn -0.876553 -0.425245 0.225349 +vn 0.628407 0.047426 -0.776421 +vn 0.780297 -0.115146 -0.614673 +vn 0.783563 0.069918 -0.617328 +vn -0.748405 -0.260353 0.609973 +vn -0.749504 -0.261116 0.608264 +vn 0.753014 -0.055422 -0.655629 +vn -0.749474 -0.261025 0.608386 +vn 0.758538 -0.024628 -0.651143 +vn -0.749535 -0.261055 0.608264 +vn 0.767571 -0.000092 -0.640919 +vn -0.749565 -0.261055 0.608234 +vn 0.611438 -0.665090 -0.428663 +vn -0.751213 -0.254250 0.609088 +vn 0.407697 -0.867550 -0.284768 +vn -0.752129 -0.255257 0.607532 +vn 0.356670 -0.868801 -0.343394 +vn 0.296243 -0.863887 -0.407300 +vn 0.220618 -0.847133 -0.483383 +vn 0.021393 -0.758263 -0.651570 +vn -0.141331 -0.391461 -0.909268 +vn -0.187475 -0.241737 0.952055 +vn 0.837397 0.084780 -0.539933 +vn -0.023103 -0.413434 0.910215 +vn 0.824702 0.075686 -0.560442 +vn -0.095370 -0.585253 0.805200 +vn 0.812677 0.068239 -0.578692 +vn -0.154118 -0.691580 0.705618 +vn 0.800623 0.065004 -0.595599 +vn -0.233772 -0.776574 0.584979 +vn 0.788965 0.064425 -0.611011 +vn -0.333689 -0.831782 0.443556 +vn 0.785150 0.052644 -0.617023 +vn -0.415937 -0.817103 0.399091 +vn 0.779382 0.063265 -0.623310 +vn -0.437086 -0.843745 0.311441 +vn 0.767327 0.061159 -0.638295 +vn -0.553789 -0.813898 0.175604 +vn 0.753685 0.061495 -0.654317 +vn -0.658071 -0.750481 0.060671 +vn 0.738456 0.065859 -0.671041 +vn -0.748985 -0.661794 -0.031587 +vn 0.720969 0.071505 -0.689230 +vn -0.847560 -0.510117 -0.146214 +vn -0.934355 -0.298257 -0.194861 +vn -0.388806 -0.092868 0.916593 +vn -0.783807 -0.067476 0.617298 +vn -0.417402 -0.324046 0.848964 +vn 0.045289 -0.536576 0.842616 +vn 0.101627 -0.139500 0.984985 +vn 0.268532 -0.648549 0.712180 +vn 0.342967 -0.179662 0.921964 +vn 0.327982 -0.690237 0.644917 +vn 0.425520 -0.197241 0.883145 +vn 0.333811 -0.702963 0.627979 +vn 0.428297 -0.209754 0.878933 +vn 0.353496 -0.704642 0.615192 +vn 0.454268 -0.208472 0.866115 +vn 0.400616 -0.719047 0.567858 +vn 0.518052 -0.221412 0.826167 +vn 0.455519 -0.674062 0.581439 +vn 0.540757 -0.377819 0.751518 +vn -0.056063 -0.389721 0.919187 +vn -0.071047 -0.408887 0.909787 +vn -0.799554 -0.060579 0.597491 +vn -0.499863 -0.502060 0.705710 +vn -0.068178 -0.820673 0.567278 +vn 0.131443 -0.908658 0.396252 +vn 0.175726 -0.927091 0.331004 +vn 0.185553 -0.931791 0.311930 +vn 0.202643 -0.933988 0.294198 +vn 0.229926 -0.937773 0.260079 +vn 0.249245 -0.927580 0.278268 +vn -0.605335 -0.408643 0.683035 +vn -0.580706 -0.562334 0.588610 +vn -0.185675 -0.914670 0.358959 +vn 0.015992 -0.980255 0.196997 +vn 0.065188 -0.986755 0.148350 +vn 0.079196 -0.987762 0.134129 +vn 0.098361 -0.988250 0.116916 +vn 0.122410 -0.988006 0.093783 +vn 0.182073 -0.973327 0.139500 +vn -0.653920 -0.466628 0.595477 +vn -0.661611 -0.570849 0.486160 +vn -0.310678 -0.929106 0.200537 +vn -0.097995 -0.993866 0.051149 +vn -0.037843 -0.999146 0.014679 +vn -0.021607 -0.999725 0.005341 +vn 0.000305 -0.999939 -0.008972 +vn 0.045381 -0.998901 0.010651 +vn 0.056917 0.996734 0.057009 +vn -0.761834 -0.063967 0.644581 +vn -0.760979 -0.529954 0.374187 +vn -0.492935 -0.869564 0.028474 +vn -0.268410 -0.956328 -0.115513 +vn -0.190405 -0.971160 -0.143376 +vn -0.172277 -0.974181 -0.145756 +vn -0.149602 -0.976074 -0.157720 +vn -0.080416 -0.988525 -0.127750 +vn 0.157567 0.963073 0.218177 +vn -0.774438 -0.080844 0.627430 +vn -0.895108 -0.376080 0.239387 +vn -0.758873 -0.626179 -0.178747 +vn -0.565905 -0.744591 -0.353954 +vn -0.478896 -0.784600 -0.393689 +vn -0.462539 -0.795160 -0.392071 +vn -0.446303 -0.797143 -0.406629 +vn -0.383801 -0.811182 -0.441176 +vn -0.569842 -0.402722 -0.716269 +vn -0.867214 -0.482040 -0.124546 +vn -0.972625 -0.160802 0.167699 +vn -0.913907 -0.254952 -0.315836 +vn -0.789666 -0.307993 -0.530595 +vn -0.731681 -0.333262 -0.594592 +vn -0.719108 -0.343425 -0.604053 +vn -0.711142 -0.341990 -0.614215 +vn -0.645924 -0.361064 -0.672597 +vn -0.589465 -0.359386 -0.723411 +vn -0.558916 -0.355724 -0.749016 +vn -0.984649 -0.017029 0.173559 +vn -0.950407 0.063387 -0.304422 +vn -0.841395 0.103824 -0.530290 +vn -0.806238 0.117710 -0.579699 +vn -0.796869 0.065188 -0.600574 +vn -0.815058 0.082797 -0.573412 +vn -0.747459 0.052217 -0.662221 +vn -0.662618 0.054933 -0.746910 +vn -0.946440 0.184942 0.264534 +vn -0.901151 0.405866 -0.152165 +vn -0.791284 0.484329 -0.373119 +vn -0.758507 0.497940 -0.420301 +vn -0.832942 0.412763 -0.368511 +vn -0.853053 0.386303 -0.350780 +vn -0.759667 0.372906 -0.532762 +vn -0.660024 0.386639 -0.644063 +vn -0.246498 0.354472 -0.901975 +vn -0.869778 0.307108 0.386151 +vn -0.768059 0.638356 0.050264 +vn -0.649892 0.745659 -0.146855 +vn -0.625477 0.757378 -0.187414 +vn -0.755150 0.653676 -0.049348 +vn -0.807489 0.588000 -0.046693 +vn -0.708548 0.652181 -0.269387 +vn -0.584002 0.694327 -0.420484 +vn -0.127415 0.687521 -0.714866 +vn -0.815638 0.347392 0.462600 +vn -0.668355 0.719535 0.188513 +vn -0.535874 0.844081 0.017426 +vn -0.510453 0.859676 -0.017945 +vn -0.661184 0.738060 0.134465 +vn -0.726676 0.668844 0.156682 +vn -0.613208 0.788659 -0.044343 +vn -0.469069 0.859127 -0.204505 +vn -0.005982 0.836024 -0.548601 +vn -0.769311 0.368267 0.521989 +vn -0.581652 0.755638 0.301035 +vn -0.433180 0.887845 0.155126 +vn -0.404614 0.905606 0.127018 +vn -0.570086 0.773553 0.276742 +vn -0.631184 0.706412 0.320200 +vn -0.491775 0.855464 0.162236 +vn -0.328074 0.944578 0.010163 +vn 0.136052 0.927732 -0.347453 +vn -0.751610 0.359294 0.553117 +vn -0.543962 0.760521 0.354472 +vn -0.385083 0.896725 0.218116 +vn -0.352306 0.916196 0.190802 +vn -0.517808 0.788629 0.331462 +vn -0.585437 0.710044 0.391247 +vn -0.428938 0.866573 0.255043 +vn -0.254402 0.960753 0.110477 +vn 0.239692 0.931913 -0.272164 +vn -0.726463 0.373699 0.576678 +vn -0.501083 0.765160 0.404187 +vn -0.335337 0.899319 0.280557 +vn -0.301431 0.917936 0.257851 +vn -0.474563 0.785058 0.398083 +vn -0.523942 0.719474 0.455824 +vn -0.352763 0.870785 0.342387 +vn -0.173925 0.962493 0.208197 +vn -0.035707 0.993988 0.103244 +vn -0.678487 0.364696 0.637684 +vn -0.410749 0.749992 0.518418 +vn -0.224158 0.880764 0.417066 +vn -0.182409 0.898740 0.398663 +vn -0.354961 0.774987 0.522843 +vn -0.385052 0.709708 0.589892 +vn -0.180914 0.837123 0.516190 +vn -0.016968 0.917447 0.397443 +vn -0.462996 0.684591 0.562975 +vn -0.612934 0.339244 0.713553 +vn -0.294046 0.693960 0.657216 +vn -0.084872 0.811884 0.577593 +vn -0.034639 0.826319 0.562120 +vn -0.180486 0.720573 0.669454 +vn -0.199011 0.655660 0.728324 +vn 0.022217 0.734397 0.678335 +vn 0.145665 0.812983 0.563738 +vn -0.714560 0.223823 0.662770 +vn -0.504288 0.240455 0.829341 +vn -0.113254 0.500290 0.858394 +vn 0.119724 0.592853 0.796319 +vn 0.180761 0.608966 0.772301 +vn 0.118778 0.528459 0.840571 +vn 0.082675 0.500137 0.861965 +vn 0.284463 0.498032 0.819147 +vn 0.422620 0.516312 0.744835 +vn 0.035096 0.318186 0.947356 +vn -0.410077 0.052370 0.910520 +vn 0.049562 0.183081 0.981842 +vn 0.291269 0.239875 0.926054 +vn 0.358043 0.252937 0.898770 +vn 0.362987 0.210150 0.907773 +vn 0.343822 0.221992 0.912381 +vn 0.442213 0.193243 0.875820 +vn 0.550554 0.199133 0.810663 +vn -0.318186 0.933988 0.162420 +vn -0.200354 0.971496 0.126591 +vn -0.223548 0.970855 0.086062 +vn -0.066347 0.990966 0.116398 +vn -0.139744 0.989990 0.019105 +vn -0.061495 0.991150 0.117496 +vn -0.134465 0.990814 0.013977 +vn -0.061037 0.991882 0.111362 +vn -0.123630 0.992309 0.003357 +vn -0.057009 0.994201 0.090915 +vn -0.100101 0.994812 -0.016938 +vn -0.012268 0.998962 0.043367 +vn -0.037477 0.996979 -0.067965 +vn 0.029817 0.999237 -0.024323 +vn 0.018677 0.993408 -0.112918 +vn 0.065371 0.994415 -0.082797 +vn 0.053926 0.989196 -0.136113 +vn 0.033235 0.996521 -0.076144 +vn 0.028626 0.992889 -0.115299 +vn -0.016511 0.998688 -0.048128 +vn -0.013733 0.996429 -0.083041 +vn 0.529496 0.712210 -0.460829 +vn 0.523026 0.708579 -0.473617 +vn 0.789056 -0.011536 -0.614185 +vn -0.302988 0.938353 0.166265 +vn -0.265389 0.938994 0.218726 +vn 0.008972 0.974334 0.224860 +vn 0.017914 0.971709 0.235389 +vn 0.009888 0.967406 0.252937 +vn -0.025452 0.968108 0.249153 +vn -0.026399 0.977172 0.210669 +vn -0.000336 0.993500 0.113742 +vn 0.030061 0.999298 0.021210 +vn 0.023957 0.999512 -0.018830 +vn -0.017853 0.999817 0.001221 +vn 0.536332 0.721305 -0.438215 +vn -0.298685 0.932981 0.200690 +vn -0.114658 -0.798791 -0.590564 +vn -0.370708 -0.526780 -0.764885 +vn -0.291025 -0.616688 -0.731407 +vn -0.143254 -0.625172 -0.767205 +vn 0.027223 -0.640553 -0.767418 +vn 0.111332 -0.662038 -0.741111 +vn 0.135563 -0.740410 -0.658315 +vn 0.095309 -0.862575 -0.496811 +vn 0.031892 -0.950316 -0.309610 +vn -0.117801 -0.895688 -0.428724 +vn 0.703726 0.085910 -0.705222 +vn 0.140812 -0.988342 -0.057833 +vn -0.176916 0.112217 -0.977783 +vn -0.463820 -0.000244 -0.885922 +vn -0.396466 0.495773 -0.772637 +vn -0.251259 0.629933 -0.734855 +vn -0.082247 0.613941 -0.785028 +vn 0.062685 0.432813 -0.899289 +vn 0.114200 0.360729 -0.925626 +vn 0.067263 0.509629 -0.857723 +vn -0.147404 0.666036 -0.731162 +vn -0.333659 0.761101 -0.556200 +vn -0.153478 0.913327 -0.377148 +vn -0.370403 -0.745903 -0.553514 +vn -0.117374 0.990661 0.069308 +vn -0.045228 -0.997101 0.061098 +vn -0.457808 -0.775109 -0.435408 +vn -0.716849 -0.364360 -0.594409 +vn -0.842647 0.257424 -0.472915 +vn -0.742363 -0.635609 -0.211829 +vn -0.978973 -0.126621 -0.159734 +vn -0.920560 0.370373 -0.124027 +vn -0.685263 -0.714591 -0.140477 +vn -0.822535 0.452986 -0.343791 +vn -0.424726 0.841395 -0.334086 +vn 0.104862 -0.938810 0.328043 +vn -0.146855 0.961974 -0.230232 +vn -0.592578 0.640126 -0.488907 +vn -0.615009 0.631336 -0.472365 +vn -0.672750 0.632954 -0.383038 +vn -0.732994 0.635304 -0.243019 +vn -0.733726 0.657796 -0.169988 +vn -0.650349 0.747856 -0.133000 +vn -0.509659 0.849849 -0.133976 +vn -0.374187 0.917051 -0.137638 +vn -0.261879 0.943449 -0.203101 +vn -0.750603 0.280526 0.598193 +vn -0.264351 0.961669 0.072787 +vn -0.292032 0.955870 -0.031159 +vn -0.329691 0.920103 -0.211402 +vn -0.339793 0.915555 -0.215125 +vn -0.354900 0.915159 -0.190985 +vn -0.364147 0.919675 -0.146916 +vn -0.327921 0.934049 -0.141392 +vn -0.236305 0.962859 -0.130497 +vn -0.132908 0.981079 -0.140751 +vn -0.095737 0.987732 -0.123142 +vn -0.095767 0.990448 -0.099002 +vn 0.488937 0.717917 -0.495499 +vn -0.306192 0.943205 0.128666 +vn -0.249184 0.966887 0.054659 +vn -0.214362 0.973754 -0.076144 +vn -0.213904 0.973327 -0.082614 +vn -0.208258 0.974456 -0.083956 +vn -0.190619 0.978057 -0.083712 +vn -0.134281 0.983673 -0.119694 +vn -0.065127 0.987915 -0.140446 +vn -0.005676 0.987548 -0.157170 +vn -0.010773 0.991577 -0.128971 +vn -0.047334 0.995025 -0.087466 +vn 0.511277 0.710990 -0.482742 +vn -0.317698 0.934355 0.161168 +vn 0.115726 -0.988556 0.096622 +vn 0.137333 -0.978088 0.156377 +vn 0.149754 -0.969787 0.192450 +vn 0.113804 -0.975402 0.188665 +vn 0.085177 -0.975860 0.201056 +vn 0.090030 -0.983673 0.155797 +vn 0.118778 -0.991150 0.058962 +vn 0.183233 -0.982940 -0.013886 +vn 0.138249 -0.988586 0.059420 +vn 0.134892 -0.989990 0.041383 +vn 0.729453 -0.456862 -0.509049 +vn -0.047090 -0.987243 0.151891 +vn 0.017121 -0.989441 0.143895 +vn 0.119480 -0.983428 0.136174 +vn 0.126865 -0.983886 0.125919 +vn 0.118809 -0.985748 0.119053 +vn 0.100009 -0.988037 0.117252 +vn 0.092471 -0.989349 0.112217 +vn 0.103183 -0.991180 0.082858 +vn 0.137211 -0.989776 0.038453 +vn 0.148564 -0.988800 0.012665 +vn 0.133518 -0.990966 0.011872 +vn 0.619343 -0.669668 -0.409772 +vn -0.082308 -0.983306 0.162206 +vn -0.013428 -0.993072 0.116581 +vn 0.076205 -0.994476 0.071780 +vn 0.089877 -0.994110 0.060427 +vn 0.092868 -0.994140 0.054903 +vn 0.089145 -0.994568 0.053194 +vn 0.092318 -0.994537 0.048616 +vn 0.110874 -0.993408 0.028413 +vn 0.147404 -0.989044 -0.005463 +vn 0.164617 -0.986053 -0.024293 +vn 0.148503 -0.988769 -0.015656 +vn 0.615223 -0.670400 -0.414716 +vn -0.095187 -0.981842 0.163976 +vn -0.030366 -0.994903 0.096072 +vn 0.037080 -0.999023 0.022736 +vn 0.052431 -0.998535 0.012757 +vn 0.059267 -0.998169 0.011414 +vn 0.060518 -0.998016 0.015534 +vn 0.066469 -0.997681 0.014222 +vn 0.091372 -0.995788 0.001312 +vn 0.133305 -0.990692 -0.027131 +vn 0.154118 -0.987274 -0.039003 +vn 0.143284 -0.989319 -0.026276 +vn 0.612751 -0.670492 -0.418226 +vn -0.094028 -0.983001 0.157659 +vn -0.057375 -0.996307 0.063570 +vn -0.016144 -0.999237 -0.034730 +vn -0.002991 -0.999207 -0.039277 +vn 0.003479 -0.999542 -0.030030 +vn 0.002014 -0.999908 -0.012177 +vn 0.004822 -0.999969 -0.002655 +vn 0.037202 -0.999268 -0.006897 +vn 0.092563 -0.995239 -0.029878 +vn 0.117161 -0.992523 -0.033509 +vn 0.112613 -0.993439 -0.018281 +vn 0.609699 -0.669759 -0.423841 +vn -0.078555 -0.987610 0.135685 +vn -0.137303 -0.990387 -0.015900 +vn -0.142460 -0.977447 -0.155675 +vn -0.135044 -0.979888 -0.146733 +vn -0.135533 -0.984924 -0.107364 +vn -0.147191 -0.987518 -0.055879 +vn -0.153020 -0.987945 -0.022248 +vn -0.097934 -0.995117 -0.011658 +vn -0.015992 -0.999634 -0.021455 +vn 0.047945 -0.998474 -0.026307 +vn 0.066897 -0.997559 -0.018983 +vn 0.600665 -0.671346 -0.434065 +vn -0.027772 -0.995697 0.088137 +vn 0.090670 -0.995666 0.019135 +vn 0.090579 -0.995697 0.019044 +vn 0.188879 -0.970733 0.148198 +vn 0.334513 -0.875454 0.348735 +vn 0.092196 -0.995514 0.021088 +vn 0.092257 -0.995483 0.021180 +vn -0.010163 -0.994171 -0.107089 +vn -0.175970 -0.935575 -0.306040 +vn -0.539750 0.570238 -0.619221 +vn 0.411664 0.682363 0.604022 +vn -0.004852 -0.012940 0.999878 +vn 0.696249 0.084201 0.712821 +vn -0.004883 -0.012940 0.999878 +vn 0.990326 0.131718 0.043519 +vn 0.050478 0.019013 -0.998535 +vn -0.983367 -0.129765 -0.126957 +vn -0.108615 -0.026734 0.993713 +vn 0.991089 0.132176 0.014832 +vn 0.062136 0.020569 -0.997833 +vn -0.673421 -0.080905 -0.734764 +vn -0.673421 -0.080905 -0.734794 +vn -0.689810 -0.083254 -0.719169 +vn -0.689810 -0.083285 -0.719169 +vn -0.981964 -0.129429 -0.137730 +vn -0.979644 -0.153935 -0.128636 +vn 0.215613 0.068148 -0.974090 +vn 0.972839 0.151250 0.175115 +vn 0.549547 0.060335 0.833247 +vn 0.530747 0.056856 0.845607 +vn -0.095462 -0.049409 0.994201 +vn -0.961516 -0.147496 -0.231666 +vn -0.523148 -0.055483 -0.850398 +vn 0.187872 0.063875 -0.980102 +vn 0.970794 0.150517 0.186682 +vn -0.106113 -0.051088 0.993011 +vn -0.567309 0.638081 -0.520554 +vn -0.672536 0.613269 -0.414167 +vn -0.600604 0.726829 -0.333079 +vn -0.865627 0.485733 -0.121220 +vn -0.821436 0.567125 -0.059572 +vn -0.932096 0.276009 0.234443 +vn -0.919126 0.294443 0.261635 +vn -0.874447 0.076205 0.479049 +vn -0.883908 0.053224 0.464583 +vn -0.603076 0.791742 0.096896 +vn -0.825739 -0.022523 0.563585 +vn -0.512680 0.740440 -0.434584 +vn -0.416974 0.906156 -0.070406 +vn -0.663503 0.732475 0.152409 +vn -0.827845 0.397473 0.395764 +vn -0.848079 0.094943 0.521256 +vn -0.826167 -0.037660 0.562120 +vn -0.333415 0.927396 -0.169500 +vn -0.134648 0.947172 0.290994 +vn -0.400525 0.795190 0.455214 +vn -0.672384 0.449507 0.588031 +vn -0.789911 0.101962 0.604633 +vn -0.920225 -0.098300 0.378765 +vn -0.052156 0.977294 0.205359 +vn 0.416059 0.251076 0.873959 +vn 0.175665 0.154698 0.972198 +vn -0.109897 -0.090121 0.989837 +vn -0.221870 -0.384716 0.895932 +vn 0.825556 -0.543962 0.150029 +vn 0.010590 -0.999908 0.005737 +vn 0.058046 -0.998260 -0.008972 +vn 0.495163 0.283273 0.821284 +vn -0.225318 -0.960570 0.162877 +vn -0.348491 -0.769860 0.534623 +vn -0.290658 -0.581988 0.759453 +vn 0.808496 -0.539659 0.234657 +vn -0.564837 -0.555742 -0.609973 +vn -0.793847 -0.511612 -0.328593 +vn -0.928495 -0.333506 0.163152 +vn -0.862911 -0.147649 0.483230 +vn 0.801141 0.079134 -0.593188 +vn -0.487472 -0.583026 -0.649922 +vn -0.719382 -0.151708 -0.677816 +vn -0.918424 -0.140019 -0.369945 +vn -0.987701 -0.086550 0.129978 +vn -0.884121 -0.044404 0.465102 +vn 0.804407 0.067415 -0.590197 +vn -0.601794 -0.283303 -0.746666 +vn -0.756035 0.248848 -0.605335 +vn -0.935301 0.212775 -0.282632 +vn -0.976867 0.133824 0.166753 +vn -0.877590 0.049898 0.476791 +vn 0.810999 0.054262 -0.582507 +vn -0.590533 0.500595 -0.632954 +vn -0.717277 0.486373 -0.498917 +vn -0.895657 0.404645 -0.184393 +vn -0.943510 0.251595 0.215583 +vn -0.863338 0.100070 0.494583 +vn 0.816828 0.033052 -0.575884 +vn -0.622181 0.495407 -0.606128 +vn 0.305063 0.740837 0.598376 +vn 0.108097 0.810297 0.575915 +vn 0.178533 0.713523 0.677450 +vn -0.196326 0.640767 0.742180 +vn -0.140599 0.571123 0.808710 +vn -0.506272 0.343059 0.791162 +vn -0.481643 0.329081 0.812220 +vn -0.671957 0.078188 0.736412 +vn -0.685629 0.098453 0.721244 +vn -0.746391 -0.013153 0.665365 +vn -0.312754 0.825800 0.469253 +vn 0.226600 0.827509 0.513627 +vn -0.115696 0.941649 0.316019 +vn -0.376385 0.766289 0.520676 +vn -0.622211 0.421674 0.659536 +vn -0.721458 0.109836 0.683645 +vn -0.743767 -0.027955 0.667837 +vn -0.001251 0.966521 0.256508 +vn -0.398022 0.916166 -0.046754 +vn -0.608875 0.770653 0.187964 +vn -0.773766 0.437574 0.458022 +vn -0.788141 0.102176 0.606922 +vn -0.584918 -0.058779 0.808954 +vn -0.297617 0.948363 -0.109470 +vn -0.762291 0.112308 -0.637410 +vn -0.908078 0.027039 -0.417859 +vn -0.974181 -0.191931 -0.118686 +vn -0.885250 -0.462874 0.045076 +vn 0.108097 -0.628437 -0.770287 +vn 0.111484 -0.991974 0.059542 +vn 0.085665 -0.991058 0.102023 +vn -0.694693 0.143132 -0.704886 +vn -0.127873 -0.949065 0.287851 +vn -0.533952 -0.791681 0.296762 +vn -0.753136 -0.636464 0.166173 +vn -0.002838 -0.644154 -0.764885 +vn 0.497726 -0.430586 0.752861 +vn 0.165593 -0.398602 0.902036 +vn -0.357982 -0.266305 0.894925 +vn -0.666982 -0.124607 0.734550 +vn 0.763817 0.074709 -0.641072 +vn 0.557848 -0.459914 0.690817 +vn 0.489090 -0.009369 0.872158 +vn 0.141697 -0.015137 0.989776 +vn -0.362529 -0.012940 0.931852 +vn -0.663869 -0.018494 0.747581 +vn 0.762780 0.062502 -0.643605 +vn 0.596301 -0.142186 0.790033 +vn 0.374584 0.382000 0.844813 +vn 0.022004 0.325541 0.945250 +vn -0.414838 0.200018 0.887631 +vn -0.681906 0.072970 0.727775 +vn 0.758141 0.048006 -0.650288 +vn 0.419935 0.619617 0.663076 +vn 0.260475 0.601550 0.755150 +vn -0.079928 0.500717 0.861873 +vn -0.464095 0.308054 0.830470 +vn -0.699973 0.119327 0.704093 +vn 0.755120 0.025727 -0.655049 +vn 0.386670 0.614246 0.687857 +vn -0.797174 -0.603107 -0.026521 +vn -0.244301 -0.755242 -0.608173 +vn 0.250954 -0.962004 -0.107303 +vn -0.112033 -0.515458 0.849513 +vn 0.600024 -0.644642 0.473647 +vn 0.108371 -0.565691 -0.817438 +vn 0.871578 -0.463698 0.159001 +vn 0.153172 -0.253609 -0.955077 +vn 0.984649 -0.136814 0.108341 +vn 0.192694 -0.028748 -0.980804 +vn 0.995270 0.083193 0.049776 +vn 0.059358 0.501267 -0.863247 +vn 0.796503 0.598743 0.084140 +vn -0.371776 0.692984 -0.617664 +vn 0.433210 0.798486 0.417951 +vn -0.580859 0.677633 -0.450972 +vn 0.224006 0.783013 0.580218 +vn -0.743370 0.583178 -0.327494 +vn 0.075320 0.691855 0.718070 +vn -0.787133 0.522355 -0.327952 +vn 0.071963 0.637623 0.766930 +vn -0.818659 0.513230 -0.257607 +vn -0.003662 0.622639 0.782464 +vn -0.911527 0.376415 -0.165441 +vn -0.102359 0.487228 0.867244 +vn -0.980560 0.178625 -0.081088 +vn -0.182897 0.289376 0.939573 +vn -0.523942 -0.729637 0.439375 +vn -0.776849 -0.229896 0.586200 +vn -0.699820 -0.676443 0.229469 +vn -0.117740 -0.979553 0.162999 +vn -0.408979 -0.893521 -0.185186 +vn 0.164525 -0.985473 -0.041627 +vn -0.152776 -0.891842 -0.425672 +vn 0.689962 -0.561754 -0.456435 +vn 0.454360 -0.492691 -0.742149 +vn 0.777703 0.233924 -0.583453 +vn -0.864254 -0.495743 0.085330 +vn -0.681845 -0.595752 -0.424390 +vn -0.450972 -0.565142 -0.690786 +vn 0.231727 -0.250313 -0.940001 +vn -0.972015 -0.229987 0.047243 +vn -0.859523 -0.156987 -0.486312 +vn -0.646535 -0.082919 -0.758324 +vn 0.085025 0.109104 -0.990356 +vn -0.990478 0.048311 0.128666 +vn -0.887539 0.300302 -0.349345 +vn -0.679220 0.417493 -0.603565 +vn 0.060488 0.482131 -0.873989 +vn -0.915403 0.260872 0.306497 +vn -0.761162 0.646199 -0.054811 +vn -0.543809 0.792535 -0.275918 +vn 0.161473 0.760338 -0.629109 +vn -0.769860 0.352367 0.532090 +vn -0.521287 0.792932 0.315378 +vn -0.284402 0.949736 0.130772 +vn 0.353923 0.876370 -0.326548 +vn -0.592029 0.299966 0.747978 +vn -0.230628 0.706900 0.668630 +vn 0.032563 0.856044 0.515854 +vn 0.588946 0.807154 -0.040101 +vn -0.427168 0.113987 0.896939 +vn 0.038881 0.404797 0.913541 +vn 0.329691 0.528428 0.782311 +vn 0.810205 0.564165 0.158971 +vn 0.777703 0.233955 -0.583422 +vn -0.320933 -0.157964 0.933805 +vn 0.213782 -0.038301 0.976104 +vn 0.524552 0.045381 0.850154 +vn 0.956145 0.204260 0.209815 +vn -0.304483 -0.437086 0.846278 +vn 0.242317 -0.495590 0.834040 +vn 0.557573 -0.454939 0.694327 +vn 0.981292 -0.168584 0.092654 +vn -0.379955 -0.644246 0.663717 +vn 0.119327 -0.837092 0.533830 +vn 0.423261 -0.829066 0.365337 +vn 0.881680 -0.446150 -0.153417 +vn -0.488205 -0.725425 0.485153 +vn -0.733879 -0.224799 0.640980 +vn -0.678182 -0.676565 0.286843 +vn -0.099948 -0.977447 0.185858 +vn -0.414655 -0.898648 -0.143132 +vn 0.167852 -0.985076 -0.037324 +vn -0.175298 -0.899380 -0.400403 +vn 0.661214 -0.565142 -0.493301 +vn 0.406384 -0.501968 -0.763451 +vn 0.734886 0.228889 -0.638356 +vn -0.853175 -0.499100 0.151402 +vn -0.705008 -0.606250 -0.367931 +vn -0.492904 -0.578600 -0.649770 +vn 0.169286 -0.264016 -0.949522 +vn -0.965056 -0.234626 0.116550 +vn -0.889401 -0.169530 -0.424482 +vn -0.695791 -0.098636 -0.711417 +vn 0.017121 0.093722 -0.995422 +vn -0.979827 0.044832 0.194708 +vn -0.911100 0.289743 -0.293100 +vn -0.721244 0.404004 -0.562609 +vn -0.001923 0.468429 -0.883480 +vn 0.734916 0.228889 -0.638356 +vn -0.894284 0.260659 0.363689 +vn -0.767388 0.641011 -0.013215 +vn -0.566454 0.784967 -0.250771 +vn 0.113529 0.751061 -0.650380 +vn -0.734428 0.356548 0.577441 +vn -0.504013 0.794977 0.337565 +vn -0.281198 0.950102 0.134922 +vn 0.325205 0.873012 -0.363384 +vn -0.542070 0.308603 0.781579 +vn -0.189550 0.716208 0.671590 +vn 0.061708 0.864345 0.499069 +vn 0.579547 0.809656 -0.092471 +vn 0.734886 0.228858 -0.638356 +vn -0.366283 0.125919 0.921934 +vn 0.097873 0.419568 0.902402 +vn 0.378338 0.542711 0.749840 +vn 0.815332 0.571123 0.094913 +vn -0.255989 -0.144780 0.955748 +vn 0.279550 -0.021516 0.959868 +vn 0.580523 0.061892 0.811853 +vn 0.966765 0.212897 0.141392 +vn -0.243629 -0.425153 0.871670 +vn 0.301462 -0.480850 0.823328 +vn 0.606250 -0.440687 0.661977 +vn 0.986419 -0.161626 0.028596 +vn -0.329936 -0.635670 0.697867 +vn 0.160710 -0.827784 0.537492 +vn 0.452467 -0.820734 0.348704 +vn 0.872219 -0.443617 -0.205817 +vn -0.279611 -0.239448 0.929746 +vn -0.728965 -0.331675 0.598804 +vn -0.241798 -0.437696 0.865963 +vn 0.251595 -0.073305 0.965026 +vn 0.314890 -0.405164 0.858272 +vn 0.554064 0.045930 0.831172 +vn 0.623554 -0.318369 0.713981 +vn 0.945769 0.279214 0.165960 +vn 0.996704 0.012146 0.080050 +vn 0.728965 0.331675 -0.598804 +vn 0.728965 0.331675 -0.598773 +vn -0.260384 -0.611774 0.746940 +vn 0.283761 -0.696493 0.659017 +vn 0.589404 -0.638203 0.495224 +vn 0.971679 -0.222236 -0.080233 +vn -0.332560 -0.735099 0.590747 +vn 0.162969 -0.902921 0.397626 +vn 0.456801 -0.864834 0.208258 +vn 0.874447 -0.388348 -0.290597 +vn -0.447310 -0.788965 0.421186 +vn -0.029084 -0.993042 0.113865 +vn 0.245918 -0.963744 -0.103214 +vn 0.719901 -0.460891 -0.518937 +vn -0.587207 -0.765099 0.264107 +vn -0.263222 -0.953124 -0.149022 +vn -0.011078 -0.919919 -0.391858 +vn 0.531480 -0.428755 -0.730522 +vn 0.728965 0.331706 -0.598804 +vn -0.730918 -0.667196 0.143406 +vn -0.503769 -0.789239 -0.351085 +vn -0.275155 -0.740013 -0.613666 +vn 0.337901 -0.296884 -0.893094 +vn -0.856594 -0.510117 0.077425 +vn -0.714072 -0.526353 -0.461501 +vn -0.506058 -0.451430 -0.734886 +vn 0.168676 -0.085330 -0.981964 +vn -0.945067 -0.317820 0.076235 +vn -0.862178 -0.204505 -0.463485 +vn -0.668630 -0.098056 -0.737083 +vn 0.049501 0.173650 -0.983551 +vn -0.982879 -0.119541 0.140019 +vn -0.925474 0.127354 -0.356700 +vn -0.738121 0.266243 -0.619861 +vn -0.001404 0.440687 -0.897641 +vn -0.964293 0.054506 0.259072 +vn -0.894345 0.418653 -0.157476 +vn -0.703970 0.586077 -0.401135 +vn 0.023591 0.675130 -0.737297 +vn 0.728935 0.331706 -0.598804 +vn -0.892117 0.177862 0.415265 +vn -0.773553 0.625111 0.103916 +vn -0.571368 0.812708 -0.114170 +vn 0.120792 0.841243 -0.526933 +vn -0.777367 0.231697 0.584796 +vn -0.581469 0.715232 0.387677 +vn -0.360485 0.911618 0.197333 +vn 0.275369 0.913755 -0.298593 +vn -0.637471 0.207831 0.741874 +vn -0.347331 0.675314 0.650594 +vn -0.103458 0.867794 0.485977 +vn 0.463790 0.881649 -0.087039 +vn -0.493759 0.109928 0.862606 +vn -0.106815 0.511429 0.852626 +vn 0.160588 0.687887 0.707785 +vn 0.657338 0.749779 0.075533 +vn -0.368084 -0.047121 0.928587 +vn 0.103488 0.248543 0.963042 +vn 0.391491 0.399304 0.829005 +vn 0.826594 0.538224 0.164373 +vn 0.869930 0.207892 -0.447157 +vn 0.764183 0.197546 -0.613941 +vn 0.834101 0.302194 -0.461409 +vn 0.965789 0.207770 -0.155034 +vn 0.877438 0.440321 -0.190191 +vn 0.906308 0.209906 -0.366710 +vn 0.854885 0.345256 -0.387158 +vn 0.889004 0.209113 -0.407300 +vn 0.845332 0.324046 -0.424696 +vn 0.972869 0.189856 0.131993 +vn 0.842891 0.532029 0.080264 +vn 0.805780 0.125584 0.578692 +vn 0.631306 0.584796 0.509323 +vn 0.701254 0.095614 0.706442 +vn 0.520554 0.571215 0.634571 +vn 0.775536 0.367412 -0.513291 +vn 0.733024 0.601154 -0.318217 +vn 0.770806 0.438887 -0.461684 +vn 0.773949 0.403546 -0.487960 +vn 0.630360 0.768731 -0.108066 +vn 0.346110 0.902432 0.256508 +vn 0.225166 0.900174 0.372753 +vn 0.709952 0.386090 -0.588977 +vn 0.571215 0.647175 -0.504776 +vn 0.676626 0.465712 -0.570299 +vn 0.693960 0.426313 -0.580187 +vn 0.392254 0.836482 -0.382641 +vn 0.026582 0.993347 -0.111942 +vn -0.105747 0.994324 -0.008850 +vn 0.654866 0.353191 -0.668111 +vn 0.435377 0.566088 -0.699942 +vn 0.597552 0.418500 -0.683920 +vn 0.626820 0.386212 -0.676656 +vn 0.192389 0.717124 -0.669820 +vn -0.241646 0.833186 -0.497330 +vn -0.383557 0.828486 -0.408002 +vn 0.625080 0.277566 -0.729514 +vn 0.361919 0.379589 -0.851405 +vn 0.554796 0.309915 -0.772057 +vn 0.590503 0.294046 -0.751518 +vn 0.084262 0.442701 -0.892666 +vn -0.386700 0.464888 -0.796411 +vn -0.533799 0.447035 -0.717765 +vn 0.628559 0.179479 -0.756737 +vn 0.370525 0.137669 -0.918546 +vn 0.559801 0.169073 -0.811151 +vn 0.594775 0.174444 -0.784722 +vn 0.096927 0.086673 -0.991485 +vn -0.369732 -0.012848 -0.929014 +vn -0.516221 -0.047761 -0.855098 +vn 0.664388 0.085177 -0.742485 +vn 0.458876 -0.094852 -0.883389 +vn 0.611225 0.033723 -0.790704 +vn 0.638417 0.059511 -0.767357 +vn 0.226936 -0.255470 -0.939787 +vn -0.195257 -0.472060 -0.859645 +vn -0.335521 -0.523392 -0.783227 +vn 0.722953 0.019959 -0.690573 +vn 0.603290 -0.255684 -0.755394 +vn 0.695303 -0.059877 -0.716178 +vn 0.709830 -0.019990 -0.704062 +vn 0.439467 -0.492141 -0.751396 +vn 0.089908 -0.789697 -0.606830 +vn -0.040132 -0.852351 -0.521409 +vn 0.788568 0.001282 -0.614917 +vn 0.765099 -0.301706 -0.568804 +vn 0.789483 -0.086673 -0.607562 +vn 0.789788 -0.042726 -0.611835 +vn 0.677572 -0.559893 -0.476852 +vn 0.409467 -0.880612 -0.238350 +vn 0.290780 -0.946501 -0.139775 +vn 0.843623 0.034150 -0.535783 +vn 0.900937 -0.220618 -0.373638 +vn 0.868557 -0.039460 -0.493973 +vn 0.856929 -0.002655 -0.515366 +vn 0.877438 -0.440565 -0.189642 +vn 0.677694 -0.720481 0.147008 +vn 0.568590 -0.780633 0.259346 +vn 0.873409 0.109806 -0.474380 +vn 0.974395 -0.034120 -0.222205 +vn 0.911313 0.069063 -0.405805 +vn 0.893246 0.089511 -0.440504 +vn 0.985534 -0.166112 0.033174 +vn 0.822748 -0.352153 0.446089 +vn 0.718833 -0.399182 0.569109 +vn 0.862636 0.241218 -0.444533 +vn 0.756767 0.231544 -0.611286 +vn 0.824213 0.334788 -0.456679 +vn 0.959288 0.237495 -0.152715 +vn 0.864498 0.468215 -0.182653 +vn 0.899197 0.242531 -0.364147 +vn 0.844020 0.376812 -0.381573 +vn 0.881771 0.242103 -0.404706 +vn 0.834925 0.356151 -0.419507 +vn 0.967681 0.213660 0.133854 +vn 0.828181 0.553178 0.089785 +vn 0.803674 0.135289 0.579455 +vn 0.616474 0.590899 0.520310 +vn 0.700339 0.099704 0.706778 +vn 0.506485 0.571612 0.645497 +vn 0.763726 0.399457 -0.507035 +vn 0.715354 0.627735 -0.306864 +vn 0.757195 0.469680 -0.453902 +vn 0.761193 0.435011 -0.480911 +vn 0.608722 0.787896 -0.092959 +vn 0.321940 0.905911 0.275033 +vn 0.201453 0.897855 0.391461 +vn 0.697409 0.417951 -0.582141 +vn 0.551836 0.673269 -0.492080 +vn 0.662008 0.496200 -0.561693 +vn 0.680380 0.457503 -0.572466 +vn 0.368084 0.854915 -0.365490 +vn -0.000977 0.995849 -0.090670 +vn -0.133000 0.991028 0.012665 +vn 0.643055 0.385266 -0.661824 +vn 0.417737 0.592669 -0.688620 +vn 0.583941 0.449263 -0.676107 +vn 0.614093 0.417676 -0.669637 +vn 0.170751 0.736290 -0.654714 +vn -0.265786 0.836665 -0.478835 +vn -0.407270 0.826136 -0.389294 +vn 0.615162 0.310160 -0.724784 +vn 0.348979 0.407514 -0.843867 +vn 0.543901 0.341472 -0.766472 +vn 0.580096 0.326151 -0.746361 +vn 0.069582 0.463820 -0.883175 +vn -0.401532 0.470992 -0.785394 +vn -0.547868 0.447432 -0.706809 +vn 0.621265 0.212806 -0.754112 +vn 0.364025 0.167394 -0.916196 +vn 0.552660 0.201697 -0.808588 +vn 0.587542 0.207465 -0.782128 +vn 0.091739 0.110477 -0.989624 +vn -0.371838 -0.003143 -0.928251 +vn -0.517106 -0.043641 -0.854762 +vn 0.659688 0.119236 -0.741966 +vn 0.458815 -0.063295 -0.886258 +vn 0.607837 0.067385 -0.791162 +vn 0.634388 0.093417 -0.767327 +vn 0.231208 -0.229011 -0.945555 +vn -0.184667 -0.458785 -0.869106 +vn -0.323252 -0.515549 -0.793512 +vn 0.720176 0.054567 -0.691610 +vn 0.607959 -0.222816 -0.762047 +vn 0.694662 -0.025422 -0.718863 +vn 0.708121 0.014557 -0.705924 +vn 0.450667 -0.463729 -0.762749 +vn 0.109836 -0.773766 -0.623829 +vn -0.018220 -0.841792 -0.539476 +vn 0.786493 0.036073 -0.616504 +vn 0.771477 -0.268349 -0.576830 +vn 0.789850 -0.051943 -0.611042 +vn 0.788934 -0.007935 -0.614368 +vn 0.691305 -0.530747 -0.490249 +vn 0.432783 -0.863735 -0.258095 +vn 0.316233 -0.934965 -0.160680 +vn 0.840877 0.068789 -0.536790 +vn 0.905576 -0.187750 -0.380291 +vn 0.867916 -0.005036 -0.496628 +vn 0.855251 0.031892 -0.517228 +vn 0.888638 -0.412152 -0.200995 +vn 0.697592 -0.704550 0.130039 +vn 0.590503 -0.770074 0.241279 +vn 0.868740 0.143864 -0.473861 +vn 0.974334 -0.002594 -0.225043 +vn 0.907956 0.102756 -0.406262 +vn 0.889218 0.123417 -0.440474 +vn 0.989807 -0.139653 0.027406 +vn 0.833369 -0.338878 0.436598 +vn 0.731101 -0.391369 0.558794 +vn 0.862636 0.241218 -0.444502 +vn 0.899197 0.242500 -0.364147 +vn 0.506485 0.571612 0.645527 +vn 0.763726 0.399487 -0.507035 +vn 0.757195 0.469680 -0.453871 +vn 0.697439 0.417951 -0.582141 +vn -0.133000 0.991028 0.012696 +vn 0.583911 0.449263 -0.676107 +vn 0.348979 0.407483 -0.843867 +vn 0.543931 0.341472 -0.766472 +vn -0.401532 0.470992 -0.785424 +vn 0.091708 0.110477 -0.989624 +vn 0.659719 0.119236 -0.741966 +vn 0.694662 -0.025452 -0.718863 +vn 0.109836 -0.773797 -0.623829 +vn 0.786493 0.036103 -0.616504 +vn 0.771477 -0.268349 -0.576861 +vn 0.840877 0.068789 -0.536821 +vn 0.855220 0.031892 -0.517228 +vn 0.907956 0.102725 -0.406262 +vn 0.731101 -0.391369 0.558824 +vn 0.841060 0.204260 -0.500870 +vn 0.784539 0.059572 -0.617145 +vn 0.798791 0.241432 -0.550981 +vn 0.865413 0.414045 -0.282052 +vn 0.760613 0.506211 -0.406415 +vn 0.723441 0.675710 0.141453 +vn 0.536637 0.839961 -0.080142 +vn 0.548235 0.746971 0.376080 +vn 0.335978 0.933622 0.124271 +vn 0.752129 0.250740 -0.609424 +vn 0.644826 0.529283 -0.551347 +vn 0.330332 0.881069 -0.338450 +vn 0.101566 0.980316 -0.169195 +vn 0.708152 0.230750 -0.667257 +vn 0.535752 0.479720 -0.694845 +vn 0.135899 0.792749 -0.594165 +vn -0.119327 0.879971 -0.459731 +vn 0.673544 0.184545 -0.715720 +vn 0.449904 0.365093 -0.814997 +vn -0.017029 0.588488 -0.808313 +vn -0.293130 0.647877 -0.703055 +vn 0.653584 0.119144 -0.747368 +vn 0.400433 0.202857 -0.893582 +vn -0.105197 0.299326 -0.948302 +vn -0.393292 0.319315 -0.862148 +vn 0.651326 0.044496 -0.757439 +vn 0.394848 0.017670 -0.918546 +vn -0.115177 -0.030641 -0.992859 +vn -0.404645 -0.055605 -0.912748 +vn 0.667104 -0.028016 -0.744407 +vn 0.433973 -0.162206 -0.886196 +vn -0.045442 -0.351268 -0.935148 +vn -0.325419 -0.419904 -0.847194 +vn 0.698508 -0.087374 -0.710196 +vn 0.511856 -0.309458 -0.801355 +vn 0.093356 -0.613666 -0.783990 +vn -0.167669 -0.718070 -0.675436 +vn 0.740776 -0.124516 -0.660085 +vn 0.616688 -0.401624 -0.677023 +vn 0.280160 -0.777917 -0.562395 +vn 0.044557 -0.904721 -0.423658 +vn 0.787439 -0.133824 -0.601642 +vn 0.732444 -0.424696 -0.532060 +vn 0.486465 -0.819056 -0.304086 +vn 0.278970 -0.951415 -0.130131 +vn 0.831416 -0.113834 -0.543809 +vn 0.841578 -0.375134 -0.388592 +vn 0.680929 -0.730735 -0.048372 +vn 0.499893 -0.851070 0.160375 +vn 0.866024 -0.067629 -0.495376 +vn 0.927396 -0.260506 -0.268410 +vn 0.833857 -0.526444 0.165746 +vn 0.673696 -0.618976 0.403699 +vn 0.885983 -0.002228 -0.463698 +vn 0.976867 -0.098239 -0.189856 +vn 0.922025 -0.237312 0.305765 +vn 0.773888 -0.290414 0.562761 +vn 0.888241 0.072390 -0.453627 +vn 0.982452 0.086886 -0.164861 +vn 0.932005 0.092654 0.350291 +vn 0.785211 0.084506 0.613392 +vn 0.872463 0.144902 -0.466659 +vn 0.943327 0.266793 -0.197241 +vn 0.862270 0.413282 0.292611 +vn 0.705985 0.448805 0.547838 +vn 0.841060 0.204291 -0.500870 +vn 0.784570 0.059572 -0.617145 +vn 0.760613 0.506211 -0.406384 +vn 0.723441 0.675680 0.141423 +vn 0.336009 0.933622 0.124271 +vn 0.535722 0.479720 -0.694845 +vn 0.135899 0.792749 -0.594134 +vn -0.016999 0.588488 -0.808313 +vn -0.293100 0.647877 -0.703055 +vn 0.400433 0.202857 -0.893551 +vn 0.651326 0.044496 -0.757469 +vn 0.394818 0.017670 -0.918577 +vn -0.045442 -0.351238 -0.935148 +vn 0.093387 -0.613666 -0.783990 +vn 0.280160 -0.777947 -0.562395 +vn 0.486496 -0.819056 -0.304056 +vn 0.279000 -0.951415 -0.130131 +vn 0.841578 -0.375134 -0.388562 +vn 0.499924 -0.851070 0.160375 +vn 0.866024 -0.067629 -0.495346 +vn 0.976867 -0.098270 -0.189856 +vn 0.922056 -0.237281 0.305765 +vn 0.982452 0.086886 -0.164830 +vn 0.932035 0.092685 0.350291 +vn 0.785241 0.084506 0.613392 +vn 0.872463 0.144932 -0.466659 +vn 0.943327 0.266793 -0.197211 +vn 0.862300 0.413282 0.292611 +vn 0.705985 0.448805 0.547807 +vn -0.602557 -0.161321 -0.781579 +vn 0.115879 -0.991913 0.051546 +vn 0.569872 -0.461409 0.679922 +vn 0.613330 -0.220466 0.758415 +vn 0.618824 -0.013581 0.785363 +vn 0.597156 0.195227 0.777978 +vn 0.547441 0.400739 0.734611 +vn 0.291025 0.843501 0.451399 +vn -0.326334 0.885983 -0.329356 +vn 0.618976 -0.017457 0.785180 +vn 0.066836 -0.997681 -0.011322 +vn -0.473830 -0.584338 -0.658773 +vn -0.560137 -0.358684 -0.746696 +vn -0.603107 -0.157506 -0.781915 +vn -0.619861 0.051881 -0.782983 +vn -0.608539 0.264595 -0.748070 +vn -0.438063 0.757622 -0.483779 +vn 0.158361 0.943083 0.292367 +vn -0.298898 -0.455184 0.838710 +vn -0.785272 -0.055574 0.616627 +vn -0.454390 -0.633290 0.626423 +vn 0.329417 -0.651906 0.682974 +vn 0.086978 -0.932493 0.350475 +vn 0.897397 -0.441145 0.003815 +vn 0.716025 -0.653005 -0.246620 +vn -0.653432 -0.658773 0.372845 +vn -0.224281 -0.973388 -0.046602 +vn 0.481399 -0.685232 -0.546495 +vn -0.843684 -0.517258 0.143559 +vn -0.521531 -0.751701 -0.403577 +vn 0.255623 -0.517869 -0.816340 +vn -0.969604 -0.244484 0.005127 +vn -0.717399 -0.325297 -0.616016 +vn 0.106540 -0.194739 -0.975036 +vn -0.996765 0.080172 -0.004120 +vn -0.758629 0.180090 -0.626087 +vn 0.074801 0.186163 -0.979644 +vn -0.922208 0.369335 0.114292 +vn -0.642232 0.628254 -0.439070 +vn 0.161504 0.522233 -0.837336 +vn -0.766472 0.551256 0.329508 +vn -0.400708 0.910184 -0.104678 +vn 0.342265 0.734306 -0.586200 +vn -0.566881 0.576373 0.588549 +vn -0.091281 0.950774 0.296030 +vn 0.575640 0.766411 -0.284951 +vn 0.784570 0.059542 -0.617145 +vn -0.376293 0.430647 0.820307 +vn 0.205054 0.727439 0.654775 +vn 0.800806 0.598712 -0.014435 +vn -0.250587 0.154088 0.955718 +vn 0.401807 0.299661 0.865291 +vn 0.950530 0.275399 0.143498 +vn -0.224036 -0.170202 0.959593 +vn 0.444899 -0.205390 0.871700 +vn 0.983520 -0.105411 0.146764 +vn -0.298898 -0.455153 0.838710 +vn -0.454360 -0.633290 0.626453 +vn 0.897427 -0.441115 0.003815 +vn 0.716056 -0.653005 -0.246651 +vn 0.478042 0.346324 -0.807154 +vn 0.590289 0.472304 -0.654561 +vn 0.786279 0.049104 -0.615894 +vn -0.653401 -0.658773 0.372845 +vn 0.732505 0.487350 -0.475265 +vn 0.866543 0.386975 -0.315104 +vn 0.106510 -0.194739 -0.975036 +vn 0.956420 0.196387 -0.216041 +vn -0.996765 0.080203 -0.004120 +vn -0.758660 0.180090 -0.626057 +vn 0.074770 0.186193 -0.979644 +vn 0.977935 -0.034547 -0.205969 +vn -0.922208 0.369366 0.114322 +vn 0.161473 0.522233 -0.837367 +vn 0.925565 -0.243690 -0.289712 +vn -0.766472 0.551256 0.329539 +vn 0.342235 0.734306 -0.586200 +vn 0.813379 -0.373730 -0.445723 +vn -0.566851 0.576373 0.588549 +vn 0.671163 -0.388348 -0.631397 +vn -0.376263 0.430647 0.820307 +vn 0.205023 0.727439 0.654775 +vn 0.800806 0.598743 -0.014435 +vn 0.537034 -0.283303 -0.794519 +vn 0.950530 0.275369 0.143498 +vn 0.447127 -0.088626 -0.890042 +vn 0.444899 -0.205390 0.871670 +vn 0.983520 -0.105411 0.146733 +vn 0.425611 0.141789 -0.893704 +s 1 +f 1204/1/1 1236/2/2 1176/3/3 +f 1132/4/4 1176/3/3 1177/5/5 +f 1131/6/6 1177/5/5 980/7/7 +f 980/7/7 448/8/8 466/9/9 +f 940/10/10 980/7/7 466/9/9 +f 466/9/9 448/8/8 175/11/11 +f 223/12/12 175/11/11 174/13/13 +f 224/14/14 174/13/13 117/15/15 +f 1204/1/1 1176/3/3 1132/4/4 +f 1132/4/4 1177/5/5 1131/6/6 +f 1131/6/6 980/7/7 940/10/10 +f 466/9/9 175/11/11 223/12/12 +f 223/12/12 174/13/13 224/14/14 +f 224/14/14 117/15/15 148/16/16 +f 1260/17/17 1208/18/18 1238/19/19 +f 1253/20/20 1109/21/21 1208/18/18 +f 1183/22/22 979/23/23 1109/21/21 +f 1013/24/24 868/25/25 979/23/23 +f 889/26/26 760/27/27 868/25/25 +f 760/27/27 580/28/28 593/29/29 +f 773/30/30 580/28/28 760/27/27 +f 580/28/28 487/31/31 593/29/29 +f 464/32/32 378/33/33 487/31/31 +f 341/34/34 247/35/35 378/33/33 +f 170/36/36 144/37/37 247/35/35 +f 102/38/38 119/39/39 144/37/37 +f 119/39/39 1260/17/17 1238/19/19 +f 95/40/40 1260/17/17 119/39/39 +f 1260/17/17 1253/20/20 1208/18/18 +f 1253/20/20 1183/22/22 1109/21/21 +f 1183/22/22 1013/24/24 979/23/23 +f 1013/24/24 889/26/26 868/25/25 +f 889/26/26 773/30/30 760/27/27 +f 580/28/28 464/32/32 487/31/31 +f 464/32/32 341/34/34 378/33/33 +f 341/34/34 170/36/36 247/35/35 +f 170/36/36 102/38/38 144/37/37 +f 102/38/38 95/40/40 119/39/39 +f 1237/41/41 1207/42/42 661/43/43 +f 1237/41/41 1253/20/20 1260/17/17 +f 697/44/44 696/45/45 1253/20/20 +f 1260/17/17 697/44/44 1253/20/20 +f 1207/42/42 1108/46/46 661/43/43 +f 1207/42/42 1183/22/22 1253/20/20 +f 696/45/45 695/47/47 1183/22/22 +f 1253/20/20 696/45/45 1183/22/22 +f 1108/46/46 978/48/48 661/43/43 +f 1108/46/46 1013/24/24 1183/22/22 +f 695/47/47 694/49/49 1013/24/24 +f 1183/22/22 695/47/47 1013/24/24 +f 978/48/48 867/50/50 661/43/43 +f 978/48/48 889/26/26 1013/24/24 +f 694/49/49 693/51/51 889/26/26 +f 1013/24/24 694/49/49 889/26/26 +f 867/50/50 759/52/52 661/43/43 +f 867/50/50 773/30/30 889/26/26 +f 693/51/51 690/53/53 773/30/30 +f 889/26/26 693/51/51 773/30/30 +f 759/52/52 592/54/54 661/43/43 +f 773/30/30 592/54/54 580/28/28 +f 759/52/52 592/54/54 773/30/30 +f 690/53/53 668/55/55 580/28/28 +f 773/30/30 580/28/28 690/53/53 +f 592/54/54 486/56/56 661/43/43 +f 592/54/54 464/32/32 580/28/28 +f 580/28/28 464/32/32 668/55/55 +f 486/56/56 377/57/57 661/43/43 +f 486/56/56 341/34/34 464/32/32 +f 464/32/32 668/55/55 341/34/34 +f 377/57/57 246/58/58 661/43/43 +f 377/57/57 170/36/36 341/34/34 +f 341/34/34 668/55/55 170/36/36 +f 246/58/58 143/59/59 661/43/43 +f 246/58/58 102/38/38 170/36/36 +f 170/36/36 668/55/55 102/38/38 +f 143/59/59 118/60/60 661/43/43 +f 143/59/59 95/40/40 102/38/38 +f 102/38/38 668/55/55 95/40/40 +f 118/60/60 117/15/15 661/43/43 +f 118/60/60 95/40/40 94/61/61 +f 95/40/40 668/55/55 94/61/61 +f 117/15/15 142/62/62 661/43/43 +f 117/15/15 94/61/61 101/63/63 +f 94/61/61 668/55/55 101/63/63 +f 142/62/62 245/64/64 661/43/43 +f 142/62/62 101/63/63 169/65/65 +f 101/63/63 668/55/55 169/65/65 +f 245/64/64 376/66/66 661/43/43 +f 245/64/64 169/65/65 340/67/67 +f 169/65/65 668/55/55 340/67/67 +f 376/66/66 485/68/68 661/43/43 +f 376/66/66 340/67/67 463/69/69 +f 340/67/67 668/55/55 463/69/69 +f 485/68/68 591/70/70 661/43/43 +f 485/68/68 463/69/69 579/71/71 +f 463/69/69 668/55/55 579/71/71 +f 591/70/70 647/72/72 661/43/43 +f 591/70/70 579/71/71 646/73/73 +f 579/71/71 668/55/55 646/73/73 +f 647/72/72 758/74/74 661/43/43 +f 647/72/72 646/73/73 772/75/75 +f 668/55/55 690/53/53 772/75/75 +f 646/73/73 668/55/55 772/75/75 +f 758/74/74 866/76/76 661/43/43 +f 758/74/74 772/75/75 888/77/77 +f 690/53/53 693/51/51 888/77/77 +f 772/75/75 690/53/53 888/77/77 +f 866/76/76 977/78/78 661/43/43 +f 866/76/76 888/77/77 1012/79/79 +f 693/51/51 694/49/49 1012/79/79 +f 888/77/77 693/51/51 1012/79/79 +f 977/78/78 1107/80/80 661/43/43 +f 977/78/78 1012/79/79 1182/81/81 +f 694/49/49 695/47/47 1182/81/81 +f 1012/79/79 694/49/49 1182/81/81 +f 1107/80/80 1206/82/82 661/43/43 +f 1107/80/80 1182/81/81 1252/83/83 +f 695/47/47 696/45/45 1252/83/83 +f 1182/81/81 695/47/47 1252/83/83 +f 1206/82/82 1236/2/2 661/43/43 +f 1206/82/82 1252/83/83 1259/84/84 +f 696/45/45 697/44/44 1259/84/84 +f 1252/83/83 696/45/45 1259/84/84 +f 1236/2/2 1237/41/41 661/43/43 +f 1236/2/2 1259/84/84 1260/17/17 +f 1259/84/84 697/44/44 1260/17/17 +f 1237/41/41 1253/20/20 1207/42/42 +f 1207/42/42 1183/22/22 1108/46/46 +f 1108/46/46 1013/24/24 978/48/48 +f 978/48/48 889/26/26 867/50/50 +f 867/50/50 773/30/30 759/52/52 +f 592/54/54 486/56/56 464/32/32 +f 486/56/56 377/57/57 341/34/34 +f 377/57/57 246/58/58 170/36/36 +f 246/58/58 143/59/59 102/38/38 +f 143/59/59 118/60/60 95/40/40 +f 118/60/60 117/15/15 94/61/61 +f 117/15/15 101/63/63 142/62/62 +f 142/62/62 169/65/65 245/64/64 +f 245/64/64 340/67/67 376/66/66 +f 376/66/66 463/69/69 485/68/68 +f 485/68/68 579/71/71 591/70/70 +f 591/70/70 646/73/73 647/72/72 +f 647/72/72 772/75/75 758/74/74 +f 758/74/74 888/77/77 866/76/76 +f 866/76/76 1012/79/79 977/78/78 +f 977/78/78 1182/81/81 1107/80/80 +f 1107/80/80 1252/83/83 1206/82/82 +f 1206/82/82 1259/84/84 1236/2/2 +f 1236/2/2 1260/17/17 1237/41/41 +f 777/85/85 659/86/86 766/87/87 +f 777/85/85 824/88/88 835/89/89 +f 835/89/89 924/90/90 945/91/91 +f 945/91/91 1030/92/92 1068/93/93 +f 1068/93/93 1067/94/94 1129/95/95 +f 1129/95/95 1117/96/96 1163/97/97 +f 1163/97/97 1155/98/98 1200/99/99 +f 1200/99/99 1160/100/100 1203/101/101 +f 1203/101/101 1161/102/102 1205/103/103 +f 1205/103/103 660/104/104 1161/102/102 +f 766/87/87 659/86/86 734/105/105 +f 766/87/87 789/106/106 824/88/88 +f 824/88/88 846/107/107 924/90/90 +f 924/90/90 895/108/108 1030/92/92 +f 1030/92/92 906/109/109 1067/94/94 +f 1067/94/94 917/110/110 1117/96/96 +f 1117/96/96 931/111/111 1155/98/98 +f 1155/98/98 937/112/112 1160/100/100 +f 1160/100/100 1161/102/102 938/113/113 +f 1161/102/102 660/104/104 938/113/113 +f 734/105/105 659/86/86 701/114/114 +f 734/105/105 708/115/115 789/106/106 +f 789/106/106 731/116/116 846/107/107 +f 846/107/107 738/117/117 895/108/108 +f 895/108/108 743/118/118 906/109/109 +f 906/109/109 746/119/119 917/110/110 +f 917/110/110 752/120/120 931/111/111 +f 931/111/111 754/121/121 937/112/112 +f 937/112/112 938/113/113 756/122/122 +f 938/113/113 660/104/104 756/122/122 +f 701/114/114 659/86/86 645/123/123 +f 708/115/115 645/123/123 641/124/124 +f 701/114/114 645/123/123 708/115/115 +f 731/116/116 641/124/124 632/125/125 +f 708/115/115 641/124/124 731/116/116 +f 738/117/117 632/125/125 611/126/126 +f 731/116/116 632/125/125 738/117/117 +f 743/118/118 611/126/126 606/127/127 +f 738/117/117 611/126/126 743/118/118 +f 746/119/119 606/127/127 604/128/128 +f 743/118/118 606/127/127 746/119/119 +f 752/120/120 604/128/128 599/129/129 +f 746/119/119 604/128/128 752/120/120 +f 754/121/121 599/129/129 596/130/130 +f 752/120/120 599/129/129 754/121/121 +f 756/122/122 597/131/131 596/130/130 +f 754/121/121 756/122/122 596/130/130 +f 756/122/122 660/104/104 597/131/131 +f 645/123/123 659/86/86 630/132/132 +f 645/123/123 566/133/133 641/124/124 +f 641/124/124 507/134/134 632/125/125 +f 632/125/125 459/135/135 611/126/126 +f 611/126/126 445/136/136 606/127/127 +f 606/127/127 435/137/137 604/128/128 +f 604/128/128 420/138/138 599/129/129 +f 599/129/129 596/130/130 414/139/139 +f 596/130/130 597/131/131 415/140/140 +f 597/131/131 660/104/104 415/140/140 +f 630/132/132 659/86/86 587/141/141 +f 630/132/132 529/142/142 566/133/133 +f 566/133/133 427/143/143 507/134/134 +f 507/134/134 320/144/144 459/135/135 +f 459/135/135 288/145/145 445/136/136 +f 445/136/136 235/146/146 435/137/137 +f 435/137/137 197/147/147 420/138/138 +f 420/138/138 414/139/139 190/148/148 +f 414/139/139 415/140/140 191/149/149 +f 415/140/140 660/104/104 191/149/149 +f 587/141/141 659/86/86 578/150/150 +f 587/141/141 525/151/151 529/142/142 +f 529/142/142 408/152/152 427/143/143 +f 427/143/143 286/153/153 320/144/144 +f 320/144/144 221/154/154 288/145/145 +f 288/145/145 187/155/155 235/146/146 +f 235/146/146 154/156/156 197/147/147 +f 197/147/147 146/157/157 190/148/148 +f 190/148/148 147/158/158 191/149/149 +f 191/149/149 660/104/104 147/158/158 +f 578/150/150 659/86/86 577/159/159 +f 578/150/150 524/160/160 525/151/151 +f 525/151/151 409/161/161 408/152/152 +f 408/152/152 287/162/162 286/153/153 +f 286/153/153 222/163/163 221/154/154 +f 221/154/154 188/164/164 187/155/155 +f 187/155/155 153/165/165 154/156/156 +f 154/156/156 145/166/166 146/157/157 +f 147/158/158 145/166/166 148/16/16 +f 146/157/157 145/166/166 147/158/158 +f 147/158/158 148/16/16 660/104/104 +f 577/159/159 659/86/86 586/167/167 +f 577/159/159 530/168/168 524/160/160 +f 524/160/160 428/169/169 409/161/161 +f 409/161/161 321/170/170 287/162/162 +f 287/162/162 289/171/171 222/163/163 +f 222/163/163 236/172/172 188/164/164 +f 188/164/164 196/173/173 153/165/165 +f 153/165/165 189/174/174 145/166/166 +f 145/166/166 192/175/175 148/16/16 +f 148/16/16 192/175/175 660/104/104 +f 586/167/167 659/86/86 629/176/176 +f 586/167/167 567/177/177 530/168/168 +f 530/168/168 508/178/178 428/169/169 +f 428/169/169 460/179/179 321/170/170 +f 321/170/170 446/180/180 289/171/171 +f 289/171/171 436/181/181 236/172/172 +f 236/172/172 419/182/182 196/173/173 +f 196/173/173 413/183/183 189/174/174 +f 189/174/174 416/184/184 192/175/175 +f 192/175/175 416/184/184 660/104/104 +f 629/176/176 659/86/86 638/185/185 +f 629/176/176 601/186/186 567/177/177 +f 567/177/177 554/187/187 508/178/178 +f 508/178/178 533/188/188 460/179/179 +f 460/179/179 526/189/189 446/180/180 +f 446/180/180 517/190/190 436/181/181 +f 436/181/181 504/191/191 419/182/182 +f 419/182/182 500/192/192 413/183/183 +f 413/183/183 501/193/193 416/184/184 +f 416/184/184 501/193/193 660/104/104 +f 638/185/185 659/86/86 644/194/194 +f 638/185/185 642/195/195 601/186/186 +f 601/186/186 633/196/196 554/187/187 +f 554/187/187 612/197/197 533/188/188 +f 533/188/188 607/198/198 526/189/189 +f 526/189/189 605/199/199 517/190/190 +f 517/190/190 598/200/200 504/191/191 +f 504/191/191 594/201/201 500/192/192 +f 500/192/192 595/202/202 501/193/193 +f 501/193/193 595/202/202 660/104/104 +f 644/194/194 659/86/86 658/203/203 +f 642/195/195 658/203/203 657/204/204 +f 644/194/194 658/203/203 642/195/195 +f 633/196/196 657/204/204 656/205/205 +f 642/195/195 657/204/204 633/196/196 +f 612/197/197 656/205/205 655/206/206 +f 633/196/196 656/205/205 612/197/197 +f 607/198/198 655/206/206 654/207/207 +f 612/197/197 655/206/206 607/198/198 +f 607/198/198 653/208/208 605/199/199 +f 605/199/199 652/209/209 598/200/200 +f 594/201/201 652/209/209 649/210/210 +f 598/200/200 652/209/209 594/201/201 +f 594/201/201 651/211/211 595/202/202 +f 595/202/202 651/211/211 660/104/104 +f 658/203/203 659/86/86 700/212/212 +f 657/204/204 700/212/212 709/213/213 +f 658/203/203 700/212/212 657/204/204 +f 656/205/205 709/213/213 730/214/214 +f 657/204/204 709/213/213 656/205/205 +f 655/206/206 730/214/214 739/215/215 +f 656/205/205 730/214/214 655/206/206 +f 654/207/207 739/215/215 744/216/216 +f 655/206/206 739/215/215 654/207/207 +f 654/207/207 745/217/217 653/208/208 +f 653/208/208 751/218/218 652/209/209 +f 649/210/210 751/218/218 753/219/219 +f 652/209/209 751/218/218 649/210/210 +f 649/210/210 755/220/220 651/211/211 +f 651/211/211 755/220/220 660/104/104 +f 700/212/212 659/86/86 720/221/221 +f 700/212/212 749/222/222 709/213/213 +f 709/213/213 801/223/223 730/214/214 +f 730/214/214 823/224/224 739/215/215 +f 739/215/215 828/225/225 744/216/216 +f 744/216/216 840/226/226 745/217/217 +f 745/217/217 850/227/227 751/218/218 +f 751/218/218 852/228/228 753/219/219 +f 753/219/219 853/229/229 755/220/220 +f 755/220/220 660/104/104 853/229/229 +f 720/221/221 659/86/86 733/230/230 +f 720/221/221 790/231/231 749/222/222 +f 749/222/222 847/232/232 801/223/223 +f 801/223/223 894/233/233 823/224/224 +f 823/224/224 907/234/234 828/225/225 +f 828/225/225 916/235/235 840/226/226 +f 840/226/226 930/236/236 850/227/227 +f 850/227/227 936/237/237 852/228/228 +f 852/228/228 853/229/229 939/238/238 +f 853/229/229 660/104/104 939/238/238 +f 733/230/230 659/86/86 765/239/239 +f 733/230/230 825/240/240 790/231/231 +f 790/231/231 923/241/241 847/232/232 +f 847/232/232 1031/242/242 894/233/233 +f 894/233/233 1066/243/243 907/234/234 +f 907/234/234 1116/244/244 916/235/235 +f 916/235/235 1154/245/245 930/236/236 +f 930/236/236 1159/246/246 936/237/237 +f 936/237/237 939/238/238 1162/247/247 +f 939/238/238 660/104/104 1162/247/247 +f 765/239/239 659/86/86 776/248/248 +f 765/239/239 834/249/249 825/240/240 +f 825/240/240 946/250/250 923/241/241 +f 923/241/241 1069/251/251 1031/242/242 +f 1031/242/242 1130/252/252 1066/243/243 +f 1066/243/243 1164/253/253 1116/244/244 +f 1116/244/244 1199/254/254 1154/245/245 +f 1154/245/245 1202/255/255 1159/246/246 +f 1159/246/246 1204/1/1 1162/247/247 +f 1162/247/247 660/104/104 1204/1/1 +f 776/248/248 659/86/86 777/85/85 +f 776/248/248 835/89/89 834/249/249 +f 834/249/249 945/91/91 946/250/250 +f 946/250/250 1068/93/93 1069/251/251 +f 1069/251/251 1129/95/95 1130/252/252 +f 1130/252/252 1163/97/97 1164/253/253 +f 1164/253/253 1200/99/99 1199/254/254 +f 1199/254/254 1203/101/101 1202/255/255 +f 1204/1/1 1205/103/103 1203/101/101 +f 1202/255/255 1203/101/101 1204/1/1 +f 1204/1/1 660/104/104 1205/103/103 +f 777/85/85 766/87/87 824/88/88 +f 835/89/89 824/88/88 924/90/90 +f 945/91/91 924/90/90 1030/92/92 +f 1068/93/93 1030/92/92 1067/94/94 +f 1129/95/95 1067/94/94 1117/96/96 +f 1163/97/97 1117/96/96 1155/98/98 +f 1200/99/99 1155/98/98 1160/100/100 +f 1203/101/101 1160/100/100 1161/102/102 +f 766/87/87 734/105/105 789/106/106 +f 824/88/88 789/106/106 846/107/107 +f 924/90/90 846/107/107 895/108/108 +f 1030/92/92 895/108/108 906/109/109 +f 1067/94/94 906/109/109 917/110/110 +f 1117/96/96 917/110/110 931/111/111 +f 1155/98/98 931/111/111 937/112/112 +f 1160/100/100 937/112/112 938/113/113 +f 734/105/105 701/114/114 708/115/115 +f 789/106/106 708/115/115 731/116/116 +f 846/107/107 731/116/116 738/117/117 +f 895/108/108 738/117/117 743/118/118 +f 906/109/109 743/118/118 746/119/119 +f 917/110/110 746/119/119 752/120/120 +f 931/111/111 752/120/120 754/121/121 +f 937/112/112 754/121/121 756/122/122 +f 645/123/123 630/132/132 566/133/133 +f 641/124/124 566/133/133 507/134/134 +f 632/125/125 507/134/134 459/135/135 +f 611/126/126 459/135/135 445/136/136 +f 606/127/127 445/136/136 435/137/137 +f 604/128/128 435/137/137 420/138/138 +f 599/129/129 420/138/138 414/139/139 +f 596/130/130 415/140/140 414/139/139 +f 630/132/132 587/141/141 529/142/142 +f 566/133/133 529/142/142 427/143/143 +f 507/134/134 427/143/143 320/144/144 +f 459/135/135 320/144/144 288/145/145 +f 445/136/136 288/145/145 235/146/146 +f 435/137/137 235/146/146 197/147/147 +f 420/138/138 197/147/147 190/148/148 +f 414/139/139 191/149/149 190/148/148 +f 587/141/141 578/150/150 525/151/151 +f 529/142/142 525/151/151 408/152/152 +f 427/143/143 408/152/152 286/153/153 +f 320/144/144 286/153/153 221/154/154 +f 288/145/145 221/154/154 187/155/155 +f 235/146/146 187/155/155 154/156/156 +f 197/147/147 154/156/156 146/157/157 +f 190/148/148 146/157/157 147/158/158 +f 578/150/150 577/159/159 524/160/160 +f 525/151/151 524/160/160 409/161/161 +f 408/152/152 409/161/161 287/162/162 +f 286/153/153 287/162/162 222/163/163 +f 221/154/154 222/163/163 188/164/164 +f 187/155/155 188/164/164 153/165/165 +f 154/156/156 153/165/165 145/166/166 +f 577/159/159 586/167/167 530/168/168 +f 524/160/160 530/168/168 428/169/169 +f 409/161/161 428/169/169 321/170/170 +f 287/162/162 321/170/170 289/171/171 +f 222/163/163 289/171/171 236/172/172 +f 188/164/164 236/172/172 196/173/173 +f 153/165/165 196/173/173 189/174/174 +f 145/166/166 189/174/174 192/175/175 +f 586/167/167 629/176/176 567/177/177 +f 530/168/168 567/177/177 508/178/178 +f 428/169/169 508/178/178 460/179/179 +f 321/170/170 460/179/179 446/180/180 +f 289/171/171 446/180/180 436/181/181 +f 236/172/172 436/181/181 419/182/182 +f 196/173/173 419/182/182 413/183/183 +f 189/174/174 413/183/183 416/184/184 +f 629/176/176 638/185/185 601/186/186 +f 567/177/177 601/186/186 554/187/187 +f 508/178/178 554/187/187 533/188/188 +f 460/179/179 533/188/188 526/189/189 +f 446/180/180 526/189/189 517/190/190 +f 436/181/181 517/190/190 504/191/191 +f 419/182/182 504/191/191 500/192/192 +f 413/183/183 500/192/192 501/193/193 +f 638/185/185 644/194/194 642/195/195 +f 601/186/186 642/195/195 633/196/196 +f 554/187/187 633/196/196 612/197/197 +f 533/188/188 612/197/197 607/198/198 +f 526/189/189 607/198/198 605/199/199 +f 517/190/190 605/199/199 598/200/200 +f 504/191/191 598/200/200 594/201/201 +f 500/192/192 594/201/201 595/202/202 +f 607/198/198 654/207/207 653/208/208 +f 605/199/199 653/208/208 652/209/209 +f 594/201/201 649/210/210 651/211/211 +f 654/207/207 744/216/216 745/217/217 +f 653/208/208 745/217/217 751/218/218 +f 649/210/210 753/219/219 755/220/220 +f 700/212/212 720/221/221 749/222/222 +f 709/213/213 749/222/222 801/223/223 +f 730/214/214 801/223/223 823/224/224 +f 739/215/215 823/224/224 828/225/225 +f 744/216/216 828/225/225 840/226/226 +f 745/217/217 840/226/226 850/227/227 +f 751/218/218 850/227/227 852/228/228 +f 753/219/219 852/228/228 853/229/229 +f 720/221/221 733/230/230 790/231/231 +f 749/222/222 790/231/231 847/232/232 +f 801/223/223 847/232/232 894/233/233 +f 823/224/224 894/233/233 907/234/234 +f 828/225/225 907/234/234 916/235/235 +f 840/226/226 916/235/235 930/236/236 +f 850/227/227 930/236/236 936/237/237 +f 852/228/228 936/237/237 939/238/238 +f 733/230/230 765/239/239 825/240/240 +f 790/231/231 825/240/240 923/241/241 +f 847/232/232 923/241/241 1031/242/242 +f 894/233/233 1031/242/242 1066/243/243 +f 907/234/234 1066/243/243 1116/244/244 +f 916/235/235 1116/244/244 1154/245/245 +f 930/236/236 1154/245/245 1159/246/246 +f 936/237/237 1159/246/246 1162/247/247 +f 765/239/239 776/248/248 834/249/249 +f 825/240/240 834/249/249 946/250/250 +f 923/241/241 946/250/250 1069/251/251 +f 1031/242/242 1069/251/251 1130/252/252 +f 1066/243/243 1130/252/252 1164/253/253 +f 1116/244/244 1164/253/253 1199/254/254 +f 1154/245/245 1199/254/254 1202/255/255 +f 1159/246/246 1202/255/255 1204/1/1 +f 776/248/248 777/85/85 835/89/89 +f 834/249/249 835/89/89 945/91/91 +f 946/250/250 945/91/91 1068/93/93 +f 1069/251/251 1068/93/93 1129/95/95 +f 1130/252/252 1129/95/95 1163/97/97 +f 1164/253/253 1163/97/97 1200/99/99 +f 1199/254/254 1200/99/99 1203/101/101 +f 698/256/256 870/257/257 699/258/258 +f 699/258/258 1033/259/259 702/260/260 +f 702/260/260 1062/261/261 703/262/262 +f 703/262/262 1125/263/263 704/264/264 +f 704/264/264 1178/265/265 705/266/266 +f 705/266/266 1234/267/267 707/268/268 +f 707/268/268 1271/269/269 712/270/270 +f 712/270/270 1297/271/271 719/272/272 +f 719/272/272 1301/273/273 725/274/274 +f 725/274/274 1308/275/275 727/276/276 +f 727/276/276 1306/277/277 726/278/278 +f 726/278/278 1306/277/277 662/279/279 +f 829/280/280 1011/281/281 870/257/257 +f 870/257/257 1263/282/282 1033/259/259 +f 1033/259/259 1270/283/283 1062/261/261 +f 1062/261/261 1291/284/284 1125/263/263 +f 1125/263/263 1295/285/285 1178/265/265 +f 1178/265/265 1303/286/286 1234/267/267 +f 1234/267/267 1313/287/287 1271/269/269 +f 1271/269/269 1320/288/288 1297/271/271 +f 1297/271/271 1329/289/289 1301/273/273 +f 1301/273/273 1333/290/290 1308/275/275 +f 1308/275/275 1332/291/291 1306/277/277 +f 1306/277/277 1332/291/291 662/279/279 +f 922/292/292 1158/293/293 1011/281/281 +f 1011/281/281 1294/294/294 1263/282/282 +f 1263/282/282 1298/295/295 1270/283/283 +f 1270/283/283 1302/296/296 1291/284/284 +f 1291/284/284 1309/297/297 1295/285/285 +f 1295/285/285 1312/298/298 1303/286/286 +f 1303/286/286 1321/299/299 1313/287/287 +f 1313/287/287 1335/300/300 1320/288/288 +f 1320/288/288 1340/301/301 1329/289/289 +f 1329/289/289 1343/302/302 1333/290/290 +f 1333/290/290 1341/303/303 1332/291/291 +f 1332/291/291 1341/303/303 662/279/279 +f 1005/304/304 1158/293/293 1180/305/305 +f 1158/293/293 1294/294/294 1296/306/306 +f 1294/294/294 1298/295/295 1300/307/307 +f 1298/295/295 1302/296/296 1307/308/308 +f 1302/296/296 1309/297/297 1310/309/309 +f 1309/297/297 1312/298/298 1315/310/310 +f 1312/298/298 1321/299/299 1322/311/311 +f 1321/299/299 1335/300/300 1337/312/312 +f 1335/300/300 1340/301/301 1342/313/313 +f 1340/301/301 1343/302/302 1353/314/314 +f 1343/302/302 1341/303/303 1352/315/315 +f 1341/303/303 1352/315/315 662/279/279 +f 1180/305/305 184/316/316 342/317/317 +f 1038/318/318 1180/305/305 342/317/317 +f 1296/306/306 184/316/316 57/319/319 +f 1180/305/305 1296/306/306 184/316/316 +f 1300/307/307 57/319/319 54/320/320 +f 1296/306/306 1300/307/307 57/319/319 +f 1307/308/308 46/321/321 54/320/320 +f 1300/307/307 1307/308/308 54/320/320 +f 1310/309/309 44/322/322 46/321/321 +f 1307/308/308 1310/309/309 46/321/321 +f 1315/310/310 44/322/322 39/323/323 +f 1310/309/309 1315/310/310 44/322/322 +f 1322/311/311 32/324/324 39/323/323 +f 1315/310/310 1322/311/311 39/323/323 +f 1337/312/312 17/325/325 32/324/324 +f 1322/311/311 1337/312/312 32/324/324 +f 1337/312/312 1342/313/313 12/326/326 +f 1353/314/314 1/327/327 12/326/326 +f 1342/313/313 1353/314/314 12/326/326 +f 1/327/327 1352/315/315 2/328/328 +f 1/327/327 1353/314/314 1352/315/315 +f 1352/315/315 662/279/279 2/328/328 +f 342/317/317 184/316/316 204/329/329 +f 184/316/316 60/330/330 57/319/319 +f 57/319/319 55/331/331 54/320/320 +f 54/320/320 52/332/332 46/321/321 +f 46/321/321 45/333/333 44/322/322 +f 44/322/322 40/334/334 39/323/323 +f 39/323/323 33/335/335 32/324/324 +f 32/324/324 19/336/336 17/325/325 +f 17/325/325 14/337/337 12/326/326 +f 12/326/326 11/338/338 1/327/327 +f 1/327/327 2/328/328 13/339/339 +f 2/328/328 662/279/279 13/339/339 +f 353/340/340 349/341/341 204/329/329 +f 204/329/329 93/342/342 60/330/330 +f 60/330/330 85/343/343 55/331/331 +f 55/331/331 71/344/344 52/332/332 +f 52/332/332 59/345/345 45/333/333 +f 45/333/333 51/346/346 40/334/334 +f 40/334/334 41/347/347 33/335/335 +f 33/335/335 34/348/348 19/336/336 +f 19/336/336 27/349/349 14/337/337 +f 14/337/337 21/350/350 11/338/338 +f 11/338/338 22/351/351 13/339/339 +f 13/339/339 22/351/351 662/279/279 +f 431/352/352 492/353/353 349/341/341 +f 349/341/341 352/354/354 93/342/342 +f 93/342/342 313/355/355 85/343/343 +f 85/343/343 268/356/356 71/344/344 +f 71/344/344 202/357/357 59/345/345 +f 59/345/345 133/358/358 51/346/346 +f 51/346/346 87/359/359 41/347/347 +f 41/347/347 58/360/360 34/348/348 +f 34/348/348 53/361/361 27/349/349 +f 27/349/349 47/362/362 21/350/350 +f 21/350/350 48/363/363 22/351/351 +f 22/351/351 48/363/363 662/279/279 +f 531/364/364 699/258/258 492/353/353 +f 492/353/353 702/260/260 352/354/354 +f 352/354/354 703/262/262 313/355/355 +f 313/355/355 704/264/264 268/356/356 +f 268/356/356 705/266/266 202/357/357 +f 202/357/357 707/268/268 133/358/358 +f 133/358/358 712/270/270 87/359/359 +f 87/359/359 719/272/272 58/360/360 +f 58/360/360 725/274/274 53/361/361 +f 53/361/361 727/276/276 47/362/362 +f 47/362/362 726/278/278 48/363/363 +f 48/363/363 726/278/278 662/279/279 +f 698/256/256 829/280/280 870/257/257 +f 699/258/258 870/257/257 1033/259/259 +f 702/260/260 1033/259/259 1062/261/261 +f 703/262/262 1062/261/261 1125/263/263 +f 704/264/264 1125/263/263 1178/265/265 +f 705/266/266 1178/265/265 1234/267/267 +f 707/268/268 1234/267/267 1271/269/269 +f 712/270/270 1271/269/269 1297/271/271 +f 719/272/272 1297/271/271 1301/273/273 +f 725/274/274 1301/273/273 1308/275/275 +f 727/276/276 1308/275/275 1306/277/277 +f 829/280/280 922/292/292 1011/281/281 +f 870/257/257 1011/281/281 1263/282/282 +f 1033/259/259 1263/282/282 1270/283/283 +f 1062/261/261 1270/283/283 1291/284/284 +f 1125/263/263 1291/284/284 1295/285/285 +f 1178/265/265 1295/285/285 1303/286/286 +f 1234/267/267 1303/286/286 1313/287/287 +f 1271/269/269 1313/287/287 1320/288/288 +f 1297/271/271 1320/288/288 1329/289/289 +f 1301/273/273 1329/289/289 1333/290/290 +f 1308/275/275 1333/290/290 1332/291/291 +f 922/292/292 1158/293/293 1005/304/304 +f 1011/281/281 1294/294/294 1158/293/293 +f 1263/282/282 1298/295/295 1294/294/294 +f 1270/283/283 1302/296/296 1298/295/295 +f 1291/284/284 1309/297/297 1302/296/296 +f 1295/285/285 1312/298/298 1309/297/297 +f 1303/286/286 1321/299/299 1312/298/298 +f 1313/287/287 1335/300/300 1321/299/299 +f 1320/288/288 1340/301/301 1335/300/300 +f 1329/289/289 1343/302/302 1340/301/301 +f 1333/290/290 1343/302/302 1341/303/303 +f 1005/304/304 1180/305/305 1038/318/318 +f 1158/293/293 1296/306/306 1180/305/305 +f 1294/294/294 1300/307/307 1296/306/306 +f 1298/295/295 1307/308/308 1300/307/307 +f 1302/296/296 1310/309/309 1307/308/308 +f 1309/297/297 1315/310/310 1310/309/309 +f 1312/298/298 1322/311/311 1315/310/310 +f 1321/299/299 1337/312/312 1322/311/311 +f 1335/300/300 1342/313/313 1337/312/312 +f 1340/301/301 1353/314/314 1342/313/313 +f 1343/302/302 1352/315/315 1353/314/314 +f 1337/312/312 17/325/325 12/326/326 +f 342/317/317 353/340/340 204/329/329 +f 184/316/316 204/329/329 60/330/330 +f 57/319/319 60/330/330 55/331/331 +f 54/320/320 55/331/331 52/332/332 +f 46/321/321 52/332/332 45/333/333 +f 44/322/322 45/333/333 40/334/334 +f 39/323/323 40/334/334 33/335/335 +f 32/324/324 33/335/335 19/336/336 +f 17/325/325 19/336/336 14/337/337 +f 12/326/326 14/337/337 11/338/338 +f 1/327/327 11/338/338 13/339/339 +f 353/340/340 431/352/352 349/341/341 +f 204/329/329 349/341/341 93/342/342 +f 60/330/330 93/342/342 85/343/343 +f 55/331/331 85/343/343 71/344/344 +f 52/332/332 71/344/344 59/345/345 +f 45/333/333 59/345/345 51/346/346 +f 40/334/334 51/346/346 41/347/347 +f 33/335/335 41/347/347 34/348/348 +f 19/336/336 34/348/348 27/349/349 +f 14/337/337 27/349/349 21/350/350 +f 11/338/338 21/350/350 22/351/351 +f 431/352/352 531/364/364 492/353/353 +f 349/341/341 492/353/353 352/354/354 +f 93/342/342 352/354/354 313/355/355 +f 85/343/343 313/355/355 268/356/356 +f 71/344/344 268/356/356 202/357/357 +f 59/345/345 202/357/357 133/358/358 +f 51/346/346 133/358/358 87/359/359 +f 41/347/347 87/359/359 58/360/360 +f 34/348/348 58/360/360 53/361/361 +f 27/349/349 53/361/361 47/362/362 +f 21/350/350 47/362/362 48/363/363 +f 531/364/364 698/256/256 699/258/258 +f 492/353/353 699/258/258 702/260/260 +f 352/354/354 702/260/260 703/262/262 +f 313/355/355 703/262/262 704/264/264 +f 268/356/356 704/264/264 705/266/266 +f 202/357/357 705/266/266 707/268/268 +f 133/358/358 707/268/268 712/270/270 +f 87/359/359 712/270/270 719/272/272 +f 58/360/360 719/272/272 725/274/274 +f 53/361/361 725/274/274 727/276/276 +f 47/362/362 727/276/276 726/278/278 +f 1038/318/318 1180/305/305 1101/365/365 +f 1180/305/305 1296/306/306 1284/366/366 +f 1296/306/306 1300/307/307 1293/367/367 +f 1300/307/307 1307/308/308 1299/368/368 +f 1307/308/308 1310/309/309 1304/369/369 +f 1310/309/309 1315/310/310 1311/370/370 +f 1315/310/310 1322/311/311 1318/371/371 +f 1322/311/311 1337/312/312 1334/372/372 +f 1337/312/312 1342/313/313 1336/373/373 +f 1342/313/313 1353/314/314 1339/374/374 +f 1353/314/314 1352/315/315 1338/375/375 +f 1352/315/315 1338/375/375 662/279/279 +f 984/376/376 935/377/377 1101/365/365 +f 1101/365/365 1219/378/378 1284/366/366 +f 1284/366/366 1240/379/379 1293/367/367 +f 1293/367/367 1255/380/380 1299/368/368 +f 1299/368/368 1267/381/381 1304/369/369 +f 1304/369/369 1292/382/382 1311/370/370 +f 1311/370/370 1305/383/383 1318/371/371 +f 1318/371/371 1314/384/384 1334/372/372 +f 1334/372/372 1316/385/385 1336/373/373 +f 1336/373/373 1319/386/386 1339/374/374 +f 1339/374/374 1317/387/387 1338/375/375 +f 1338/375/375 1317/387/387 662/279/279 +f 887/388/388 799/389/389 935/377/377 +f 935/377/377 865/390/390 1219/378/378 +f 1219/378/378 874/391/391 1240/379/379 +f 1240/379/379 896/392/392 1255/380/380 +f 1255/380/380 908/393/393 1267/381/381 +f 1267/381/381 942/394/394 1292/382/382 +f 1292/382/382 1045/395/395 1305/383/383 +f 1305/383/383 1174/396/396 1314/384/384 +f 1314/384/384 1218/397/397 1316/385/385 +f 1316/385/385 1231/398/398 1319/386/386 +f 1319/386/386 1228/399/399 1317/387/387 +f 1317/387/387 1228/399/399 662/279/279 +f 778/400/400 564/401/401 799/389/389 +f 799/389/389 510/402/402 865/390/390 +f 874/391/391 510/402/402 493/403/403 +f 865/390/390 510/402/402 874/391/391 +f 896/392/392 493/403/403 483/404/404 +f 874/391/391 493/403/403 896/392/392 +f 908/393/393 483/404/404 465/405/405 +f 896/392/392 483/404/404 908/393/393 +f 942/394/394 465/405/405 441/406/406 +f 908/393/393 465/405/405 942/394/394 +f 1045/395/395 441/406/406 362/407/407 +f 942/394/394 441/406/406 1045/395/395 +f 1045/395/395 251/408/408 1174/396/396 +f 1174/396/396 193/409/409 1218/397/397 +f 1218/397/397 164/410/410 1231/398/398 +f 1228/399/399 164/410/410 171/411/411 +f 1231/398/398 164/410/410 1228/399/399 +f 1228/399/399 171/411/411 662/279/279 +f 585/412/412 429/413/413 564/401/401 +f 564/401/401 150/414/414 510/402/402 +f 510/402/402 125/415/415 493/403/403 +f 493/403/403 100/416/416 483/404/404 +f 483/404/404 92/417/417 465/405/405 +f 465/405/405 70/418/418 441/406/406 +f 441/406/406 49/419/419 362/407/407 +f 362/407/407 42/420/420 251/408/408 +f 251/408/408 38/421/421 193/409/409 +f 193/409/409 36/422/422 164/410/410 +f 164/410/410 37/423/423 171/411/411 +f 171/411/411 37/423/423 662/279/279 +f 477/424/424 264/425/425 429/413/413 +f 429/413/413 74/426/426 150/414/414 +f 150/414/414 61/427/427 125/415/415 +f 125/415/415 56/428/428 100/416/416 +f 100/416/416 50/429/429 92/417/417 +f 92/417/417 43/430/430 70/418/418 +f 70/418/418 35/431/431 49/419/419 +f 49/419/419 20/432/432 42/420/420 +f 42/420/420 18/433/433 38/421/421 +f 38/421/421 15/434/434 36/422/422 +f 36/422/422 16/435/435 37/423/423 +f 37/423/423 16/435/435 662/279/279 +f 381/436/436 184/316/316 264/425/425 +f 264/425/425 57/319/319 74/426/426 +f 74/426/426 54/320/320 61/427/427 +f 61/427/427 46/321/321 56/428/428 +f 56/428/428 44/322/322 50/429/429 +f 50/429/429 39/323/323 43/430/430 +f 43/430/430 32/324/324 35/431/431 +f 35/431/431 17/325/325 20/432/432 +f 20/432/432 12/326/326 18/433/433 +f 1/327/327 15/434/434 18/433/433 +f 15/434/434 2/328/328 16/435/435 +f 16/435/435 2/328/328 662/279/279 +f 1038/318/318 984/376/376 1101/365/365 +f 1180/305/305 1101/365/365 1284/366/366 +f 1296/306/306 1284/366/366 1293/367/367 +f 1300/307/307 1293/367/367 1299/368/368 +f 1307/308/308 1299/368/368 1304/369/369 +f 1310/309/309 1304/369/369 1311/370/370 +f 1315/310/310 1311/370/370 1318/371/371 +f 1322/311/311 1318/371/371 1334/372/372 +f 1337/312/312 1334/372/372 1336/373/373 +f 1342/313/313 1336/373/373 1339/374/374 +f 1353/314/314 1339/374/374 1338/375/375 +f 984/376/376 887/388/388 935/377/377 +f 1101/365/365 935/377/377 1219/378/378 +f 1284/366/366 1219/378/378 1240/379/379 +f 1293/367/367 1240/379/379 1255/380/380 +f 1299/368/368 1255/380/380 1267/381/381 +f 1304/369/369 1267/381/381 1292/382/382 +f 1311/370/370 1292/382/382 1305/383/383 +f 1318/371/371 1305/383/383 1314/384/384 +f 1334/372/372 1314/384/384 1316/385/385 +f 1336/373/373 1316/385/385 1319/386/386 +f 1339/374/374 1319/386/386 1317/387/387 +f 887/388/388 778/400/400 799/389/389 +f 935/377/377 799/389/389 865/390/390 +f 1219/378/378 865/390/390 874/391/391 +f 1240/379/379 874/391/391 896/392/392 +f 1255/380/380 896/392/392 908/393/393 +f 1267/381/381 908/393/393 942/394/394 +f 1292/382/382 942/394/394 1045/395/395 +f 1305/383/383 1045/395/395 1174/396/396 +f 1314/384/384 1174/396/396 1218/397/397 +f 1316/385/385 1218/397/397 1231/398/398 +f 1319/386/386 1231/398/398 1228/399/399 +f 778/400/400 585/412/412 564/401/401 +f 799/389/389 564/401/401 510/402/402 +f 1045/395/395 362/407/407 251/408/408 +f 1174/396/396 251/408/408 193/409/409 +f 1218/397/397 193/409/409 164/410/410 +f 585/412/412 477/424/424 429/413/413 +f 564/401/401 429/413/413 150/414/414 +f 510/402/402 150/414/414 125/415/415 +f 493/403/403 125/415/415 100/416/416 +f 483/404/404 100/416/416 92/417/417 +f 465/405/405 92/417/417 70/418/418 +f 441/406/406 70/418/418 49/419/419 +f 362/407/407 49/419/419 42/420/420 +f 251/408/408 42/420/420 38/421/421 +f 193/409/409 38/421/421 36/422/422 +f 164/410/410 36/422/422 37/423/423 +f 477/424/424 381/436/436 264/425/425 +f 429/413/413 264/425/425 74/426/426 +f 150/414/414 74/426/426 61/427/427 +f 125/415/415 61/427/427 56/428/428 +f 100/416/416 56/428/428 50/429/429 +f 92/417/417 50/429/429 43/430/430 +f 70/418/418 43/430/430 35/431/431 +f 49/419/419 35/431/431 20/432/432 +f 42/420/420 20/432/432 18/433/433 +f 38/421/421 18/433/433 15/434/434 +f 36/422/422 15/434/434 16/435/435 +f 381/436/436 342/317/317 184/316/316 +f 264/425/425 184/316/316 57/319/319 +f 74/426/426 57/319/319 54/320/320 +f 61/427/427 54/320/320 46/321/321 +f 56/428/428 46/321/321 44/322/322 +f 50/429/429 44/322/322 39/323/323 +f 43/430/430 39/323/323 32/324/324 +f 35/431/431 32/324/324 17/325/325 +f 20/432/432 17/325/325 12/326/326 +f 18/433/433 12/326/326 1/327/327 +f 1/327/327 2/328/328 15/434/434 +f 648/437/437 759/52/52 756/122/122 +f 650/438/438 648/437/437 756/122/122 +f 756/122/122 759/52/52 867/50/50 +f 854/439/439 867/50/50 978/48/48 +f 938/113/113 978/48/48 1108/46/46 +f 1057/440/440 1108/46/46 1207/42/42 +f 1161/102/102 1207/42/42 1237/41/41 +f 1205/103/103 1237/41/41 1236/2/2 +f 756/122/122 867/50/50 854/439/439 +f 854/439/439 978/48/48 938/113/113 +f 938/113/113 1108/46/46 1057/440/440 +f 1057/440/440 1207/42/42 1161/102/102 +f 1161/102/102 1237/41/41 1205/103/103 +f 1205/103/103 1236/2/2 1204/1/1 +f 692/441/441 597/131/131 592/54/54 +f 691/442/442 597/131/131 692/441/441 +f 597/131/131 486/56/56 592/54/54 +f 502/443/443 377/57/57 486/56/56 +f 415/140/140 246/58/58 377/57/57 +f 297/444/444 143/59/59 246/58/58 +f 191/149/149 118/60/60 143/59/59 +f 147/158/158 117/15/15 118/60/60 +f 597/131/131 502/443/443 486/56/56 +f 502/443/443 415/140/140 377/57/57 +f 415/140/140 297/444/444 246/58/58 +f 297/444/444 191/149/149 143/59/59 +f 191/149/149 147/158/158 118/60/60 +f 147/158/158 148/16/16 117/15/15 +f 148/16/16 142/62/62 117/15/15 +f 192/175/175 245/64/64 142/62/62 +f 298/445/445 376/66/66 245/64/64 +f 416/184/184 485/68/68 376/66/66 +f 501/193/193 591/70/70 485/68/68 +f 595/202/202 647/72/72 591/70/70 +f 148/16/16 192/175/175 142/62/62 +f 192/175/175 298/445/445 245/64/64 +f 298/445/445 416/184/184 376/66/66 +f 416/184/184 501/193/193 485/68/68 +f 501/193/193 595/202/202 591/70/70 +f 595/202/202 651/211/211 647/72/72 +f 651/211/211 758/74/74 647/72/72 +f 755/220/220 866/76/76 758/74/74 +f 853/229/229 977/78/78 866/76/76 +f 939/238/238 1107/80/80 977/78/78 +f 1056/446/446 1206/82/82 1107/80/80 +f 1162/247/247 1236/2/2 1206/82/82 +f 651/211/211 755/220/220 758/74/74 +f 755/220/220 853/229/229 866/76/76 +f 853/229/229 939/238/238 977/78/78 +f 939/238/238 1056/446/446 1107/80/80 +f 1056/446/446 1162/247/247 1206/82/82 +f 1162/247/247 1204/1/1 1236/2/2 +f 97/447/447 78/448/448 77/449/448 +f 98/450/449 78/448/448 97/447/447 +f 77/449/448 80/451/450 79/452/450 +f 78/448/448 80/451/450 77/449/448 +f 79/452/450 31/453/451 30/454/451 +f 80/451/450 31/453/451 79/452/450 +f 30/454/451 29/455/452 28/456/452 +f 31/453/451 29/455/452 30/454/451 +f 28/456/452 98/450/449 97/447/447 +f 29/455/452 98/450/449 28/456/452 +f 25/457/453 24/458/454 23/459/454 +f 26/460/453 24/458/454 25/457/453 +f 23/459/454 10/461/455 9/462/455 +f 24/458/454 10/461/455 23/459/454 +f 9/462/455 4/463/456 3/464/457 +f 10/461/455 4/463/456 9/462/455 +f 3/464/457 8/465/458 7/466/459 +f 4/463/456 8/465/458 3/464/457 +f 7/466/459 6/467/460 5/468/460 +f 8/465/458 6/467/460 7/466/459 +f 5/468/460 26/460/453 25/457/453 +f 6/467/460 26/460/453 5/468/460 +f 1328/469/461 1330/470/462 1331/471/462 +f 1327/472/461 1330/470/462 1328/469/461 +f 1331/471/462 1344/473/463 1345/474/463 +f 1330/470/462 1344/473/463 1331/471/462 +f 1345/474/463 1350/475/464 1351/476/464 +f 1344/473/463 1350/475/464 1345/474/463 +f 1351/476/464 1346/477/465 1347/478/465 +f 1350/475/464 1346/477/465 1351/476/464 +f 1347/478/465 1348/479/466 1349/480/466 +f 1346/477/465 1348/479/466 1347/478/465 +f 1349/480/466 1327/472/461 1328/469/461 +f 1348/479/466 1327/472/461 1349/480/466 +f 1257/481/467 1274/482/468 1275/483/468 +f 1256/484/467 1274/482/468 1257/481/467 +f 1275/483/468 1276/485/469 1277/486/469 +f 1274/482/468 1276/485/469 1275/483/468 +f 1277/486/469 1323/487/470 1324/488/470 +f 1276/485/469 1323/487/470 1277/486/469 +f 1324/488/470 1325/489/471 1326/490/471 +f 1323/487/470 1325/489/471 1324/488/470 +f 1326/490/471 1256/484/467 1257/481/467 +f 1325/489/471 1256/484/467 1326/490/471 +f 91/491/472 99/492/473 141/493/474 +f 99/492/473 108/494/475 157/495/476 +f 108/494/475 149/496/477 203/497/478 +f 149/496/477 258/498/479 292/499/480 +f 258/498/479 329/500/481 335/501/482 +f 124/502/483 141/493/474 198/503/484 +f 141/493/474 157/495/476 209/504/485 +f 157/495/476 203/497/478 261/505/486 +f 203/497/478 292/499/480 299/506/487 +f 335/501/482 338/507/488 299/506/487 +f 292/499/480 335/501/482 299/506/487 +f 182/508/489 198/503/484 412/509/490 +f 198/503/484 209/504/485 407/510/491 +f 209/504/485 261/505/486 388/511/492 +f 261/505/486 299/506/487 350/512/493 +f 299/506/487 338/507/488 337/513/494 +f 437/514/495 412/509/490 565/515/496 +f 412/509/490 407/510/491 540/516/497 +f 407/510/491 388/511/492 489/517/498 +f 350/512/493 405/518/499 489/517/498 +f 388/511/492 350/512/493 489/517/498 +f 350/512/493 337/513/494 328/519/500 +f 565/515/496 109/520/501 104/521/502 +f 643/522/503 565/515/496 104/521/502 +f 540/516/497 116/523/504 109/520/501 +f 565/515/496 540/516/497 109/520/501 +f 489/517/498 155/524/505 116/523/504 +f 540/516/497 489/517/498 116/523/504 +f 405/518/499 265/525/506 155/524/505 +f 489/517/498 405/518/499 155/524/505 +f 328/519/500 325/526/507 265/525/506 +f 405/518/499 328/519/500 265/525/506 +f 104/521/502 109/520/501 84/527/508 +f 109/520/501 116/523/504 89/528/509 +f 116/523/504 155/524/505 110/529/510 +f 155/524/505 265/525/506 227/530/511 +f 325/526/507 333/531/512 227/530/511 +f 265/525/506 325/526/507 227/530/511 +f 75/532/513 84/527/508 82/533/514 +f 84/527/508 89/528/509 86/534/515 +f 89/528/509 110/529/510 106/535/516 +f 110/529/510 227/530/511 216/536/517 +f 227/530/511 333/531/512 336/537/518 +f 66/538/519 82/533/514 83/539/520 +f 82/533/514 86/534/515 90/540/521 +f 86/534/515 106/535/516 111/541/522 +f 106/535/516 216/536/517 225/542/523 +f 216/536/517 336/537/518 334/543/524 +f 72/544/525 83/539/520 88/545/526 +f 83/539/520 90/540/521 96/546/527 +f 90/540/521 111/541/522 123/547/528 +f 111/541/522 225/542/523 240/548/529 +f 225/542/523 334/543/524 332/549/530 +f 81/550/531 88/545/526 99/492/473 +f 88/545/526 96/546/527 108/494/475 +f 96/546/527 123/547/528 149/496/477 +f 123/547/528 240/548/529 258/498/479 +f 240/548/529 332/549/530 329/500/481 +f 91/491/472 141/493/474 124/502/483 +f 99/492/473 157/495/476 141/493/474 +f 108/494/475 203/497/478 157/495/476 +f 149/496/477 292/499/480 203/497/478 +f 258/498/479 335/501/482 292/499/480 +f 124/502/483 198/503/484 182/508/489 +f 141/493/474 209/504/485 198/503/484 +f 157/495/476 261/505/486 209/504/485 +f 203/497/478 299/506/487 261/505/486 +f 182/508/489 412/509/490 437/514/495 +f 198/503/484 407/510/491 412/509/490 +f 209/504/485 388/511/492 407/510/491 +f 261/505/486 350/512/493 388/511/492 +f 299/506/487 337/513/494 350/512/493 +f 437/514/495 565/515/496 643/522/503 +f 412/509/490 540/516/497 565/515/496 +f 407/510/491 489/517/498 540/516/497 +f 350/512/493 328/519/500 405/518/499 +f 104/521/502 84/527/508 75/532/513 +f 109/520/501 89/528/509 84/527/508 +f 116/523/504 110/529/510 89/528/509 +f 155/524/505 227/530/511 110/529/510 +f 75/532/513 82/533/514 66/538/519 +f 84/527/508 86/534/515 82/533/514 +f 89/528/509 106/535/516 86/534/515 +f 110/529/510 216/536/517 106/535/516 +f 227/530/511 336/537/518 216/536/517 +f 66/538/519 83/539/520 72/544/525 +f 82/533/514 90/540/521 83/539/520 +f 86/534/515 111/541/522 90/540/521 +f 106/535/516 225/542/523 111/541/522 +f 216/536/517 334/543/524 225/542/523 +f 72/544/525 88/545/526 81/550/531 +f 83/539/520 96/546/527 88/545/526 +f 90/540/521 123/547/528 96/546/527 +f 111/541/522 240/548/529 123/547/528 +f 225/542/523 332/549/530 240/548/529 +f 81/550/531 99/492/473 91/491/472 +f 88/545/526 108/494/475 99/492/473 +f 96/546/527 149/496/477 108/494/475 +f 123/547/528 258/498/479 149/496/477 +f 240/548/529 329/500/481 258/498/479 +f 1261/551/532 1209/552/533 1254/553/534 +f 1254/553/534 1196/554/535 1246/555/536 +f 1246/555/536 1149/556/537 1201/557/538 +f 1201/557/538 1063/558/539 1094/559/540 +f 1094/559/540 1017/560/541 1023/561/542 +f 1226/562/543 1153/563/544 1209/552/533 +f 1209/552/533 1146/564/545 1196/554/535 +f 1196/554/535 1091/565/546 1149/556/537 +f 1149/556/537 1055/566/547 1063/558/539 +f 1017/560/541 1055/566/547 1014/567/548 +f 1063/558/539 1055/566/547 1017/560/541 +f 1169/568/549 941/569/550 1153/563/544 +f 1153/563/544 947/570/551 1146/564/545 +f 1146/564/545 968/571/552 1091/565/546 +f 1091/565/546 1002/572/553 1055/566/547 +f 1055/566/547 1015/573/554 1014/567/548 +f 915/574/555 791/575/556 941/569/550 +f 941/569/550 816/576/557 947/570/551 +f 947/570/551 863/577/558 968/571/552 +f 1002/572/553 863/577/558 949/578/559 +f 968/571/552 863/577/558 1002/572/553 +f 1002/572/553 1024/579/560 1015/573/554 +f 791/575/556 1249/580/561 1245/581/562 +f 706/582/563 1249/580/561 791/575/556 +f 816/576/557 1245/581/562 1239/583/564 +f 791/575/556 1245/581/562 816/576/557 +f 863/577/558 1239/583/564 1197/584/565 +f 816/576/557 1239/583/564 863/577/558 +f 949/578/559 1197/584/565 1088/585/566 +f 863/577/558 1197/584/565 949/578/559 +f 1024/579/560 1088/585/566 1027/586/567 +f 949/578/559 1088/585/566 1024/579/560 +f 1249/580/561 1268/587/568 1245/581/562 +f 1245/581/562 1264/588/569 1239/583/564 +f 1239/583/564 1244/589/570 1197/584/565 +f 1197/584/565 1124/590/571 1088/585/566 +f 1027/586/567 1124/590/571 1019/591/572 +f 1088/585/566 1124/590/571 1027/586/567 +f 1278/592/573 1272/593/574 1268/587/568 +f 1268/587/568 1266/594/575 1264/588/569 +f 1264/588/569 1248/595/576 1244/589/570 +f 1244/589/570 1137/596/577 1124/590/571 +f 1124/590/571 1016/597/578 1019/591/572 +f 1285/598/579 1269/599/580 1272/593/574 +f 1272/593/574 1262/600/581 1266/594/575 +f 1266/594/575 1243/601/582 1248/595/576 +f 1248/595/576 1126/602/583 1137/596/577 +f 1137/596/577 1018/603/584 1016/597/578 +f 1280/604/585 1265/605/586 1269/599/580 +f 1269/599/580 1258/606/587 1262/600/581 +f 1262/600/581 1227/607/588 1243/601/582 +f 1243/601/582 1114/608/589 1126/602/583 +f 1126/602/583 1020/609/590 1018/603/584 +f 1273/610/591 1254/553/534 1265/605/586 +f 1265/605/586 1246/555/536 1258/606/587 +f 1258/606/587 1201/557/538 1227/607/588 +f 1227/607/588 1094/559/540 1114/608/589 +f 1114/608/589 1023/561/542 1020/609/590 +f 1261/551/532 1226/562/543 1209/552/533 +f 1254/553/534 1209/552/533 1196/554/535 +f 1246/555/536 1196/554/535 1149/556/537 +f 1201/557/538 1149/556/537 1063/558/539 +f 1094/559/540 1063/558/539 1017/560/541 +f 1226/562/543 1169/568/549 1153/563/544 +f 1209/552/533 1153/563/544 1146/564/545 +f 1196/554/535 1146/564/545 1091/565/546 +f 1149/556/537 1091/565/546 1055/566/547 +f 1169/568/549 915/574/555 941/569/550 +f 1153/563/544 941/569/550 947/570/551 +f 1146/564/545 947/570/551 968/571/552 +f 1091/565/546 968/571/552 1002/572/553 +f 1055/566/547 1002/572/553 1015/573/554 +f 915/574/555 706/582/563 791/575/556 +f 941/569/550 791/575/556 816/576/557 +f 947/570/551 816/576/557 863/577/558 +f 1002/572/553 949/578/559 1024/579/560 +f 1249/580/561 1278/592/573 1268/587/568 +f 1245/581/562 1268/587/568 1264/588/569 +f 1239/583/564 1264/588/569 1244/589/570 +f 1197/584/565 1244/589/570 1124/590/571 +f 1278/592/573 1285/598/579 1272/593/574 +f 1268/587/568 1272/593/574 1266/594/575 +f 1264/588/569 1266/594/575 1248/595/576 +f 1244/589/570 1248/595/576 1137/596/577 +f 1124/590/571 1137/596/577 1016/597/578 +f 1285/598/579 1280/604/585 1269/599/580 +f 1272/593/574 1269/599/580 1262/600/581 +f 1266/594/575 1262/600/581 1243/601/582 +f 1248/595/576 1243/601/582 1126/602/583 +f 1137/596/577 1126/602/583 1018/603/584 +f 1280/604/585 1273/610/591 1265/605/586 +f 1269/599/580 1265/605/586 1258/606/587 +f 1262/600/581 1258/606/587 1227/607/588 +f 1243/601/582 1227/607/588 1114/608/589 +f 1126/602/583 1114/608/589 1020/609/590 +f 1273/610/591 1261/551/532 1254/553/534 +f 1265/605/586 1254/553/534 1246/555/536 +f 1258/606/587 1246/555/536 1201/557/538 +f 1227/607/588 1201/557/538 1094/559/540 +f 1114/608/589 1094/559/540 1023/561/542 +f 615/611/592 576/612/593 618/613/594 +f 774/614/595 618/613/594 775/615/594 +f 615/611/592 618/613/594 774/614/595 +f 774/614/595 775/615/594 800/616/596 +f 618/613/594 576/612/593 623/617/597 +f 775/615/594 623/617/597 757/618/598 +f 618/613/594 623/617/597 775/615/594 +f 775/615/594 757/618/598 800/616/596 +f 623/617/597 576/612/593 625/619/599 +f 757/618/598 625/619/599 722/620/600 +f 623/617/597 625/619/599 757/618/598 +f 757/618/598 722/620/600 800/616/596 +f 625/619/599 576/612/593 627/621/601 +f 722/620/600 627/621/601 717/622/602 +f 625/619/599 627/621/601 722/620/600 +f 722/620/600 717/622/602 800/616/596 +f 627/621/601 576/612/593 626/623/603 +f 717/622/602 626/623/603 715/624/604 +f 627/621/601 626/623/603 717/622/602 +f 717/622/602 715/624/604 800/616/596 +f 626/623/603 576/612/593 624/625/605 +f 715/624/604 624/625/605 713/626/606 +f 626/623/603 624/625/605 715/624/604 +f 715/624/604 713/626/606 800/616/596 +f 624/625/605 576/612/593 622/627/607 +f 713/626/606 622/627/607 714/628/608 +f 624/625/605 622/627/607 713/626/606 +f 713/626/606 714/628/608 800/616/596 +f 622/627/607 576/612/593 621/629/609 +f 714/628/608 621/629/609 716/630/610 +f 622/627/607 621/629/609 714/628/608 +f 714/628/608 716/630/610 800/616/596 +f 621/629/609 576/612/593 620/631/611 +f 716/630/610 620/631/611 718/632/612 +f 621/629/609 620/631/611 716/630/610 +f 716/630/610 718/632/612 800/616/596 +f 620/631/611 576/612/593 619/633/613 +f 718/632/612 619/633/613 721/634/614 +f 620/631/611 619/633/613 718/632/612 +f 718/632/612 721/634/614 800/616/596 +f 619/633/613 576/612/593 617/635/615 +f 721/634/614 617/635/615 764/636/616 +f 619/633/613 617/635/615 721/634/614 +f 721/634/614 764/636/616 800/616/596 +f 617/635/615 576/612/593 616/637/617 +f 764/636/616 616/637/617 767/638/618 +f 617/635/615 616/637/617 764/636/616 +f 764/636/616 767/638/618 800/616/596 +f 616/637/617 576/612/593 615/611/592 +f 767/638/618 615/611/592 774/614/595 +f 616/637/617 615/611/592 767/638/618 +f 767/638/618 774/614/595 800/616/596 +f 951/639/619 963/640/620 934/641/621 +f 973/642/622 934/641/621 893/643/623 +f 951/639/619 934/641/621 973/642/622 +f 983/644/624 893/643/623 872/645/625 +f 973/642/622 893/643/623 983/644/624 +f 986/646/626 872/645/625 876/647/627 +f 983/644/624 872/645/625 986/646/626 +f 988/648/628 876/647/627 898/649/628 +f 986/646/626 876/647/627 988/648/628 +f 934/641/621 963/640/620 920/650/629 +f 893/643/623 920/650/629 833/651/630 +f 934/641/621 920/650/629 893/643/623 +f 872/645/625 833/651/630 804/652/631 +f 893/643/623 833/651/630 872/645/625 +f 876/647/627 804/652/631 808/653/632 +f 872/645/625 804/652/631 876/647/627 +f 898/649/628 808/653/632 831/654/628 +f 876/647/627 808/653/632 898/649/628 +f 920/650/629 963/640/620 912/655/633 +f 833/651/630 912/655/633 819/656/634 +f 920/650/629 912/655/633 833/651/630 +f 804/652/631 819/656/634 783/657/635 +f 833/651/630 819/656/634 804/652/631 +f 808/653/632 783/657/635 788/658/636 +f 804/652/631 783/657/635 808/653/632 +f 831/654/628 788/658/636 811/659/628 +f 808/653/632 788/658/636 831/654/628 +f 912/655/633 963/640/620 919/660/637 +f 819/656/634 919/660/637 832/661/638 +f 912/655/633 919/660/637 819/656/634 +f 783/657/635 832/661/638 803/662/639 +f 819/656/634 832/661/638 783/657/635 +f 788/658/636 803/662/639 807/663/640 +f 783/657/635 803/662/639 788/658/636 +f 811/659/628 807/663/640 830/664/628 +f 788/658/636 807/663/640 811/659/628 +f 919/660/637 963/640/620 933/665/641 +f 832/661/638 933/665/641 892/666/642 +f 919/660/637 933/665/641 832/661/638 +f 803/662/639 892/666/642 871/667/643 +f 832/661/638 892/666/642 803/662/639 +f 807/663/640 871/667/643 875/668/644 +f 803/662/639 871/667/643 807/663/640 +f 830/664/628 875/668/644 897/669/628 +f 807/663/640 875/668/644 830/664/628 +f 933/665/641 963/640/620 950/670/645 +f 892/666/642 950/670/645 972/671/646 +f 933/665/641 950/670/645 892/666/642 +f 871/667/643 972/671/646 982/672/647 +f 892/666/642 972/671/646 871/667/643 +f 875/668/644 982/672/647 985/673/648 +f 871/667/643 982/672/647 875/668/644 +f 897/669/628 985/673/648 987/674/628 +f 875/668/644 985/673/648 897/669/628 +f 950/670/645 963/640/620 995/675/649 +f 972/671/646 995/675/649 1082/676/650 +f 950/670/645 995/675/649 972/671/646 +f 982/672/647 1082/676/650 1138/677/651 +f 972/671/646 1082/676/650 982/672/647 +f 985/673/648 1138/677/651 1140/678/652 +f 982/672/647 1138/677/651 985/673/648 +f 987/674/628 1140/678/652 1102/679/628 +f 985/673/648 1140/678/652 987/674/628 +f 995/675/649 963/640/620 1006/680/653 +f 1082/676/650 1006/680/653 1165/681/654 +f 995/675/649 1006/680/653 1082/676/650 +f 1138/677/651 1165/681/654 1222/682/655 +f 1082/676/650 1165/681/654 1138/677/651 +f 1140/678/652 1222/682/655 1220/683/656 +f 1138/677/651 1222/682/655 1140/678/652 +f 1102/679/628 1220/683/656 1192/684/657 +f 1140/678/652 1220/683/656 1102/679/628 +f 1006/680/653 963/640/620 1010/685/658 +f 1165/681/654 1010/685/658 1191/686/659 +f 1006/680/653 1010/685/658 1165/681/654 +f 1222/682/655 1191/686/659 1235/687/660 +f 1165/681/654 1191/686/659 1222/682/655 +f 1220/683/656 1235/687/660 1233/688/661 +f 1222/682/655 1235/687/660 1220/683/656 +f 1192/684/657 1233/688/661 1213/689/628 +f 1220/683/656 1233/688/661 1192/684/657 +f 1010/685/658 963/640/620 1007/690/662 +f 1191/686/659 1007/690/662 1166/691/663 +f 1010/685/658 1007/690/662 1191/686/659 +f 1235/687/660 1166/691/663 1223/692/664 +f 1191/686/659 1166/691/663 1235/687/660 +f 1233/688/661 1223/692/664 1221/693/665 +f 1235/687/660 1223/692/664 1233/688/661 +f 1213/689/628 1221/693/665 1193/694/628 +f 1233/688/661 1221/693/665 1213/689/628 +f 1007/690/662 963/640/620 996/695/666 +f 1166/691/663 996/695/666 1083/696/667 +f 1007/690/662 996/695/666 1166/691/663 +f 1223/692/664 1083/696/667 1139/697/668 +f 1166/691/663 1083/696/667 1223/692/664 +f 1221/693/665 1139/697/668 1141/698/669 +f 1223/692/664 1139/697/668 1221/693/665 +f 1193/694/628 1141/698/669 1103/699/628 +f 1221/693/665 1141/698/669 1193/694/628 +f 996/695/666 963/640/620 951/639/619 +f 1083/696/667 951/639/619 973/642/622 +f 996/695/666 951/639/619 1083/696/667 +f 1139/697/668 973/642/622 983/644/624 +f 1083/696/667 973/642/622 1139/697/668 +f 1141/698/669 983/644/624 986/646/626 +f 1139/697/668 983/644/624 1141/698/669 +f 1103/699/628 986/646/626 988/648/628 +f 1141/698/669 986/646/626 1103/699/628 +f 387/700/670 400/701/671 359/702/672 +f 375/703/673 359/702/672 271/704/674 +f 387/700/670 359/702/672 375/703/673 +f 368/705/675 271/704/674 213/706/676 +f 375/703/673 271/704/674 368/705/675 +f 364/707/677 213/706/676 206/708/678 +f 368/705/675 213/706/676 364/707/677 +f 366/709/679 206/708/678 238/710/679 +f 364/707/677 206/708/678 366/709/679 +f 359/702/672 400/701/671 346/711/680 +f 271/704/674 346/711/680 181/712/681 +f 359/702/672 346/711/680 271/704/674 +f 213/706/676 181/712/681 129/713/682 +f 271/704/674 181/712/681 213/706/676 +f 206/708/678 129/713/682 131/714/683 +f 213/706/676 129/713/682 206/708/678 +f 238/710/679 131/714/683 152/715/679 +f 206/708/678 131/714/683 238/710/679 +f 346/711/680 400/701/671 339/716/684 +f 181/712/681 339/716/684 159/717/685 +f 346/711/680 339/716/684 181/712/681 +f 129/713/682 159/717/685 112/718/686 +f 181/712/681 159/717/685 129/713/682 +f 131/714/683 112/718/686 115/719/687 +f 129/713/682 112/718/686 131/714/683 +f 152/715/679 115/719/687 132/720/679 +f 131/714/683 115/719/687 152/715/679 +f 339/716/684 400/701/671 345/721/688 +f 159/717/685 345/721/688 180/722/689 +f 339/716/684 345/721/688 159/717/685 +f 112/718/686 180/722/689 128/723/690 +f 159/717/685 180/722/689 112/718/686 +f 115/719/687 128/723/690 130/724/691 +f 112/718/686 128/723/690 115/719/687 +f 132/720/679 130/724/691 151/725/692 +f 115/719/687 130/724/691 132/720/679 +f 345/721/688 400/701/671 358/726/693 +f 180/722/689 358/726/693 270/727/694 +f 345/721/688 358/726/693 180/722/689 +f 128/723/690 270/727/694 212/728/695 +f 180/722/689 270/727/694 128/723/690 +f 130/724/691 212/728/695 205/729/696 +f 128/723/690 212/728/695 130/724/691 +f 151/725/692 205/729/696 237/730/679 +f 130/724/691 205/729/696 151/725/692 +f 358/726/693 400/701/671 386/731/697 +f 270/727/694 386/731/697 374/732/698 +f 358/726/693 386/731/697 270/727/694 +f 212/728/695 374/732/698 367/733/699 +f 270/727/694 374/732/698 212/728/695 +f 205/729/696 367/733/699 363/734/700 +f 212/728/695 367/733/699 205/729/696 +f 237/730/679 363/734/700 365/735/679 +f 205/729/696 363/734/700 237/730/679 +f 386/731/697 400/701/671 417/736/701 +f 374/732/698 417/736/701 455/737/702 +f 386/731/697 417/736/701 374/732/698 +f 367/733/699 455/737/702 475/738/703 +f 374/732/698 455/737/702 367/733/699 +f 363/734/700 475/738/703 469/739/704 +f 367/733/699 475/738/703 363/734/700 +f 365/735/679 469/739/704 452/740/705 +f 363/734/700 469/739/704 365/735/679 +f 417/736/701 400/701/671 432/741/706 +f 455/737/702 432/741/706 511/742/707 +f 417/736/701 432/741/706 455/737/702 +f 475/738/703 511/742/707 547/743/708 +f 455/737/702 511/742/707 475/738/703 +f 469/739/704 547/743/708 545/744/709 +f 475/738/703 547/743/708 469/739/704 +f 452/740/705 545/744/709 518/745/679 +f 469/739/704 545/744/709 452/740/705 +f 432/741/706 400/701/671 440/746/710 +f 511/742/707 440/746/710 532/747/711 +f 432/741/706 440/746/710 511/742/707 +f 547/743/708 532/747/711 563/748/712 +f 511/742/707 532/747/711 547/743/708 +f 545/744/709 563/748/712 560/749/713 +f 547/743/708 563/748/712 545/744/709 +f 518/745/679 560/749/713 535/750/679 +f 545/744/709 560/749/713 518/745/679 +f 440/746/710 400/701/671 433/751/714 +f 532/747/711 433/751/714 512/752/715 +f 440/746/710 433/751/714 532/747/711 +f 563/748/712 512/752/715 548/753/716 +f 532/747/711 512/752/715 563/748/712 +f 560/749/713 548/753/716 546/754/717 +f 563/748/712 548/753/716 560/749/713 +f 535/750/679 546/754/717 519/755/679 +f 560/749/713 546/754/717 535/750/679 +f 433/751/714 400/701/671 418/756/718 +f 512/752/715 418/756/718 456/757/719 +f 433/751/714 418/756/718 512/752/715 +f 548/753/716 456/757/719 476/758/720 +f 512/752/715 456/757/719 548/753/716 +f 546/754/717 476/758/720 470/759/721 +f 548/753/716 476/758/720 546/754/717 +f 519/755/679 470/759/721 453/760/679 +f 546/754/717 470/759/721 519/755/679 +f 418/756/718 400/701/671 387/700/670 +f 456/757/719 387/700/670 375/703/673 +f 418/756/718 387/700/670 456/757/719 +f 476/758/720 375/703/673 368/705/675 +f 456/757/719 375/703/673 476/758/720 +f 470/759/721 368/705/675 364/707/677 +f 476/758/720 368/705/675 470/759/721 +f 453/760/679 364/707/677 366/709/679 +f 470/759/721 364/707/677 453/760/679 +f 742/761/722 676/762/723 737/763/724 +f 851/764/725 737/763/724 839/765/726 +f 742/761/722 737/763/724 851/764/725 +f 902/766/727 839/765/726 886/767/728 +f 851/764/725 839/765/726 902/766/727 +f 901/768/729 886/767/728 884/769/730 +f 902/766/727 886/767/728 901/768/729 +f 864/770/731 884/769/730 858/771/732 +f 901/768/729 884/769/730 864/770/731 +f 737/763/724 676/762/723 729/772/733 +f 839/765/726 729/772/733 810/773/734 +f 737/763/724 729/772/733 839/765/726 +f 886/767/728 810/773/734 844/774/735 +f 839/765/726 810/773/734 886/767/728 +f 884/769/730 844/774/735 842/775/736 +f 886/767/728 844/774/735 884/769/730 +f 858/771/732 842/775/736 818/776/731 +f 884/769/730 842/775/736 858/771/732 +f 729/772/733 676/762/723 711/777/737 +f 810/773/734 711/777/737 762/778/738 +f 729/772/733 711/777/737 810/773/734 +f 844/774/735 762/778/738 787/779/739 +f 810/773/734 762/778/738 844/774/735 +f 842/775/736 787/779/739 785/780/740 +f 844/774/735 787/779/739 842/775/736 +f 818/776/731 785/780/740 771/781/731 +f 842/775/736 785/780/740 818/776/731 +f 711/777/737 676/762/723 679/782/741 +f 762/778/738 679/782/741 683/783/742 +f 711/777/737 679/782/741 762/778/738 +f 787/779/739 683/783/742 687/784/743 +f 762/778/738 683/783/742 787/779/739 +f 785/780/740 687/784/743 689/785/744 +f 787/779/739 687/784/743 785/780/740 +f 771/781/731 689/785/744 688/786/731 +f 785/780/740 689/785/744 771/781/731 +f 679/782/741 676/762/723 640/787/745 +f 683/783/742 640/787/745 590/788/746 +f 679/782/741 640/787/745 683/783/742 +f 687/784/743 590/788/746 569/789/747 +f 683/783/742 590/788/746 687/784/743 +f 689/785/744 569/789/747 571/790/748 +f 687/784/743 569/789/747 689/785/744 +f 688/786/731 571/790/748 582/791/749 +f 689/785/744 571/790/748 688/786/731 +f 640/787/745 676/762/723 635/792/750 +f 590/788/746 635/792/750 550/793/751 +f 640/787/745 635/792/750 590/788/746 +f 569/789/747 550/793/751 514/794/752 +f 590/788/746 550/793/751 569/789/747 +f 571/790/748 514/794/752 516/795/753 +f 569/789/747 514/794/752 571/790/748 +f 582/791/749 516/795/753 539/796/731 +f 571/790/748 516/795/753 582/791/749 +f 635/792/750 676/762/723 614/797/754 +f 550/793/751 614/797/754 521/798/755 +f 635/792/750 614/797/754 550/793/751 +f 514/794/752 521/798/755 468/799/756 +f 550/793/751 521/798/755 514/794/752 +f 516/795/753 468/799/756 472/800/757 +f 514/794/752 468/799/756 516/795/753 +f 539/796/731 472/800/757 497/801/731 +f 516/795/753 472/800/757 539/796/731 +f 614/797/754 676/762/723 608/802/758 +f 521/798/755 608/802/758 503/803/759 +f 614/797/754 608/802/758 521/798/755 +f 468/799/756 503/803/759 451/804/760 +f 521/798/755 503/803/759 468/799/756 +f 472/800/757 451/804/760 454/805/761 +f 468/799/756 451/804/760 472/800/757 +f 497/801/731 454/805/761 488/806/731 +f 472/800/757 454/805/761 497/801/731 +f 608/802/758 676/762/723 613/807/762 +f 503/803/759 613/807/762 520/808/763 +f 608/802/758 613/807/762 503/803/759 +f 451/804/760 520/808/763 467/809/764 +f 503/803/759 520/808/763 451/804/760 +f 454/805/761 467/809/764 471/810/765 +f 451/804/760 467/809/764 454/805/761 +f 488/806/731 471/810/765 496/811/731 +f 454/805/761 471/810/765 488/806/731 +f 613/807/762 676/762/723 634/812/766 +f 520/808/763 634/812/766 549/813/767 +f 613/807/762 634/812/766 520/808/763 +f 467/809/764 549/813/767 513/814/768 +f 520/808/763 549/813/767 467/809/764 +f 471/810/765 513/814/768 515/815/769 +f 467/809/764 513/814/768 471/810/765 +f 496/811/731 515/815/769 538/816/770 +f 471/810/765 515/815/769 496/811/731 +f 634/812/766 676/762/723 639/817/771 +f 549/813/767 639/817/771 589/818/772 +f 634/812/766 639/817/771 549/813/767 +f 513/814/768 589/818/772 568/819/773 +f 549/813/767 589/818/772 513/814/768 +f 515/815/769 568/819/773 570/820/774 +f 513/814/768 568/819/773 515/815/769 +f 538/816/770 570/820/774 581/821/749 +f 515/815/769 570/820/774 538/816/770 +f 639/817/771 676/762/723 675/822/775 +f 589/818/772 675/822/775 682/823/776 +f 639/817/771 675/822/775 589/818/772 +f 568/819/773 682/823/776 684/824/777 +f 589/818/772 682/823/776 568/819/773 +f 570/820/774 684/824/777 685/825/778 +f 568/819/773 684/824/777 570/820/774 +f 581/821/749 685/825/778 686/826/749 +f 570/820/774 685/825/778 581/821/749 +f 675/822/775 676/762/723 710/827/779 +f 682/823/776 710/827/779 761/828/780 +f 675/822/775 710/827/779 682/823/776 +f 684/824/777 761/828/780 786/829/781 +f 682/823/776 761/828/780 684/824/777 +f 685/825/778 786/829/781 784/830/782 +f 684/824/777 786/829/781 685/825/778 +f 686/826/749 784/830/782 770/831/749 +f 685/825/778 784/830/782 686/826/749 +f 710/827/779 676/762/723 728/832/783 +f 761/828/780 728/832/783 809/833/784 +f 710/827/779 728/832/783 761/828/780 +f 786/829/781 809/833/784 843/834/785 +f 761/828/780 809/833/784 786/829/781 +f 784/830/782 843/834/785 841/835/786 +f 786/829/781 843/834/785 784/830/782 +f 770/831/749 841/835/786 817/836/749 +f 784/830/782 841/835/786 770/831/749 +f 728/832/783 676/762/723 736/837/787 +f 809/833/784 736/837/787 838/838/788 +f 728/832/783 736/837/787 809/833/784 +f 843/834/785 838/838/788 885/839/789 +f 809/833/784 838/838/788 843/834/785 +f 841/835/786 885/839/789 883/840/790 +f 843/834/785 885/839/789 841/835/786 +f 817/836/749 883/840/790 857/841/731 +f 841/835/786 883/840/790 817/836/749 +f 736/837/787 676/762/723 742/761/722 +f 838/838/788 742/761/722 851/764/725 +f 736/837/787 742/761/722 838/838/788 +f 885/839/789 851/764/725 902/766/727 +f 838/838/788 851/764/725 885/839/789 +f 883/840/790 902/766/727 901/768/729 +f 885/839/789 902/766/727 883/840/790 +f 857/841/731 901/768/729 864/770/731 +f 883/840/790 901/768/729 857/841/731 +f 750/842/791 680/843/792 740/844/793 +f 802/845/794 740/844/793 792/846/795 +f 750/842/791 740/844/793 802/845/794 +f 845/847/796 792/846/795 826/848/797 +f 802/845/794 792/846/795 845/847/796 +f 873/849/798 826/848/797 855/850/799 +f 845/847/796 826/848/797 873/849/798 +f 905/851/800 855/850/799 879/852/801 +f 873/849/798 855/850/799 905/851/800 +f 918/853/802 879/852/801 899/854/803 +f 905/851/800 879/852/801 918/853/802 +f 921/855/804 899/854/803 903/856/805 +f 918/853/802 899/854/803 921/855/804 +f 740/844/793 680/843/792 723/857/806 +f 792/846/795 723/857/806 747/858/807 +f 740/844/793 723/857/806 792/846/795 +f 826/848/797 747/858/807 781/859/808 +f 792/846/795 747/858/807 826/848/797 +f 855/850/799 781/859/808 796/860/809 +f 826/848/797 781/859/808 855/850/799 +f 879/852/801 796/860/809 805/861/810 +f 855/850/799 796/860/809 879/852/801 +f 899/854/803 805/861/810 812/862/811 +f 879/852/801 805/861/810 899/854/803 +f 903/856/805 812/862/811 820/863/812 +f 899/854/803 812/862/811 903/856/805 +f 723/857/806 680/843/792 677/864/813 +f 747/858/807 677/864/813 674/865/814 +f 723/857/806 677/864/813 747/858/807 +f 781/859/808 674/865/814 670/866/815 +f 747/858/807 674/865/814 781/859/808 +f 796/860/809 670/866/815 667/867/816 +f 781/859/808 670/866/815 796/860/809 +f 805/861/810 667/867/816 666/868/817 +f 796/860/809 667/867/816 805/861/810 +f 812/862/811 666/868/817 664/869/818 +f 805/861/810 666/868/817 812/862/811 +f 820/863/812 664/869/818 663/870/819 +f 812/862/811 664/869/818 820/863/812 +f 677/864/813 680/843/792 636/871/820 +f 674/865/814 636/871/820 602/872/821 +f 677/864/813 636/871/820 674/865/814 +f 670/866/815 602/872/821 572/873/822 +f 674/865/814 602/872/821 670/866/815 +f 667/867/816 572/873/822 556/874/823 +f 670/866/815 572/873/822 667/867/816 +f 666/868/817 556/874/823 551/875/824 +f 667/867/816 556/874/823 666/868/817 +f 664/869/818 551/875/824 543/876/825 +f 666/868/817 551/875/824 664/869/818 +f 663/870/819 543/876/825 536/877/826 +f 664/869/818 543/876/825 663/870/819 +f 636/871/820 680/843/792 609/878/827 +f 602/872/821 609/878/827 561/879/828 +f 636/871/820 609/878/827 602/872/821 +f 572/873/822 561/879/828 527/880/829 +f 602/872/821 561/879/828 572/873/822 +f 556/874/823 527/880/829 498/881/830 +f 572/873/822 527/880/829 556/874/823 +f 551/875/824 498/881/830 478/882/831 +f 556/874/823 498/881/830 551/875/824 +f 543/876/825 478/882/831 457/883/832 +f 551/875/824 478/882/831 543/876/825 +f 536/877/826 457/883/832 449/884/833 +f 543/876/825 457/883/832 536/877/826 +f 609/878/827 680/843/792 600/885/834 +f 561/879/828 600/885/834 553/886/835 +f 609/878/827 600/885/834 561/879/828 +f 527/880/829 553/886/835 509/887/836 +f 561/879/828 553/886/835 527/880/829 +f 498/881/830 509/887/836 482/888/837 +f 527/880/829 509/887/836 498/881/830 +f 478/882/831 482/888/837 447/889/838 +f 498/881/830 482/888/837 478/882/831 +f 457/883/832 447/889/838 434/890/839 +f 478/882/831 447/889/838 457/883/832 +f 449/884/833 434/890/839 430/891/840 +f 457/883/832 434/890/839 449/884/833 +f 600/885/834 680/843/792 610/892/841 +f 553/886/835 610/892/841 562/893/842 +f 600/885/834 610/892/841 553/886/835 +f 509/887/836 562/893/842 528/894/843 +f 553/886/835 562/893/842 509/887/836 +f 482/888/837 528/894/843 499/895/844 +f 509/887/836 528/894/843 482/888/837 +f 447/889/838 499/895/844 479/896/845 +f 482/888/837 499/895/844 447/889/838 +f 434/890/839 479/896/845 458/897/846 +f 447/889/838 479/896/845 434/890/839 +f 430/891/840 458/897/846 450/898/847 +f 434/890/839 458/897/846 430/891/840 +f 610/892/841 680/843/792 637/899/848 +f 562/893/842 637/899/848 603/900/849 +f 610/892/841 637/899/848 562/893/842 +f 528/894/843 603/900/849 573/901/850 +f 562/893/842 603/900/849 528/894/843 +f 499/895/844 573/901/850 557/902/851 +f 528/894/843 573/901/850 499/895/844 +f 479/896/845 557/902/851 552/903/852 +f 499/895/844 557/902/851 479/896/845 +f 458/897/846 552/903/852 544/904/853 +f 479/896/845 552/903/852 458/897/846 +f 450/898/847 544/904/853 537/905/854 +f 458/897/846 544/904/853 450/898/847 +f 637/899/848 680/843/792 681/906/855 +f 603/900/849 681/906/855 678/907/856 +f 637/899/848 681/906/855 603/900/849 +f 573/901/850 678/907/856 672/908/857 +f 603/900/849 678/907/856 573/901/850 +f 557/902/851 672/908/857 673/909/858 +f 573/901/850 672/908/857 557/902/851 +f 552/903/852 673/909/858 671/910/859 +f 557/902/851 673/909/858 552/903/852 +f 544/904/853 671/910/859 669/911/860 +f 552/903/852 671/910/859 544/904/853 +f 537/905/854 669/911/860 665/912/861 +f 544/904/853 669/911/860 537/905/854 +f 681/906/855 680/843/792 724/913/862 +f 678/907/856 724/913/862 748/914/863 +f 681/906/855 724/913/862 678/907/856 +f 672/908/857 748/914/863 782/915/864 +f 678/907/856 748/914/863 672/908/857 +f 673/909/858 782/915/864 797/916/865 +f 672/908/857 782/915/864 673/909/858 +f 671/910/859 797/916/865 806/917/866 +f 673/909/858 797/916/865 671/910/859 +f 669/911/860 806/917/866 813/918/867 +f 671/910/859 806/917/866 669/911/860 +f 665/912/861 813/918/867 821/919/868 +f 669/911/860 813/918/867 665/912/861 +f 724/913/862 680/843/792 741/920/869 +f 748/914/863 741/920/869 793/921/870 +f 724/913/862 741/920/869 748/914/863 +f 782/915/864 793/921/870 827/922/871 +f 748/914/863 793/921/870 782/915/864 +f 797/916/865 827/922/871 856/923/872 +f 782/915/864 827/922/871 797/916/865 +f 806/917/866 856/923/872 880/924/873 +f 797/916/865 856/923/872 806/917/866 +f 813/918/867 880/924/873 900/925/874 +f 806/917/866 880/924/873 813/918/867 +f 821/919/868 900/925/874 904/926/875 +f 813/918/867 900/925/874 821/919/868 +f 741/920/869 680/843/792 750/842/791 +f 793/921/870 750/842/791 802/845/794 +f 741/920/869 750/842/791 793/921/870 +f 827/922/871 802/845/794 845/847/796 +f 793/921/870 802/845/794 827/922/871 +f 856/923/872 845/847/796 873/849/798 +f 827/922/871 845/847/796 856/923/872 +f 880/924/873 873/849/798 905/851/800 +f 856/923/872 873/849/798 880/924/873 +f 900/925/874 905/851/800 918/853/802 +f 880/924/873 905/851/800 900/925/874 +f 904/926/875 918/853/802 921/855/804 +f 900/925/874 918/853/802 904/926/875 +f 444/927/876 403/928/877 438/929/878 +f 484/930/879 438/929/878 473/931/880 +f 444/927/876 438/929/878 484/930/879 +f 534/932/881 473/931/880 522/933/882 +f 484/930/879 473/931/880 534/932/881 +f 555/934/883 522/933/882 541/935/884 +f 534/932/881 522/933/882 555/934/883 +f 588/936/885 541/935/884 558/937/886 +f 555/934/883 541/935/884 588/936/885 +f 628/938/887 558/937/886 574/939/888 +f 588/936/885 558/937/886 628/938/887 +f 631/940/889 574/939/888 583/941/890 +f 628/938/887 574/939/888 631/940/889 +f 438/929/878 403/928/877 423/942/891 +f 473/931/880 423/942/891 443/943/892 +f 438/929/878 423/942/891 473/931/880 +f 522/933/882 443/943/892 461/944/893 +f 473/931/880 443/943/892 522/933/882 +f 541/935/884 461/944/893 480/945/894 +f 522/933/882 461/944/893 541/935/884 +f 558/937/886 480/945/894 490/946/895 +f 541/935/884 480/945/894 558/937/886 +f 574/939/888 490/946/895 494/947/896 +f 558/937/886 490/946/895 574/939/888 +f 583/941/890 494/947/896 505/948/897 +f 574/939/888 494/947/896 583/941/890 +f 423/942/891 403/928/877 401/949/898 +f 443/943/892 401/949/898 397/950/899 +f 423/942/891 401/949/898 443/943/892 +f 461/944/893 397/950/899 394/951/900 +f 443/943/892 397/950/899 461/944/893 +f 480/945/894 394/951/900 393/952/901 +f 461/944/893 394/951/900 480/945/894 +f 490/946/895 393/952/901 392/953/902 +f 480/945/894 393/952/901 490/946/895 +f 494/947/896 392/953/902 390/954/903 +f 490/946/895 392/953/902 494/947/896 +f 505/948/897 390/954/903 389/955/904 +f 494/947/896 390/954/903 505/948/897 +f 401/949/898 403/928/877 356/956/905 +f 397/950/899 356/956/905 322/957/906 +f 401/949/898 356/956/905 397/950/899 +f 394/951/900 322/957/906 290/958/907 +f 397/950/899 322/957/906 394/951/900 +f 393/952/901 290/958/907 259/959/908 +f 394/951/900 290/958/907 393/952/901 +f 392/953/902 259/959/908 229/960/909 +f 393/952/901 259/959/908 392/953/902 +f 390/954/903 229/960/909 214/961/910 +f 392/953/902 229/960/909 390/954/903 +f 389/955/904 214/961/910 210/962/911 +f 390/954/903 214/961/910 389/955/904 +f 356/956/905 403/928/877 343/963/912 +f 322/957/906 343/963/912 266/964/913 +f 356/956/905 343/963/912 322/957/906 +f 290/958/907 266/964/913 194/965/914 +f 322/957/906 266/964/913 290/958/907 +f 259/959/908 194/965/914 161/966/915 +f 290/958/907 194/965/914 259/959/908 +f 229/960/909 161/966/915 134/967/916 +f 259/959/908 161/966/915 229/960/909 +f 214/961/910 134/967/916 126/968/917 +f 229/960/909 134/967/916 214/961/910 +f 210/962/911 126/968/917 121/969/918 +f 214/961/910 126/968/917 210/962/911 +f 343/963/912 403/928/877 318/970/919 +f 266/964/913 318/970/919 250/971/920 +f 343/963/912 318/970/919 266/964/913 +f 194/965/914 250/971/920 173/972/921 +f 266/964/913 250/971/920 194/965/914 +f 161/966/915 173/972/921 140/973/922 +f 194/965/914 173/972/921 161/966/915 +f 134/967/916 140/973/922 120/974/923 +f 161/966/915 140/973/922 134/967/916 +f 126/968/917 120/974/923 107/975/924 +f 134/967/916 120/974/923 126/968/917 +f 121/969/918 107/975/924 103/976/925 +f 126/968/917 107/975/924 121/969/918 +f 318/970/919 403/928/877 344/977/926 +f 250/971/920 344/977/926 267/978/927 +f 318/970/919 344/977/926 250/971/920 +f 173/972/921 267/978/927 195/979/928 +f 250/971/920 267/978/927 173/972/921 +f 140/973/922 195/979/928 162/980/929 +f 173/972/921 195/979/928 140/973/922 +f 120/974/923 162/980/929 135/981/930 +f 140/973/922 162/980/929 120/974/923 +f 107/975/924 135/981/930 127/982/931 +f 120/974/923 135/981/930 107/975/924 +f 103/976/925 127/982/931 122/983/932 +f 107/975/924 127/982/931 103/976/925 +f 344/977/926 403/928/877 357/984/933 +f 267/978/927 357/984/933 323/985/934 +f 344/977/926 357/984/933 267/978/927 +f 195/979/928 323/985/934 291/986/935 +f 267/978/927 323/985/934 195/979/928 +f 162/980/929 291/986/935 260/987/936 +f 195/979/928 291/986/935 162/980/929 +f 135/981/930 260/987/936 230/988/937 +f 162/980/929 260/987/936 135/981/930 +f 127/982/931 230/988/937 215/989/938 +f 135/981/930 230/988/937 127/982/931 +f 122/983/932 215/989/938 211/990/939 +f 127/982/931 215/989/938 122/983/932 +f 357/984/933 403/928/877 404/991/940 +f 323/985/934 404/991/940 402/992/941 +f 357/984/933 404/991/940 323/985/934 +f 291/986/935 402/992/941 398/993/942 +f 323/985/934 402/992/941 291/986/935 +f 260/987/936 398/993/942 399/994/943 +f 291/986/935 398/993/942 260/987/936 +f 230/988/937 399/994/943 396/995/944 +f 260/987/936 399/994/943 230/988/937 +f 215/989/938 396/995/944 395/996/945 +f 230/988/937 396/995/944 215/989/938 +f 211/990/939 395/996/945 391/997/946 +f 215/989/938 395/996/945 211/990/939 +f 404/991/940 403/928/877 424/998/947 +f 402/992/941 424/998/947 442/999/948 +f 404/991/940 424/998/947 402/992/941 +f 398/993/942 442/999/948 462/1000/949 +f 402/992/941 442/999/948 398/993/942 +f 399/994/943 462/1000/949 481/1001/950 +f 398/993/942 462/1000/949 399/994/943 +f 396/995/944 481/1001/950 491/1002/951 +f 399/994/943 481/1001/950 396/995/944 +f 395/996/945 491/1002/951 495/1003/952 +f 396/995/944 491/1002/951 395/996/945 +f 391/997/946 495/1003/952 506/1004/953 +f 395/996/945 495/1003/952 391/997/946 +f 424/998/947 403/928/877 439/1005/954 +f 442/999/948 439/1005/954 474/1006/955 +f 424/998/947 439/1005/954 442/999/948 +f 462/1000/949 474/1006/955 523/1007/956 +f 442/999/948 474/1006/955 462/1000/949 +f 481/1001/950 523/1007/956 542/1008/957 +f 462/1000/949 523/1007/956 481/1001/950 +f 491/1002/951 542/1008/957 559/1009/958 +f 481/1001/950 542/1008/957 491/1002/951 +f 495/1003/952 559/1009/958 575/1010/959 +f 491/1002/951 559/1009/958 495/1003/952 +f 506/1004/953 575/1010/959 584/1011/960 +f 495/1003/952 575/1010/959 506/1004/953 +f 439/1005/954 403/928/877 444/927/876 +f 474/1006/955 444/927/876 484/930/879 +f 439/1005/954 444/927/876 474/1006/955 +f 523/1007/956 484/930/879 534/932/881 +f 474/1006/955 484/930/879 523/1007/956 +f 542/1008/957 534/932/881 555/934/883 +f 523/1007/956 534/932/881 542/1008/957 +f 559/1009/958 555/934/883 588/936/885 +f 542/1008/957 555/934/883 559/1009/958 +f 575/1010/959 588/936/885 628/938/887 +f 559/1009/958 588/936/885 575/1010/959 +f 584/1011/960 628/938/887 631/940/889 +f 575/1010/959 628/938/887 584/1011/960 +f 1035/1012/961 966/1013/877 1008/1014/878 +f 1104/1015/879 1008/1014/878 1086/1016/880 +f 1035/1012/961 1008/1014/878 1104/1015/879 +f 1179/1017/962 1086/1016/880 1156/1018/882 +f 1104/1015/879 1086/1016/880 1179/1017/962 +f 1210/1019/883 1156/1018/882 1188/1020/884 +f 1179/1017/962 1156/1018/882 1210/1019/883 +f 1232/1021/885 1188/1020/884 1215/1022/886 +f 1210/1019/883 1188/1020/884 1232/1021/885 +f 1247/1023/887 1215/1022/886 1225/1024/888 +f 1232/1021/885 1215/1022/886 1247/1023/887 +f 1251/1025/889 1225/1024/888 1229/1026/963 +f 1247/1023/887 1225/1024/888 1251/1025/889 +f 1008/1014/878 966/1013/877 997/1027/964 +f 1086/1016/880 997/1027/964 1029/1028/892 +f 1008/1014/878 997/1027/964 1086/1016/880 +f 1156/1018/882 1029/1028/892 1064/1029/965 +f 1086/1016/880 1029/1028/892 1156/1018/882 +f 1188/1020/884 1064/1029/965 1092/1030/894 +f 1156/1018/882 1064/1029/965 1188/1020/884 +f 1215/1022/886 1092/1030/894 1122/1031/895 +f 1188/1020/884 1092/1030/894 1215/1022/886 +f 1225/1024/888 1122/1031/895 1142/1032/896 +f 1215/1022/886 1122/1031/895 1225/1024/888 +f 1229/1026/963 1142/1032/896 1144/1033/897 +f 1225/1024/888 1142/1032/896 1229/1026/963 +f 997/1027/964 966/1013/877 964/1034/966 +f 1029/1028/892 964/1034/966 960/1035/899 +f 997/1027/964 964/1034/966 1029/1028/892 +f 1064/1029/965 960/1035/899 957/1036/900 +f 1029/1028/892 960/1035/899 1064/1029/965 +f 1092/1030/894 957/1036/900 956/1037/901 +f 1064/1029/965 957/1036/900 1092/1030/894 +f 1122/1031/895 956/1037/901 955/1038/902 +f 1092/1030/894 956/1037/901 1122/1031/895 +f 1142/1032/896 955/1038/902 953/1039/903 +f 1122/1031/895 955/1038/902 1142/1032/896 +f 1144/1033/897 953/1039/903 952/1040/967 +f 1142/1032/896 953/1039/903 1144/1033/897 +f 964/1034/966 966/1013/877 926/1041/905 +f 960/1035/899 926/1041/905 910/1042/906 +f 964/1034/966 926/1041/905 960/1035/899 +f 957/1036/900 910/1042/906 890/1043/968 +f 960/1035/899 910/1042/906 957/1036/900 +f 956/1037/901 890/1043/968 877/1044/908 +f 957/1036/900 890/1043/968 956/1037/901 +f 955/1038/902 877/1044/908 861/1045/909 +f 956/1037/901 877/1044/908 955/1038/902 +f 953/1039/903 861/1045/909 859/1046/910 +f 955/1038/902 861/1045/909 953/1039/903 +f 952/1040/967 859/1046/910 848/1047/911 +f 953/1039/903 859/1046/910 952/1040/967 +f 926/1041/905 966/1013/877 913/1048/912 +f 910/1042/906 913/1048/912 881/1049/969 +f 926/1041/905 913/1048/912 910/1042/906 +f 890/1043/968 881/1049/969 836/1050/970 +f 910/1042/906 881/1049/969 890/1043/968 +f 877/1044/908 836/1050/970 814/1051/915 +f 890/1043/968 836/1050/970 877/1044/908 +f 861/1045/909 814/1051/915 794/1052/916 +f 877/1044/908 814/1051/915 861/1045/909 +f 859/1046/910 794/1052/916 779/1053/971 +f 861/1045/909 794/1052/916 859/1046/910 +f 848/1047/911 779/1053/971 768/1054/918 +f 859/1046/910 779/1053/971 848/1047/911 +f 913/1048/912 966/1013/877 909/1055/919 +f 881/1049/969 909/1055/919 869/1056/920 +f 913/1048/912 909/1055/919 881/1049/969 +f 836/1050/970 869/1056/920 822/1057/921 +f 881/1049/969 869/1056/920 836/1050/970 +f 814/1051/915 822/1057/921 798/1058/922 +f 836/1050/970 822/1057/921 814/1051/915 +f 794/1052/916 798/1058/922 763/1059/972 +f 814/1051/915 798/1058/922 794/1052/916 +f 779/1053/971 763/1059/972 735/1060/924 +f 794/1052/916 763/1059/972 779/1053/971 +f 768/1054/918 735/1060/924 732/1061/925 +f 779/1053/971 735/1060/924 768/1054/918 +f 909/1055/919 966/1013/877 914/1062/973 +f 869/1056/920 914/1062/973 882/1063/927 +f 909/1055/919 914/1062/973 869/1056/920 +f 822/1057/921 882/1063/927 837/1064/928 +f 869/1056/920 882/1063/927 822/1057/921 +f 798/1058/922 837/1064/928 815/1065/929 +f 822/1057/921 837/1064/928 798/1058/922 +f 763/1059/972 815/1065/929 795/1066/930 +f 798/1058/922 815/1065/929 763/1059/972 +f 735/1060/924 795/1066/930 780/1067/931 +f 763/1059/972 795/1066/930 735/1060/924 +f 732/1061/925 780/1067/931 769/1068/932 +f 735/1060/924 780/1067/931 732/1061/925 +f 914/1062/973 966/1013/877 927/1069/933 +f 882/1063/927 927/1069/933 911/1070/934 +f 914/1062/973 927/1069/933 882/1063/927 +f 837/1064/928 911/1070/934 891/1071/974 +f 882/1063/927 911/1070/934 837/1064/928 +f 815/1065/929 891/1071/974 878/1072/936 +f 837/1064/928 891/1071/974 815/1065/929 +f 795/1066/930 878/1072/936 862/1073/937 +f 815/1065/929 878/1072/936 795/1066/930 +f 780/1067/931 862/1073/937 860/1074/975 +f 795/1066/930 862/1073/937 780/1067/931 +f 769/1068/932 860/1074/975 849/1075/939 +f 780/1067/931 860/1074/975 769/1068/932 +f 927/1069/933 966/1013/877 967/1076/976 +f 911/1070/934 967/1076/976 965/1077/977 +f 927/1069/933 967/1076/976 911/1070/934 +f 891/1071/974 965/1077/977 961/1078/942 +f 911/1070/934 965/1077/977 891/1071/974 +f 878/1072/936 961/1078/942 962/1079/943 +f 891/1071/974 961/1078/942 878/1072/936 +f 862/1073/937 962/1079/943 959/1080/944 +f 878/1072/936 962/1079/943 862/1073/937 +f 860/1074/975 959/1080/944 958/1081/945 +f 862/1073/937 959/1080/944 860/1074/975 +f 849/1075/939 958/1081/945 954/1082/946 +f 860/1074/975 958/1081/945 849/1075/939 +f 967/1076/976 966/1013/877 998/1083/978 +f 965/1077/977 998/1083/978 1028/1084/948 +f 967/1076/976 998/1083/978 965/1077/977 +f 961/1078/942 1028/1084/948 1065/1085/949 +f 965/1077/977 1028/1084/948 961/1078/942 +f 962/1079/943 1065/1085/949 1093/1086/979 +f 961/1078/942 1065/1085/949 962/1079/943 +f 959/1080/944 1093/1086/979 1123/1087/951 +f 962/1079/943 1093/1086/979 959/1080/944 +f 958/1081/945 1123/1087/951 1143/1088/952 +f 959/1080/944 1123/1087/951 958/1081/945 +f 954/1082/946 1143/1088/952 1145/1089/953 +f 958/1081/945 1143/1088/952 954/1082/946 +f 998/1083/978 966/1013/877 1009/1090/954 +f 1028/1084/948 1009/1090/954 1087/1091/955 +f 998/1083/978 1009/1090/954 1028/1084/948 +f 1065/1085/949 1087/1091/955 1157/1092/980 +f 1028/1084/948 1087/1091/955 1065/1085/949 +f 1093/1086/979 1157/1092/980 1189/1093/957 +f 1065/1085/949 1157/1092/980 1093/1086/979 +f 1123/1087/951 1189/1093/957 1216/1094/958 +f 1093/1086/979 1189/1093/957 1123/1087/951 +f 1143/1088/952 1216/1094/958 1224/1095/959 +f 1123/1087/951 1216/1094/958 1143/1088/952 +f 1145/1089/953 1224/1095/959 1230/1096/981 +f 1143/1088/952 1224/1095/959 1145/1089/953 +f 1009/1090/954 966/1013/877 1035/1012/961 +f 1087/1091/955 1035/1012/961 1104/1015/879 +f 1009/1090/954 1035/1012/961 1087/1091/955 +f 1157/1092/980 1104/1015/879 1179/1017/962 +f 1087/1091/955 1104/1015/879 1157/1092/980 +f 1189/1093/957 1179/1017/962 1210/1019/883 +f 1157/1092/980 1179/1017/962 1189/1093/957 +f 1216/1094/958 1210/1019/883 1232/1021/885 +f 1189/1093/957 1210/1019/883 1216/1094/958 +f 1224/1095/959 1232/1021/885 1247/1023/887 +f 1216/1094/958 1232/1021/885 1224/1095/959 +f 1230/1096/981 1247/1023/887 1251/1025/889 +f 1224/1095/959 1247/1023/887 1230/1096/981 +f 1096/1097/982 1079/1098/983 1090/1099/984 +f 1136/1100/985 1090/1099/984 1100/1101/986 +f 1096/1097/982 1090/1099/984 1136/1100/985 +f 1152/1102/987 1100/1101/986 1119/1103/988 +f 1136/1100/985 1100/1101/986 1152/1102/987 +f 1168/1104/989 1119/1103/988 1134/1105/990 +f 1152/1102/987 1119/1103/988 1168/1104/989 +f 1090/1099/984 1079/1098/983 1078/1106/991 +f 1100/1101/986 1078/1106/991 1076/1107/992 +f 1090/1099/984 1078/1106/991 1100/1101/986 +f 1119/1103/988 1076/1107/992 1073/1108/993 +f 1100/1101/986 1076/1107/992 1119/1103/988 +f 1134/1105/990 1073/1108/993 1071/1109/994 +f 1119/1103/988 1073/1108/993 1134/1105/990 +f 1078/1106/991 1079/1098/983 1059/1110/995 +f 1076/1107/992 1059/1110/995 1050/1111/996 +f 1078/1106/991 1059/1110/995 1076/1107/992 +f 1073/1108/993 1050/1111/996 1037/1112/997 +f 1076/1107/992 1050/1111/996 1073/1108/993 +f 1071/1109/994 1037/1112/997 1026/1113/998 +f 1073/1108/993 1037/1112/997 1071/1109/994 +f 1059/1110/995 1079/1098/983 1054/1114/999 +f 1050/1111/996 1054/1114/999 1022/1115/1000 +f 1059/1110/995 1054/1114/999 1050/1111/996 +f 1037/1112/997 1022/1115/1000 1000/1116/1001 +f 1050/1111/996 1022/1115/1000 1037/1112/997 +f 1026/1113/998 1000/1116/1001 994/1117/1002 +f 1037/1112/997 1000/1116/1001 1026/1113/998 +f 1054/1114/999 1079/1098/983 1048/1118/1003 +f 1022/1115/1000 1048/1118/1003 1004/1119/1004 +f 1054/1114/999 1048/1118/1003 1022/1115/1000 +f 1000/1116/1001 1004/1119/1004 990/1120/1005 +f 1022/1115/1000 1004/1119/1004 1000/1116/1001 +f 994/1117/1002 990/1120/1005 971/1121/1006 +f 1000/1116/1001 990/1120/1005 994/1117/1002 +f 1048/1118/1003 1079/1098/983 1042/1122/1007 +f 1004/1119/1004 1042/1122/1007 1001/1123/1008 +f 1048/1118/1003 1042/1122/1007 1004/1119/1004 +f 990/1120/1005 1001/1123/1008 981/1124/1009 +f 1004/1119/1004 1001/1123/1008 990/1120/1005 +f 971/1121/1006 981/1124/1009 948/1125/1010 +f 990/1120/1005 981/1124/1009 971/1121/1006 +f 1042/1122/1007 1079/1098/983 1047/1126/1011 +f 1001/1123/1008 1047/1126/1011 1003/1127/1012 +f 1042/1122/1007 1047/1126/1011 1001/1123/1008 +f 981/1124/1009 1003/1127/1012 989/1128/1013 +f 1001/1123/1008 1003/1127/1012 981/1124/1009 +f 948/1125/1010 989/1128/1013 970/1129/1014 +f 981/1124/1009 989/1128/1013 948/1125/1010 +f 1047/1126/1011 1079/1098/983 1053/1130/1015 +f 1003/1127/1012 1053/1130/1015 1021/1131/1016 +f 1047/1126/1011 1053/1130/1015 1003/1127/1012 +f 989/1128/1013 1021/1131/1016 999/1132/1017 +f 1003/1127/1012 1021/1131/1016 989/1128/1013 +f 970/1129/1014 999/1132/1017 993/1133/1018 +f 989/1128/1013 999/1132/1017 970/1129/1014 +f 1053/1130/1015 1079/1098/983 1058/1134/1019 +f 1021/1131/1016 1058/1134/1019 1049/1135/1020 +f 1053/1130/1015 1058/1134/1019 1021/1131/1016 +f 999/1132/1017 1049/1135/1020 1036/1136/1021 +f 1021/1131/1016 1049/1135/1020 999/1132/1017 +f 993/1133/1018 1036/1136/1021 1025/1137/1022 +f 999/1132/1017 1036/1136/1021 993/1133/1018 +f 1058/1134/1019 1079/1098/983 1077/1138/1023 +f 1049/1135/1020 1077/1138/1023 1075/1139/1024 +f 1058/1134/1019 1077/1138/1023 1049/1135/1020 +f 1036/1136/1021 1075/1139/1024 1072/1140/1025 +f 1049/1135/1020 1075/1139/1024 1036/1136/1021 +f 1025/1137/1022 1072/1140/1025 1070/1141/1026 +f 1036/1136/1021 1072/1140/1025 1025/1137/1022 +f 1077/1138/1023 1079/1098/983 1089/1142/1027 +f 1075/1139/1024 1089/1142/1027 1099/1143/1028 +f 1077/1138/1023 1089/1142/1027 1075/1139/1024 +f 1072/1140/1025 1099/1143/1028 1118/1144/1029 +f 1075/1139/1024 1099/1143/1028 1072/1140/1025 +f 1070/1141/1026 1118/1144/1029 1133/1145/1030 +f 1072/1140/1025 1118/1144/1029 1070/1141/1026 +f 1089/1142/1027 1079/1098/983 1095/1146/1031 +f 1099/1143/1028 1095/1146/1031 1135/1147/1032 +f 1089/1142/1027 1095/1146/1031 1099/1143/1028 +f 1118/1144/1029 1135/1147/1032 1151/1148/1033 +f 1099/1143/1028 1135/1147/1032 1118/1144/1029 +f 1133/1145/1030 1151/1148/1033 1167/1149/1034 +f 1118/1144/1029 1151/1148/1033 1133/1145/1030 +f 1095/1146/1031 1079/1098/983 1105/1150/1035 +f 1135/1147/1032 1105/1150/1035 1147/1151/1036 +f 1095/1146/1031 1105/1150/1035 1135/1147/1032 +f 1151/1148/1033 1147/1151/1036 1171/1152/1037 +f 1135/1147/1032 1147/1151/1036 1151/1148/1033 +f 1167/1149/1034 1171/1152/1037 1185/1153/1038 +f 1151/1148/1033 1171/1152/1037 1167/1149/1034 +f 1105/1150/1035 1079/1098/983 1113/1154/1039 +f 1147/1151/1036 1113/1154/1039 1150/1155/1040 +f 1105/1150/1035 1113/1154/1039 1147/1151/1036 +f 1171/1152/1037 1150/1155/1040 1175/1156/1041 +f 1147/1151/1036 1150/1155/1040 1171/1152/1037 +f 1185/1153/1038 1175/1156/1041 1194/1157/1042 +f 1171/1152/1037 1175/1156/1041 1185/1153/1038 +f 1113/1154/1039 1079/1098/983 1106/1158/1043 +f 1150/1155/1040 1106/1158/1043 1148/1159/1044 +f 1113/1154/1039 1106/1158/1043 1150/1155/1040 +f 1175/1156/1041 1148/1159/1044 1172/1160/1045 +f 1150/1155/1040 1148/1159/1044 1175/1156/1041 +f 1194/1157/1042 1172/1160/1045 1186/1161/1046 +f 1175/1156/1041 1172/1160/1045 1194/1157/1042 +f 1106/1158/1043 1079/1098/983 1096/1097/982 +f 1148/1159/1044 1096/1097/982 1136/1100/985 +f 1106/1158/1043 1096/1097/982 1148/1159/1044 +f 1172/1160/1045 1136/1100/985 1152/1102/987 +f 1148/1159/1044 1136/1100/985 1172/1160/1045 +f 1186/1161/1046 1152/1102/987 1168/1104/989 +f 1172/1160/1045 1152/1102/987 1186/1161/1046 +f 301/1162/1047 284/1163/1048 296/1164/984 +f 331/1165/985 296/1164/984 305/1166/1049 +f 301/1162/1047 296/1164/984 331/1165/985 +f 355/1167/1050 305/1166/1049 315/1168/988 +f 331/1165/985 305/1166/1049 355/1167/1050 +f 361/1169/989 315/1168/988 327/1170/1051 +f 355/1167/1050 315/1168/988 361/1169/989 +f 296/1164/984 284/1163/1048 283/1171/991 +f 305/1166/1049 283/1171/991 281/1172/992 +f 296/1164/984 283/1171/991 305/1166/1049 +f 315/1168/988 281/1172/992 278/1173/993 +f 305/1166/1049 281/1172/992 315/1168/988 +f 327/1170/1051 278/1173/993 276/1174/994 +f 315/1168/988 278/1173/993 327/1170/1051 +f 283/1171/991 284/1163/1048 263/1175/995 +f 281/1172/992 263/1175/995 253/1176/1052 +f 283/1171/991 263/1175/995 281/1172/992 +f 278/1173/993 253/1176/1052 231/1177/1053 +f 281/1172/992 253/1176/1052 278/1173/993 +f 276/1174/994 231/1177/1053 220/1178/998 +f 278/1173/993 231/1177/1053 276/1174/994 +f 263/1175/995 284/1163/1048 257/1179/999 +f 253/1176/1052 257/1179/999 218/1180/1000 +f 263/1175/995 257/1179/999 253/1176/1052 +f 231/1177/1053 218/1180/1000 199/1181/1054 +f 253/1176/1052 218/1180/1000 231/1177/1053 +f 220/1178/998 199/1181/1054 186/1182/1055 +f 231/1177/1053 199/1181/1054 220/1178/998 +f 257/1179/999 284/1163/1048 249/1183/1003 +f 218/1180/1000 249/1183/1003 208/1184/1056 +f 257/1179/999 249/1183/1003 218/1180/1000 +f 199/1181/1054 208/1184/1056 178/1185/1005 +f 218/1180/1000 208/1184/1056 199/1181/1054 +f 186/1182/1055 178/1185/1005 166/1186/1006 +f 199/1181/1054 178/1185/1005 186/1182/1055 +f 249/1183/1003 284/1163/1048 241/1187/1057 +f 208/1184/1056 241/1187/1057 201/1188/1058 +f 249/1183/1003 241/1187/1057 208/1184/1056 +f 178/1185/1005 201/1188/1058 176/1189/1009 +f 208/1184/1056 201/1188/1058 178/1185/1005 +f 166/1186/1006 176/1189/1009 160/1190/1010 +f 178/1185/1005 176/1189/1009 166/1186/1006 +f 241/1187/1057 284/1163/1048 248/1191/1011 +f 201/1188/1058 248/1191/1011 207/1192/1012 +f 241/1187/1057 248/1191/1011 201/1188/1058 +f 176/1189/1009 207/1192/1012 177/1193/1059 +f 201/1188/1058 207/1192/1012 176/1189/1009 +f 160/1190/1010 177/1193/1059 165/1194/1014 +f 176/1189/1009 177/1193/1059 160/1190/1010 +f 248/1191/1011 284/1163/1048 256/1195/1015 +f 207/1192/1012 256/1195/1015 217/1196/1016 +f 248/1191/1011 256/1195/1015 207/1192/1012 +f 177/1193/1059 217/1196/1016 200/1197/1060 +f 207/1192/1012 217/1196/1016 177/1193/1059 +f 165/1194/1014 200/1197/1060 185/1198/1018 +f 177/1193/1059 200/1197/1060 165/1194/1014 +f 256/1195/1015 284/1163/1048 262/1199/1019 +f 217/1196/1016 262/1199/1019 252/1200/1020 +f 256/1195/1015 262/1199/1019 217/1196/1016 +f 200/1197/1060 252/1200/1020 232/1201/1061 +f 217/1196/1016 252/1200/1020 200/1197/1060 +f 185/1198/1018 232/1201/1061 219/1202/1022 +f 200/1197/1060 232/1201/1061 185/1198/1018 +f 262/1199/1019 284/1163/1048 282/1203/1023 +f 252/1200/1020 282/1203/1023 280/1204/1024 +f 262/1199/1019 282/1203/1023 252/1200/1020 +f 232/1201/1061 280/1204/1024 277/1205/1062 +f 252/1200/1020 280/1204/1024 232/1201/1061 +f 219/1202/1022 277/1205/1062 275/1206/1063 +f 232/1201/1061 277/1205/1062 219/1202/1022 +f 282/1203/1023 284/1163/1048 295/1207/1027 +f 280/1204/1024 295/1207/1027 304/1208/1064 +f 282/1203/1023 295/1207/1027 280/1204/1024 +f 277/1205/1062 304/1208/1064 314/1209/1029 +f 280/1204/1024 304/1208/1064 277/1205/1062 +f 275/1206/1063 314/1209/1029 326/1210/1065 +f 277/1205/1062 314/1209/1029 275/1206/1063 +f 295/1207/1027 284/1163/1048 300/1211/1066 +f 304/1208/1064 300/1211/1066 330/1212/1032 +f 295/1207/1027 300/1211/1066 304/1208/1064 +f 314/1209/1029 330/1212/1032 354/1213/1033 +f 304/1208/1064 330/1212/1032 314/1209/1029 +f 326/1210/1065 354/1213/1033 360/1214/1034 +f 314/1209/1029 354/1213/1033 326/1210/1065 +f 300/1211/1066 284/1163/1048 306/1215/1035 +f 330/1212/1032 306/1215/1035 347/1216/1067 +f 300/1211/1066 306/1215/1035 330/1212/1032 +f 354/1213/1033 347/1216/1067 370/1217/1068 +f 330/1212/1032 347/1216/1067 354/1213/1033 +f 360/1214/1034 370/1217/1068 382/1218/1038 +f 354/1213/1033 370/1217/1068 360/1214/1034 +f 306/1215/1035 284/1163/1048 311/1219/1039 +f 347/1216/1067 311/1219/1039 351/1220/1069 +f 306/1215/1035 311/1219/1039 347/1216/1067 +f 370/1217/1068 351/1220/1069 373/1221/1070 +f 347/1216/1067 351/1220/1069 370/1217/1068 +f 382/1218/1038 373/1221/1070 406/1222/1071 +f 370/1217/1068 373/1221/1070 382/1218/1038 +f 311/1219/1039 284/1163/1048 307/1223/1072 +f 351/1220/1069 307/1223/1072 348/1224/1073 +f 311/1219/1039 307/1223/1072 351/1220/1069 +f 373/1221/1070 348/1224/1073 371/1225/1074 +f 351/1220/1069 348/1224/1073 373/1221/1070 +f 406/1222/1071 371/1225/1074 383/1226/1075 +f 373/1221/1070 371/1225/1074 406/1222/1071 +f 307/1223/1072 284/1163/1048 301/1162/1047 +f 348/1224/1073 301/1162/1047 331/1165/985 +f 307/1223/1072 301/1162/1047 348/1224/1073 +f 371/1225/1074 331/1165/985 355/1167/1050 +f 348/1224/1073 331/1165/985 371/1225/1074 +f 383/1226/1075 355/1167/1050 361/1169/989 +f 371/1225/1074 355/1167/1050 383/1226/1075 +f 1080/1227/1076 1250/1228/1077 1249/580/561 +f 1081/1229/1076 1250/1228/1077 1080/1227/1076 +f 1249/580/561 1279/1230/1078 1278/592/573 +f 1250/1228/1077 1279/1230/1078 1249/580/561 +f 1278/592/573 1286/1231/1079 1285/598/579 +f 1279/1230/1078 1286/1231/1079 1278/592/573 +f 1285/598/579 1290/1232/1080 1289/1233/1080 +f 1286/1231/1079 1290/1232/1080 1285/598/579 +f 1289/1233/1080 1288/1234/1081 1287/1235/1081 +f 1290/1232/1080 1288/1234/1081 1289/1233/1080 +f 1287/1235/1081 1283/1236/1082 1282/1237/1082 +f 1288/1234/1081 1283/1236/1082 1287/1235/1081 +f 1282/1237/1082 1281/1238/1083 1280/604/585 +f 1283/1236/1082 1281/1238/1083 1282/1237/1082 +f 1280/604/585 1242/1239/1084 1241/1240/1084 +f 1281/1238/1083 1242/1239/1084 1280/604/585 +f 1241/1240/1084 1081/1229/1076 1080/1227/1076 +f 1242/1239/1084 1081/1229/1076 1241/1240/1084 +f 273/1241/1085 104/521/502 105/1242/1086 +f 274/1243/1085 273/1241/1085 105/1242/1086 +f 104/521/502 75/532/513 76/1244/1087 +f 105/1242/1086 104/521/502 76/1244/1087 +f 75/532/513 66/538/519 67/1245/1088 +f 76/1244/1087 75/532/513 67/1245/1088 +f 66/538/519 62/1246/1089 63/1247/1089 +f 67/1245/1088 66/538/519 63/1247/1089 +f 62/1246/1089 64/1248/1090 65/1249/1090 +f 63/1247/1089 62/1246/1089 65/1249/1090 +f 64/1248/1090 68/1250/1091 69/1251/1091 +f 65/1249/1090 64/1248/1090 69/1251/1091 +f 68/1250/1091 72/544/525 73/1252/1092 +f 69/1251/1091 68/1250/1091 73/1252/1092 +f 72/544/525 113/1253/1093 114/1254/1093 +f 73/1252/1092 72/544/525 114/1254/1093 +f 113/1253/1093 273/1241/1085 274/1243/1085 +f 114/1254/1093 113/1253/1093 274/1243/1085 +f 1097/1255/1094 1074/1256/1095 1084/1257/1096 +f 1184/1258/1097 1084/1257/1096 1121/1259/1098 +f 1097/1255/1094 1084/1257/1096 1184/1258/1097 +f 1181/1260/1099 1121/1259/1098 1120/1261/1100 +f 1184/1258/1097 1121/1259/1098 1181/1260/1099 +f 1170/1262/1048 1120/1261/1100 1110/1263/1048 +f 1181/1260/1099 1120/1261/1100 1170/1262/1048 +f 1084/1257/1096 1074/1256/1095 1060/1264/1101 +f 1121/1259/1098 1060/1264/1101 1032/1265/1102 +f 1084/1257/1096 1060/1264/1101 1121/1259/1098 +f 1120/1261/1100 1032/1265/1102 1034/1266/1103 +f 1121/1259/1098 1032/1265/1102 1120/1261/1100 +f 1110/1263/1048 1034/1266/1103 1041/1267/1048 +f 1120/1261/1100 1034/1266/1103 1110/1263/1048 +f 1060/1264/1101 1074/1256/1095 1051/1268/1104 +f 1032/1265/1102 1051/1268/1104 969/1269/1105 +f 1060/1264/1101 1051/1268/1104 1032/1265/1102 +f 1034/1266/1103 969/1269/1105 974/1270/1106 +f 1032/1265/1102 969/1269/1105 1034/1266/1103 +f 1041/1267/1048 974/1270/1106 991/1271/1048 +f 1034/1266/1103 974/1270/1106 1041/1267/1048 +f 1051/1268/1104 1074/1256/1095 1043/1272/1107 +f 969/1269/1105 1043/1272/1107 925/1273/1108 +f 1051/1268/1104 1043/1272/1107 969/1269/1105 +f 974/1270/1106 925/1273/1108 929/1274/1109 +f 969/1269/1105 925/1273/1108 974/1270/1106 +f 991/1271/1048 929/1274/1109 943/1275/1048 +f 974/1270/1106 929/1274/1109 991/1271/1048 +f 1043/1272/1107 1074/1256/1095 1044/1276/1110 +f 925/1273/1108 1044/1276/1110 928/1277/1111 +f 1043/1272/1107 1044/1276/1110 925/1273/1108 +f 929/1274/1109 928/1277/1111 932/1278/1112 +f 925/1273/1108 928/1277/1111 929/1274/1109 +f 943/1275/1048 932/1278/1112 944/1279/983 +f 929/1274/1109 932/1278/1112 943/1275/1048 +f 1044/1276/1110 1074/1256/1095 1052/1280/1113 +f 928/1277/1111 1052/1280/1113 975/1281/1114 +f 1044/1276/1110 1052/1280/1113 928/1277/1111 +f 932/1278/1112 975/1281/1114 976/1282/1115 +f 928/1277/1111 975/1281/1114 932/1278/1112 +f 944/1279/983 976/1282/1115 992/1283/983 +f 932/1278/1112 976/1282/1115 944/1279/983 +f 1052/1280/1113 1074/1256/1095 1061/1284/1116 +f 975/1281/1114 1061/1284/1116 1039/1285/1117 +f 1052/1280/1113 1061/1284/1116 975/1281/1114 +f 976/1282/1115 1039/1285/1117 1040/1286/1118 +f 975/1281/1114 1039/1285/1117 976/1282/1115 +f 992/1283/983 1040/1286/1118 1046/1287/1048 +f 976/1282/1115 1040/1286/1118 992/1283/983 +f 1061/1284/1116 1074/1256/1095 1085/1288/1119 +f 1039/1285/1117 1085/1288/1119 1128/1289/1120 +f 1061/1284/1116 1085/1288/1119 1039/1285/1117 +f 1040/1286/1118 1128/1289/1120 1127/1290/1121 +f 1039/1285/1117 1128/1289/1120 1040/1286/1118 +f 1046/1287/1048 1127/1290/1121 1115/1291/1122 +f 1040/1286/1118 1127/1290/1121 1046/1287/1048 +f 1085/1288/1119 1074/1256/1095 1098/1292/1123 +f 1128/1289/1120 1098/1292/1123 1190/1293/1124 +f 1085/1288/1119 1098/1292/1123 1128/1289/1120 +f 1127/1290/1121 1190/1293/1124 1187/1294/1125 +f 1128/1289/1120 1190/1293/1124 1127/1290/1121 +f 1115/1291/1122 1187/1294/1125 1173/1295/1048 +f 1127/1290/1121 1187/1294/1125 1115/1291/1122 +f 1098/1292/1123 1074/1256/1095 1112/1296/1126 +f 1190/1293/1124 1112/1296/1126 1217/1297/1127 +f 1098/1292/1123 1112/1296/1126 1190/1293/1124 +f 1187/1294/1125 1217/1297/1127 1212/1298/1128 +f 1190/1293/1124 1217/1297/1127 1187/1294/1125 +f 1173/1295/1048 1212/1298/1128 1198/1299/1048 +f 1187/1294/1125 1212/1298/1128 1173/1295/1048 +f 1112/1296/1126 1074/1256/1095 1111/1300/1129 +f 1217/1297/1127 1111/1300/1129 1214/1301/1130 +f 1112/1296/1126 1111/1300/1129 1217/1297/1127 +f 1212/1298/1128 1214/1301/1130 1211/1302/1131 +f 1217/1297/1127 1214/1301/1130 1212/1298/1128 +f 1198/1299/1048 1211/1302/1131 1195/1303/1048 +f 1212/1298/1128 1211/1302/1131 1198/1299/1048 +f 1111/1300/1129 1074/1256/1095 1097/1255/1094 +f 1214/1301/1130 1097/1255/1094 1184/1258/1097 +f 1111/1300/1129 1097/1255/1094 1214/1301/1130 +f 1211/1302/1131 1184/1258/1097 1181/1260/1099 +f 1214/1301/1130 1184/1258/1097 1211/1302/1131 +f 1195/1303/1048 1181/1260/1099 1170/1262/1048 +f 1211/1302/1131 1181/1260/1099 1195/1303/1048 +f 302/1304/1132 279/1305/1095 293/1306/1133 +f 380/1307/1097 293/1306/1133 317/1308/1098 +f 302/1304/1132 293/1306/1133 380/1307/1097 +f 379/1309/1134 317/1308/1098 316/1310/1135 +f 380/1307/1097 317/1308/1098 379/1309/1134 +f 369/1311/1136 316/1310/1135 308/1312/1137 +f 379/1309/1134 316/1310/1135 369/1311/1136 +f 369/1311/1136 308/1312/1137 285/1313/1138 +f 293/1306/1133 279/1305/1095 269/1314/1139 +f 317/1308/1098 269/1314/1139 226/1315/1102 +f 293/1306/1133 269/1314/1139 317/1308/1098 +f 316/1310/1135 226/1315/1102 228/1316/1103 +f 317/1308/1098 226/1315/1102 316/1310/1135 +f 308/1312/1137 228/1316/1103 239/1317/1140 +f 316/1310/1135 228/1316/1103 308/1312/1137 +f 308/1312/1137 239/1317/1140 285/1313/1138 +f 269/1314/1139 279/1305/1095 254/1318/1104 +f 226/1315/1102 254/1318/1104 163/1319/1105 +f 269/1314/1139 254/1318/1104 226/1315/1102 +f 228/1316/1103 163/1319/1105 167/1320/1106 +f 226/1315/1102 163/1319/1105 228/1316/1103 +f 239/1317/1140 167/1320/1106 179/1321/1141 +f 228/1316/1103 167/1320/1106 239/1317/1140 +f 239/1317/1140 179/1321/1141 285/1313/1138 +f 254/1318/1104 279/1305/1095 242/1322/1107 +f 163/1319/1105 242/1322/1107 136/1323/1108 +f 254/1318/1104 242/1322/1107 163/1319/1105 +f 167/1320/1106 136/1323/1108 138/1324/1142 +f 163/1319/1105 136/1323/1108 167/1320/1106 +f 179/1321/1141 138/1324/1142 156/1325/1143 +f 167/1320/1106 138/1324/1142 179/1321/1141 +f 179/1321/1141 156/1325/1143 285/1313/1138 +f 242/1322/1107 279/1305/1095 243/1326/1144 +f 136/1323/1108 243/1326/1144 137/1327/1145 +f 242/1322/1107 243/1326/1144 136/1323/1108 +f 138/1324/1142 137/1327/1145 139/1328/1146 +f 136/1323/1108 137/1327/1145 138/1324/1142 +f 156/1325/1143 139/1328/1146 158/1329/1147 +f 138/1324/1142 139/1328/1146 156/1325/1143 +f 156/1325/1143 158/1329/1147 285/1313/1138 +f 243/1326/1144 279/1305/1095 255/1330/1148 +f 137/1327/1145 255/1330/1148 168/1331/1114 +f 243/1326/1144 255/1330/1148 137/1327/1145 +f 139/1328/1146 168/1331/1114 172/1332/1149 +f 137/1327/1145 168/1331/1114 139/1328/1146 +f 158/1329/1147 172/1332/1149 183/1333/1150 +f 139/1328/1146 172/1332/1149 158/1329/1147 +f 158/1329/1147 183/1333/1150 285/1313/1138 +f 255/1330/1148 279/1305/1095 272/1334/1151 +f 168/1331/1114 272/1334/1151 233/1335/1117 +f 255/1330/1148 272/1334/1151 168/1331/1114 +f 172/1332/1149 233/1335/1117 234/1336/1152 +f 168/1331/1114 233/1335/1117 172/1332/1149 +f 183/1333/1150 234/1336/1152 244/1337/1153 +f 172/1332/1149 234/1336/1152 183/1333/1150 +f 183/1333/1150 244/1337/1153 285/1313/1138 +f 272/1334/1151 279/1305/1095 294/1338/1154 +f 233/1335/1117 294/1338/1154 324/1339/1120 +f 272/1334/1151 294/1338/1154 233/1335/1117 +f 234/1336/1152 324/1339/1120 319/1340/1121 +f 233/1335/1117 324/1339/1120 234/1336/1152 +f 244/1337/1153 319/1340/1121 312/1341/1155 +f 234/1336/1152 319/1340/1121 244/1337/1153 +f 244/1337/1153 312/1341/1155 285/1313/1138 +f 294/1338/1154 279/1305/1095 303/1342/1156 +f 324/1339/1120 303/1342/1156 385/1343/1157 +f 294/1338/1154 303/1342/1156 324/1339/1120 +f 319/1340/1121 385/1343/1157 384/1344/1158 +f 324/1339/1120 385/1343/1157 319/1340/1121 +f 312/1341/1155 384/1344/1158 372/1345/1159 +f 319/1340/1121 384/1344/1158 312/1341/1155 +f 312/1341/1155 372/1345/1159 285/1313/1138 +f 303/1342/1156 279/1305/1095 310/1346/1126 +f 385/1343/1157 310/1346/1126 426/1347/1127 +f 303/1342/1156 310/1346/1126 385/1343/1157 +f 384/1344/1158 426/1347/1127 422/1348/1160 +f 385/1343/1157 426/1347/1127 384/1344/1158 +f 372/1345/1159 422/1348/1160 411/1349/1161 +f 384/1344/1158 422/1348/1160 372/1345/1159 +f 372/1345/1159 411/1349/1161 285/1313/1138 +f 310/1346/1126 279/1305/1095 309/1350/1129 +f 426/1347/1127 309/1350/1129 425/1351/1162 +f 310/1346/1126 309/1350/1129 426/1347/1127 +f 422/1348/1160 425/1351/1162 421/1352/1163 +f 426/1347/1127 425/1351/1162 422/1348/1160 +f 411/1349/1161 421/1352/1163 410/1353/1164 +f 422/1348/1160 421/1352/1163 411/1349/1161 +f 411/1349/1161 410/1353/1164 285/1313/1138 +f 309/1350/1129 279/1305/1095 302/1304/1132 +f 425/1351/1162 302/1304/1132 380/1307/1097 +f 309/1350/1129 302/1304/1132 425/1351/1162 +f 421/1352/1163 380/1307/1097 379/1309/1134 +f 425/1351/1162 380/1307/1097 421/1352/1163 +f 410/1353/1164 379/1309/1134 369/1311/1136 +f 421/1352/1163 379/1309/1134 410/1353/1164 +f 410/1353/1164 369/1311/1136 285/1313/1138 diff --git a/tests/barstest/shuttle.png b/tests/barstest/shuttle.png Binary files differnew file mode 100644 index 00000000..52d6244c --- /dev/null +++ b/tests/barstest/shuttle.png diff --git a/tests/directional/main.cpp b/tests/directional/main.cpp index 2b077b97..551ab141 100644 --- a/tests/directional/main.cpp +++ b/tests/directional/main.cpp @@ -80,6 +80,9 @@ int main(int argc, char **argv) backgroundCheckBox->setText(QStringLiteral("Show background")); backgroundCheckBox->setChecked(true); + QCheckBox *optimizationCheckBox = new QCheckBox(widget); + optimizationCheckBox->setText(QStringLiteral("Optimization static")); + QCheckBox *gridCheckBox = new QCheckBox(widget); gridCheckBox->setText(QStringLiteral("Show grid")); gridCheckBox->setChecked(true); @@ -100,6 +103,7 @@ int main(int argc, char **argv) vLayout->addWidget(labelButton, 0, Qt::AlignTop); vLayout->addWidget(cameraButton, 0, Qt::AlignTop); vLayout->addWidget(toggleRotationButton, 0, Qt::AlignTop); + vLayout->addWidget(optimizationCheckBox); vLayout->addWidget(backgroundCheckBox); vLayout->addWidget(gridCheckBox); vLayout->addWidget(new QLabel(QStringLiteral("Change dot style"))); @@ -127,6 +131,8 @@ int main(int argc, char **argv) QObject::connect(modifier, &ScatterDataModifier::backgroundEnabledChanged, backgroundCheckBox, &QCheckBox::setChecked); + QObject::connect(optimizationCheckBox, &QCheckBox::stateChanged, + modifier, &ScatterDataModifier::enableOptimization); QObject::connect(modifier, &ScatterDataModifier::gridEnabledChanged, gridCheckBox, &QCheckBox::setChecked); QObject::connect(itemStyleList, SIGNAL(currentIndexChanged(int)), modifier, @@ -150,6 +156,7 @@ int main(int argc, char **argv) &QFontComboBox::setCurrentFont); itemStyleList->setCurrentIndex(0); + optimizationCheckBox->setChecked(true); widget->show(); return app.exec(); diff --git a/tests/directional/scatterdatamodifier.cpp b/tests/directional/scatterdatamodifier.cpp index 1422cebb..2d6672b6 100644 --- a/tests/directional/scatterdatamodifier.cpp +++ b/tests/directional/scatterdatamodifier.cpp @@ -119,7 +119,14 @@ void ScatterDataModifier::addData() m_graph->seriesList().at(0)->dataProxy()->resetArray(dataArray); } -//! [8] +void ScatterDataModifier::enableOptimization(int enabled) +{ + if (enabled) + m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); + else + m_graph->setOptimizationHints(QAbstract3DGraph::OptimizationDefault); +} + void ScatterDataModifier::changeStyle(int style) { QComboBox *comboBox = qobject_cast<QComboBox *>(sender()); diff --git a/tests/directional/scatterdatamodifier.h b/tests/directional/scatterdatamodifier.h index b87fa89f..20e59cdc 100644 --- a/tests/directional/scatterdatamodifier.h +++ b/tests/directional/scatterdatamodifier.h @@ -39,6 +39,7 @@ public: void changeLabelStyle(); void changeFont(const QFont &font); void changeFontSize(int fontsize); + void enableOptimization(int enabled); void setBackgroundEnabled(int enabled); void setGridEnabled(int enabled); void toggleRotation(); diff --git a/tests/qmlcamera/qml/qmlcamera/main.qml b/tests/qmlcamera/qml/qmlcamera/main.qml index 444e175c..be4feca3 100644 --- a/tests/qmlcamera/qml/qmlcamera/main.qml +++ b/tests/qmlcamera/qml/qmlcamera/main.qml @@ -18,7 +18,7 @@ import QtQuick 2.1 import QtQuick.Controls 1.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 import "." Rectangle { @@ -35,6 +35,15 @@ Rectangle { id: chartAxes } + Camera3D { + id: customCamera + wrapXRotation: false + xRotation: camControlArea.xValue + yRotation: camControlArea.yValue + zoomLevel: zoomSlider.value + target: Qt.vector3d(0.5, 0.5, 0.5) + } + Item { id: dataView width: parent.width - camControlArea.width @@ -60,21 +69,20 @@ Rectangle { columnAxis: chartAxes.column valueAxis: chartAxes.expenses - // Bind UI controls to the camera - scene.activeCamera.wrapXRotation: false - scene.activeCamera.xRotation: camControlArea.xValue - scene.activeCamera.yRotation: camControlArea.yValue - scene.activeCamera.zoomLevel: zoomSlider.value + scene.activeCamera: customCamera inputHandler: null customItemList: [shuttleItem, labelItem] + orthoProjection: true + + floorLevel: 10 } Custom3DItem { id: shuttleItem meshFile: ":/items/shuttle.obj" textureFile: ":/items/shuttle.png" - position: Qt.vector3d(5.0,29.0,3.0) + position: Qt.vector3d(2.0,29.0,2.0) scaling: Qt.vector3d(0.2,0.2,0.2) } @@ -82,7 +90,7 @@ Rectangle { id: labelItem facingCamera: true positionAbsolute: true - position: Qt.vector3d(0.0,1.5,0.0) + position: Qt.vector3d(-1.0,1.5,-1.0) scaling: Qt.vector3d(1.0,1.0,1.0) text: "Qt Shuttle" } @@ -162,6 +170,11 @@ Rectangle { currentAngle += 5 chartData.series.meshAngle = currentAngle shuttleItem.setRotationAxisAndAngle(Qt.vector3d(0.0, 1.0, 1.0), currentAngle) + console.log("label pos:", labelItem.position) + labelItem.position.x += 0.1 + labelItem.position.z += 0.1 + customCamera.target.x -= 0.1 + customCamera.target.z -= 0.1 } } @@ -203,4 +216,20 @@ Rectangle { } } } + + Button { + id: reflectionToggle + anchors.bottom: shuttleAdd.top + width: camControlArea.width + text: "Show reflections" + onClicked: { + if (testChart.reflection === true) { + text = "Show reflections" + testChart.reflection = false + } else { + text = "Hide reflections" + testChart.reflection = true + } + } + } } diff --git a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml index 0ec9d277..29c51fb3 100644 --- a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml +++ b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml @@ -71,18 +71,16 @@ Rectangle { isIncreasing = false; } } else { - // TODO: Once QTRD-2645 is fixed, change this to remove from - // random index to add coverage. - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); - graphModel.remove(2); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); + graphModel.remove(Math.random() * (graphModel.count - 1)); if (graphModel.count == 2) { scatterGraph.theme.type = Theme3D.ThemeDigia; isIncreasing = true; @@ -118,6 +116,12 @@ Rectangle { shadowQuality: AbstractGraph3D.ShadowQualitySoftMedium scene.activeCamera.yRotation: 30.0 inputHandler: null + axisX.min: 0 + axisY.min: 0 + axisZ.min: 0 + axisX.max: 1 + axisY.max: 1 + axisZ.max: 1 Scatter3DSeries { id: scatterSeries diff --git a/tests/qmlmultiwindow/qml/qmlmultiwindow/main.qml b/tests/qmlmultiwindow/qml/qmlmultiwindow/main.qml index 7dfe0bec..da382e3d 100644 --- a/tests/qmlmultiwindow/qml/qmlmultiwindow/main.qml +++ b/tests/qmlmultiwindow/qml/qmlmultiwindow/main.qml @@ -31,19 +31,51 @@ Rectangle { id: data } - Window { - id: firstWindow - x: 100 - y: 100 - width: 500 - height: 500 - visible: true - Rectangle { - id: firstRect - color: "red" - anchors.fill: parent - } - } + property QtObject surfaceWindowObject; + property string surfaceWindowStr: + "\n + import QtQuick 2.1\n + import QtQuick.Window 2.1\n + import QtQuick.Layouts 1.0\n + import QtDataVisualization 1.0\n + import \".\"\n + Window {\n + Data {\n + id: data\n + }\n + id: firstWindow\n + x: 100\n + y: 100\n + width: 500\n + height: 500\n + visible: true\n + Rectangle {\n + id: firstRect\n + color: \"red\"\n + anchors.fill: parent\n + Surface3D {\n + id: surfaceGraph\n + anchors.fill: parent\n + anchors.margins: parent.border.width\n + theme: Theme3D {\n + type: Theme3D.ThemePrimaryColors\n + font.pointSize: 60\n + }\n + scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh\n + Surface3DSeries {\n + itemLabelFormat: \"Pop density at (@xLabel N, @zLabel E): @yLabel\"\n + ItemModelSurfaceDataProxy {\n + itemModel: data.myData\n + rowRole: \"row\"\n + columnRole: \"col\"\n + xPosRole: \"latitude\"\n + zPosRole: \"longitude\"\n + yPosRole: \"pop_density\"\n + }\n + }\n + }\n + }\n + }" Window { id: secondWindow @@ -59,18 +91,12 @@ Rectangle { } } - states: [ - State { - name: "firstWindow" - ParentChange { target: surfaceGraph; parent: firstRect; x: 0; y: 0 } - }, - State { - name: "secondWindow" - ParentChange { target: surfaceGraph; parent: secondRect; x: 0; y: 0 } - } - ] + function destroyWindow() { + if (surfaceWindowObject != null) + surfaceWindowObject.destroy() + } - state: "firstWindow" + Component.onDestruction: destroyWindow() //! [0] GridLayout { @@ -86,32 +112,17 @@ Rectangle { Rectangle { Layout.fillHeight: true Layout.fillWidth: true - border.color: surfaceGraph.theme.gridLineColor border.width: 2 + } - Surface3D { - id: surfaceGraph - anchors.fill: parent - anchors.margins: parent.border.width - theme: Theme3D { - type: Theme3D.ThemePrimaryColors - font.pointSize: 60 - } - scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricLeftHigh - - Surface3DSeries { - itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel" - ItemModelSurfaceDataProxy { - itemModel: data.myData - // The surface data points are not neatly lined up in rows and columns, - // so we define explicit row and column roles. - rowRole: "row" - columnRole: "col" - xPosRole: "latitude" - zPosRole: "longitude" - yPosRole: "pop_density" - } - } + Timer { + id: windowToggleTimer + interval: 1000 + running: false + repeat: false + onTriggered: { + destroyWindow() + surfaceWindowObject = Qt.createQmlObject(surfaceWindowStr, mainView) } } @@ -131,13 +142,10 @@ Rectangle { Layout.minimumWidth: parent.width / 2 Layout.fillHeight: true Layout.fillWidth: true - text: "Move graph between windows" + text: "(re)construct surface window in a loop" onClicked: { - if (mainView.state === "firstWindow") { - mainView.state = "secondWindow" - } else { - mainView.state = "firstWindow" - } + windowToggleTimer.running = true + windowToggleTimer.repeat = true } } @@ -231,14 +239,11 @@ Rectangle { function clearSelections() { barGraph.clearSelection() scatterGraph.clearSelection() - surfaceGraph.clearSelection() } function resetCameras() { - surfaceGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh scatterGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh barGraph.scene.activeCamera.cameraPreset = Camera3D.CameraPresetIsometricLeftHigh - surfaceGraph.scene.activeCamera.zoomLevel = 100.0 scatterGraph.scene.activeCamera.zoomLevel = 100.0 barGraph.scene.activeCamera.zoomLevel = 100.0 } @@ -246,12 +251,9 @@ Rectangle { function toggleMeshStyle() { if (barGraph.seriesList[0].meshSmooth === true) { barGraph.seriesList[0].meshSmooth = false - if (surfaceGraph.seriesList[0].flatShadingSupported) - surfaceGraph.seriesList[0].flatShadingEnabled = true scatterGraph.seriesList[0].meshSmooth = false } else { barGraph.seriesList[0].meshSmooth = true - surfaceGraph.seriesList[0].flatShadingEnabled = false scatterGraph.seriesList[0].meshSmooth = true } } diff --git a/tests/qmlperf/main.cpp b/tests/qmlperf/main.cpp new file mode 100644 index 00000000..7d35b2ed --- /dev/null +++ b/tests/qmlperf/main.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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 <QtGui/QGuiApplication> +#include <QtCore/QDir> +#include <QtQuick/QQuickView> +#include <QtQml/QQmlEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQuickView viewer; + + // The following are needed to make examples run without having to install the module + // in desktop environments. +#ifdef Q_OS_WIN + QString extraImportPath(QStringLiteral("%1/../../../%2")); +#else + QString extraImportPath(QStringLiteral("%1/../../%2")); +#endif + viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(), + QString::fromLatin1("qml"))); + QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close); + + viewer.setTitle(QStringLiteral("QML Performance")); + viewer.setSource(QUrl("qrc:/qml/qmlperf/main.qml")); + viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.show(); + + return app.exec(); +} diff --git a/tests/qmlperf/qml/qmlperf/main.qml b/tests/qmlperf/qml/qmlperf/main.qml new file mode 100644 index 00000000..35f8df5d --- /dev/null +++ b/tests/qmlperf/qml/qmlperf/main.qml @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtDataVisualization 1.1 +import "script.js" as Script +import "." + +Rectangle { + id: mainview + width: 1280 + height: 1024 + + property var itemCount: 1000.0 + property var addItems: 1000.0 + + Button { + id: changeButton + width: 350 + height: 50 + anchors.left: parent.left + enabled: true + text: "Change" + onClicked: { + console.log("changeButton clicked"); + if (graphView.state == "meshsphere") { + graphView.state = "meshcube" + } else if (graphView.state == "meshcube") { + graphView.state = "meshpyramid" + } else if (graphView.state == "meshpyramid") { + graphView.state = "meshpoint" + } else if (graphView.state == "meshpoint") { + graphView.state = "meshsphere" + } + } + } + + Text { + id: fpsText + text: "Reading" + width: 300 + height: 50 + anchors.left: changeButton.right + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + + Button { + id: optimization + width: 300 + height: 50 + anchors.left: fpsText.right + enabled: true + text: scatterPlot.optimizationHints === AbstractGraph3D.OptimizationDefault ? "To Static" : "To Default" + onClicked: { + console.log("Optimization"); + if (scatterPlot.optimizationHints === AbstractGraph3D.OptimizationDefault) { + scatterPlot.optimizationHints = AbstractGraph3D.OptimizationStatic; + optimization.text = "To Default"; + } else { + scatterPlot.optimizationHints = AbstractGraph3D.OptimizationDefault; + optimization.text = "To Static"; + } + } + } + + Button { + id: itemAdd + width: 300 + height: 50 + anchors.left: optimization.right + enabled: true + text: "Add" + onClicked: { + itemCount = itemCount + addItems; + Script.createData(addItems); + } + } + + Item { + id: graphView + width: mainview.width + height: mainview.height + anchors.top: changeButton.bottom + anchors.left: mainview.left + state: "meshsphere" + + ListModel { + id: dataModel + Component.onCompleted: Script.createData(itemCount) + } + + Scatter3D { + id: scatterPlot + width: graphView.width + height: graphView.height + shadowQuality: AbstractGraph3D.ShadowQualityNone + optimizationHints: AbstractGraph3D.OptimizationDefault + scene.activeCamera.yRotation: 45.0 + measureFps: true + onCurrentFpsChanged: { + fpsText.text = itemCount + " : " + scatterPlot.currentFps.toFixed(1); + } + +// theme: Theme3D { +// type: Theme3D.ThemeRetro +// colorStyle: Theme3D.ColorStyleRangeGradient +// baseGradients: customGradient + +// ColorGradient { +// id: customGradient +// ColorGradientStop { position: 1.0; color: "red" } +// ColorGradientStop { position: 0.0; color: "blue" } +// } +// } + + Scatter3DSeries { + id: scatterSeries + mesh: Abstract3DSeries.MeshSphere + ItemModelScatterDataProxy { + itemModel: dataModel + xPosRole: "x" + yPosRole: "y" + zPosRole: "z" + } + } + } + + states: [ + State { + name: "meshsphere" + StateChangeScript { + name: "doSphere" + script: { + console.log("Do the sphere"); + scatterSeries.mesh = Abstract3DSeries.MeshSphere; + } + } + }, + State { + name: "meshcube" + StateChangeScript { + name: "doCube" + script: { + console.log("Do the cube"); + scatterSeries.mesh = Abstract3DSeries.MeshCube; + } + } + }, + State { + name: "meshpyramid" + StateChangeScript { + name: "doPyramid" + script: { + console.log("Do the pyramid"); + scatterSeries.mesh = Abstract3DSeries.MeshPyramid; + } + } + }, + State { + name: "meshpoint" + StateChangeScript { + name: "doPoint" + script: { + console.log("Do the point"); + scatterSeries.mesh = Abstract3DSeries.MeshPoint; + } + } + } + ] + } +} diff --git a/tests/qmlperf/qml/qmlperf/script.js b/tests/qmlperf/qml/qmlperf/script.js new file mode 100644 index 00000000..dc271e8d --- /dev/null +++ b/tests/qmlperf/qml/qmlperf/script.js @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +//function createData(base) { +// for (var z = 0; z < 30; z++) { +// for (var x = 0; x < 30; x++) { +// var angle = (((z - 16) * (x - 16)) / 144.0) * 1.57; +// var y = Math.sin(angle + base); +// dataModel.append({"z": z, "x": x, "y": y}); +// } +// } +//} + +function createData(base) { + for (var i = 0; i < base; i++) { + dataModel.append({"z": Math.random(), "x": Math.random(), "y": Math.random()}); + } +} diff --git a/tests/qmlperf/qmlperf.pro b/tests/qmlperf/qmlperf.pro new file mode 100644 index 00000000..6560f55c --- /dev/null +++ b/tests/qmlperf/qmlperf.pro @@ -0,0 +1,12 @@ +!include( ../tests.pri ) { + error( "Couldn't find the tests.pri file!" ) +} + +# The .cpp file which was generated for your project. Feel free to hack it. +SOURCES += main.cpp + +RESOURCES += qmlperf.qrc + +OTHER_FILES += doc/src/* \ + doc/images/* \ + qml/qmlperf/* diff --git a/tests/qmlperf/qmlperf.qrc b/tests/qmlperf/qmlperf.qrc new file mode 100644 index 00000000..e50815c5 --- /dev/null +++ b/tests/qmlperf/qmlperf.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>qml/qmlperf/main.qml</file> + <file>qml/qmlperf/script.js</file> + </qresource> +</RCC> diff --git a/tests/qmlvolume/datasource.cpp b/tests/qmlvolume/datasource.cpp new file mode 100644 index 00000000..e1fe5385 --- /dev/null +++ b/tests/qmlvolume/datasource.cpp @@ -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 +** +****************************************************************************/ + +#include "datasource.h" +#include <QtCore/qmath.h> +#include <QtGui/QRgb> +#include <QtGui/QVector3D> + +using namespace QtDataVisualization; + +Q_DECLARE_METATYPE(QCustom3DVolume *) + +DataSource::DataSource(QObject *parent) : + QObject(parent) +{ + qRegisterMetaType<QCustom3DVolume *>(); +} + +DataSource::~DataSource() +{ +} + +void DataSource::fillVolume(QCustom3DVolume *volumeItem) +{ + // Generate example texture data for an half-ellipsoid with a section missing. + // This can take a while if the dimensions are large, so we support incremental data generation. + + int index = 0; + int textureSize = 256; + QVector3D midPoint(float(textureSize) / 2.0f, + float(textureSize) / 2.0f, + float(textureSize) / 2.0f); + + QVector<uchar> *textureData = new QVector<uchar>(textureSize * textureSize * textureSize / 2); + 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 section 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() * 512.0f / float(textureSize))); + colorIndex = 255 - int(adjLen); + } + + (*textureData)[index] = colorIndex; + index++; + } + } + } + + volumeItem->setScaling(QVector3D(2.0f, 1.0f, 2.0f)); + volumeItem->setTextureWidth(textureSize); + volumeItem->setTextureHeight(textureSize / 2); + volumeItem->setTextureDepth(textureSize); + volumeItem->setTextureFormat(QImage::Format_Indexed8); + volumeItem->setTextureData(textureData); + + QVector<QRgb> colorTable(256); + + for (int i = 1; i < 256; i++) { + if (i < 15) + colorTable[i] = qRgba(0, 0, 0, 0); + else if (i < 60) + colorTable[i] = qRgba((i * 2) + 120, 0, 0, 15); + else if (i < 120) + colorTable[i] = qRgba(0, ((i - 60) * 2) + 120, 0, 50); + else if (i < 180) + colorTable[i] = qRgba(0, 0, ((i - 120) * 2) + 120, 255); + else + colorTable[i] = qRgba(i, i, i, 255); + } + + volumeItem->setColorTable(colorTable); +} diff --git a/tests/qmlvolume/datasource.h b/tests/qmlvolume/datasource.h new file mode 100644 index 00000000..fc543792 --- /dev/null +++ b/tests/qmlvolume/datasource.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 DATASOURCE_H +#define DATASOURCE_H + +#include <QtDataVisualization/QCustom3DVolume> +#include <QtCore/QObject> + +using namespace QtDataVisualization; + +class DataSource : public QObject +{ + Q_OBJECT +public: + explicit DataSource(QObject *parent = 0); + virtual ~DataSource(); + +public: + Q_INVOKABLE void fillVolume(QCustom3DVolume *volumeItem); +}; + +#endif diff --git a/tests/qmlvolume/main.cpp b/tests/qmlvolume/main.cpp new file mode 100644 index 00000000..85de7eed --- /dev/null +++ b/tests/qmlvolume/main.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 "datasource.h" + +#include <QtDataVisualization/qutils.h> + +#include <QtGui/QGuiApplication> +#include <QtCore/QDir> +#include <QtQml/QQmlContext> +#include <QtQuick/QQuickView> +#include <QtQml/QQmlEngine> + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQuickView viewer; + + // The following are needed to make examples run without having to install the module + // in desktop environments. +#ifdef Q_OS_WIN + QString extraImportPath(QStringLiteral("%1/../../../%2")); +#else + QString extraImportPath(QStringLiteral("%1/../../%2")); +#endif + viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(), + QString::fromLatin1("qml"))); + QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close); + + viewer.setTitle(QStringLiteral("QML volume example")); + + DataSource dataSource; + viewer.rootContext()->setContextProperty("dataSource", &dataSource); + + viewer.setSource(QUrl("qrc:/qml/qmlvolume/main.qml")); + viewer.setResizeMode(QQuickView::SizeRootObjectToView); + viewer.show(); + + return app.exec(); +} diff --git a/tests/qmlvolume/qml/qmlvolume/NewButton.qml b/tests/qmlvolume/qml/qmlvolume/NewButton.qml new file mode 100644 index 00000000..e4fb99d2 --- /dev/null +++ b/tests/qmlvolume/qml/qmlvolume/NewButton.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.0 + +Item { + id: newbutton + + property alias text: buttonText.text + + signal clicked + + implicitWidth: buttonText.implicitWidth + 5 + implicitHeight: buttonText.implicitHeight + 10 + + Button { + id: buttonText + width: parent.width + height: parent.height + + style: ButtonStyle { + label: Component { + Text { + text: buttonText.text + clip: true + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.fill: parent + } + } + } + onClicked: newbutton.clicked() + } +} diff --git a/tests/qmlvolume/qml/qmlvolume/main.qml b/tests/qmlvolume/qml/qmlvolume/main.qml new file mode 100644 index 00000000..aec5f075 --- /dev/null +++ b/tests/qmlvolume/qml/qmlvolume/main.qml @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +import QtQuick 2.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtDataVisualization 1.2 +import "." + +Item { + id: mainView + width: 1280 + height: 1024 + + Item { + id: dataView + anchors.bottom: parent.bottom + width: parent.width + height: parent.height - buttonLayout.height + + Surface3D { + id: surfaceGraph + + width: dataView.width + height: dataView.height + orthoProjection: true + //measureFps: true + + onCurrentFpsChanged: { + if (fps > 10) + fpsText.text = "FPS: " + Math.round(surfaceGraph.currentFps) + else + fpsText.text = "FPS: " + Math.round(surfaceGraph.currentFps * 10.0) / 10.0 + } + + Surface3DSeries { + id: surfaceSeries + drawMode: Surface3DSeries.DrawSurface; + flatShadingEnabled: false; + meshSmooth: true + itemLabelFormat: "@xLabel, @zLabel: @yLabel" + itemLabelVisible: false + + onItemLabelChanged: { + if (surfaceSeries.selectedPoint === surfaceSeries.invalidSelectionPosition) + selectionText.text = "No selection" + else + selectionText.text = surfaceSeries.itemLabel + } + } + + Component.onCompleted: { + mainView.createVolume(); + } + } + } + + Rectangle { + width: parent.width + height: 50 + anchors.left: parent.left + anchors.top: parent.top + color: surfaceGraph.theme.backgroundColor + + ColumnLayout { + anchors.fill: parent + RowLayout { + id: sliderLayout + anchors.top: parent.top + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumHeight: 150 + spacing: 0 + + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: fpsText.implicitWidth + 10 + Layout.maximumWidth: fpsText.implicitWidth + 10 + Layout.minimumHeight: 50 + Layout.maximumHeight: 50 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + border.color: "gray" + border.width: 1 + radius: 4 + + Text { + id: fpsText + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } + } + + RowLayout { + id: buttonLayout + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumHeight: 50 + anchors.bottom: parent.bottom + spacing: 0 + + NewButton { + id: sliceButton + Layout.fillHeight: true + Layout.fillWidth: true + + text: "Slice" + + onClicked: { + if (volumeItem.sliceIndexZ == -1) { + volumeItem.sliceIndexZ = 128 + volumeItem.drawSlices = true + volumeItem.drawSliceFrames = true + } else { + volumeItem.sliceIndexZ = -1 + volumeItem.drawSlices = false + volumeItem.drawSliceFrames = false + } + } + } + NewButton { + id: exitButton + Layout.fillHeight: true + Layout.fillWidth: true + + text: "Quit" + + onClicked: Qt.quit(0); + } + } + } + + } + + Custom3DVolume { + id: volumeItem + alphaMultiplier: 0.3 + preserveOpacity: true + useHighDefShader: false + } + + function createVolume() { + surfaceGraph.addCustomItem(volumeItem) + dataSource.fillVolume(volumeItem) + } +} diff --git a/tests/qmlvolume/qmlvolume.pro b/tests/qmlvolume/qmlvolume.pro new file mode 100644 index 00000000..1d58b668 --- /dev/null +++ b/tests/qmlvolume/qmlvolume.pro @@ -0,0 +1,16 @@ +!include( ../tests.pri ) { + error( "Couldn't find the tests.pri file!" ) +} + +QT += datavisualization + +# The .cpp file which was generated for your project. Feel free to hack it. +SOURCES += main.cpp \ + datasource.cpp +HEADERS += datasource.h + +RESOURCES += qmlvolume.qrc + +OTHER_FILES += doc/src/* \ + doc/images/* \ + qml/qmlvolume/* diff --git a/tests/qmlvolume/qmlvolume.qrc b/tests/qmlvolume/qmlvolume.qrc new file mode 100644 index 00000000..18fe57e1 --- /dev/null +++ b/tests/qmlvolume/qmlvolume.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/"> + <file>qml/qmlvolume/main.qml</file> + <file>qml/qmlvolume/NewButton.qml</file> + </qresource> +</RCC> diff --git a/tests/scattertest/main.cpp b/tests/scattertest/main.cpp index 207da530..9b6f51ac 100644 --- a/tests/scattertest/main.cpp +++ b/tests/scattertest/main.cpp @@ -36,11 +36,13 @@ int main(int argc, char **argv) { QApplication app(argc, argv); + //QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); QWidget *widget = new QWidget; QHBoxLayout *hLayout = new QHBoxLayout(widget); QVBoxLayout *vLayout = new QVBoxLayout(); QVBoxLayout *vLayout2 = new QVBoxLayout(); + QVBoxLayout *vLayout3 = new QVBoxLayout(); Q3DScatter *chart = new Q3DScatter(); QSize screenSize = chart->screen()->size(); @@ -56,6 +58,7 @@ int main(int argc, char **argv) hLayout->addWidget(container, 1); hLayout->addLayout(vLayout); hLayout->addLayout(vLayout2); + hLayout->addLayout(vLayout3); QPushButton *themeButton = new QPushButton(widget); themeButton->setText(QStringLiteral("Change theme")); @@ -235,6 +238,25 @@ int main(int argc, char **argv) aspectRatioSlider->setValue(20); aspectRatioSlider->setMaximum(100); + QSlider *horizontalAspectRatioSlider = new QSlider(Qt::Horizontal, widget); + horizontalAspectRatioSlider->setTickInterval(30); + horizontalAspectRatioSlider->setTickPosition(QSlider::TicksBelow); + horizontalAspectRatioSlider->setMinimum(0); + horizontalAspectRatioSlider->setValue(0); + horizontalAspectRatioSlider->setMaximum(300); + + QCheckBox *optimizationStaticCB = new QCheckBox(widget); + optimizationStaticCB->setText(QStringLiteral("Static optimization")); + optimizationStaticCB->setChecked(false); + + QCheckBox *orthoCB = new QCheckBox(widget); + orthoCB->setText(QStringLiteral("Orthographic projection")); + orthoCB->setChecked(false); + + QCheckBox *polarCB = new QCheckBox(widget); + polarCB->setText(QStringLiteral("Polar graph")); + polarCB->setChecked(false); + QCheckBox *axisTitlesVisibleCB = new QCheckBox(widget); axisTitlesVisibleCB->setText(QStringLiteral("Axis titles visible")); axisTitlesVisibleCB->setChecked(false); @@ -250,6 +272,34 @@ int main(int argc, char **argv) axisLabelRotationSlider->setValue(0); axisLabelRotationSlider->setMaximum(90); + QSlider *radialLabelSlider = new QSlider(Qt::Horizontal, widget); + radialLabelSlider->setTickInterval(10); + radialLabelSlider->setTickPosition(QSlider::TicksBelow); + radialLabelSlider->setMinimum(0); + radialLabelSlider->setValue(100); + radialLabelSlider->setMaximum(150); + + QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderX->setTickInterval(1); + cameraTargetSliderX->setMinimum(-100); + cameraTargetSliderX->setValue(0); + cameraTargetSliderX->setMaximum(100); + QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderY->setTickInterval(1); + cameraTargetSliderY->setMinimum(-100); + cameraTargetSliderY->setValue(0); + cameraTargetSliderY->setMaximum(100); + QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderZ->setTickInterval(1); + cameraTargetSliderZ->setMinimum(-100); + cameraTargetSliderZ->setValue(0); + cameraTargetSliderZ->setMaximum(100); + + QSlider *marginSlider = new QSlider(Qt::Horizontal, widget); + marginSlider->setMinimum(-1); + marginSlider->setValue(-1); + marginSlider->setMaximum(100); + vLayout->addWidget(themeButton, 0, Qt::AlignTop); vLayout->addWidget(labelButton, 0, Qt::AlignTop); vLayout->addWidget(styleButton, 0, Qt::AlignTop); @@ -296,12 +346,26 @@ int main(int argc, char **argv) vLayout2->addWidget(fontList); vLayout2->addWidget(new QLabel(QStringLiteral("Adjust font size"))); vLayout2->addWidget(fontSizeSlider); - vLayout2->addWidget(new QLabel(QStringLiteral("Adjust aspect ratio"))); - vLayout2->addWidget(aspectRatioSlider, 1, Qt::AlignTop); - vLayout2->addWidget(axisTitlesVisibleCB); - vLayout2->addWidget(axisTitlesFixedCB); - vLayout2->addWidget(new QLabel(QStringLiteral("Axis label rotation"))); - vLayout2->addWidget(axisLabelRotationSlider, 1, Qt::AlignTop); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust vertical aspect ratio"))); + vLayout2->addWidget(aspectRatioSlider); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust horizontal aspect ratio"))); + vLayout2->addWidget(horizontalAspectRatioSlider, 1, Qt::AlignTop); + + vLayout3->addWidget(optimizationStaticCB); + vLayout3->addWidget(orthoCB); + vLayout3->addWidget(polarCB); + vLayout3->addWidget(axisTitlesVisibleCB); + vLayout3->addWidget(axisTitlesFixedCB); + vLayout3->addWidget(new QLabel(QStringLiteral("Axis label rotation"))); + vLayout3->addWidget(axisLabelRotationSlider); + vLayout3->addWidget(new QLabel(QStringLiteral("Radial label offset"))); + vLayout3->addWidget(radialLabelSlider, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Camera target")), 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderX, 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderY, 0, Qt::AlignTop); + vLayout3->addWidget(cameraTargetSliderZ, 0, Qt::AlignTop); + vLayout3->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop); + vLayout3->addWidget(marginSlider, 1, Qt::AlignTop); ScatterDataModifier *modifier = new ScatterDataModifier(chart); @@ -389,6 +453,12 @@ int main(int argc, char **argv) &ScatterDataModifier::setMaxY); QObject::connect(maxSliderZ, &QSlider::valueChanged, modifier, &ScatterDataModifier::setMaxZ); + QObject::connect(optimizationStaticCB, &QCheckBox::stateChanged, modifier, + &ScatterDataModifier::toggleStatic); + QObject::connect(orthoCB, &QCheckBox::stateChanged, modifier, + &ScatterDataModifier::toggleOrtho); + QObject::connect(polarCB, &QCheckBox::stateChanged, modifier, + &ScatterDataModifier::togglePolar); QObject::connect(axisTitlesVisibleCB, &QCheckBox::stateChanged, modifier, &ScatterDataModifier::toggleAxisTitleVisibility); QObject::connect(axisTitlesFixedCB, &QCheckBox::stateChanged, modifier, @@ -397,13 +467,25 @@ int main(int argc, char **argv) &ScatterDataModifier::changeLabelRotation); QObject::connect(aspectRatioSlider, &QSlider::valueChanged, modifier, &ScatterDataModifier::setAspectRatio); + QObject::connect(horizontalAspectRatioSlider, &QSlider::valueChanged, modifier, + &ScatterDataModifier::setHorizontalAspectRatio); + QObject::connect(radialLabelSlider, &QSlider::valueChanged, modifier, + &ScatterDataModifier::changeRadialLabelOffset); + QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier, + &ScatterDataModifier::setCameraTargetX); + QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier, + &ScatterDataModifier::setCameraTargetY); + QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier, + &ScatterDataModifier::setCameraTargetZ); + QObject::connect(marginSlider, &QSlider::valueChanged, modifier, + &ScatterDataModifier::setGraphMargin); modifier->setFpsLabel(fpsLabel); chart->setGeometry(QRect(0, 0, 800, 800)); modifier->start(); - modifier->renderToImage(); // Initial hidden render + //modifier->renderToImage(); // Initial hidden render widget->show(); diff --git a/tests/scattertest/scatterchart.cpp b/tests/scattertest/scatterchart.cpp index c00c526a..7d625d3f 100644 --- a/tests/scattertest/scatterchart.cpp +++ b/tests/scattertest/scatterchart.cpp @@ -23,6 +23,7 @@ #include <QtDataVisualization/q3dscene.h> #include <QtDataVisualization/q3dcamera.h> #include <QtDataVisualization/q3dtheme.h> +#include <QtDataVisualization/Q3DInputHandler> #include <qmath.h> using namespace QtDataVisualization; @@ -46,6 +47,8 @@ ScatterDataModifier::ScatterDataModifier(Q3DScatter *scatter) m_chart->setAxisX(new QValue3DAxis); m_chart->setAxisY(new QValue3DAxis); m_chart->setAxisZ(new QValue3DAxis); + m_chart->axisY()->setLabelFormat(QStringLiteral("%.7f")); + static_cast<Q3DInputHandler *>(m_chart->activeInputHandler())->setZoomAtTargetEnabled(true); createAndAddSeries(); createAndAddSeries(); @@ -435,6 +438,10 @@ void ScatterDataModifier::testAxisReverse() m_chart->axisX()->setRange(0.0f, 10.0f); m_chart->axisY()->setRange(-20.0f, 50.0f); m_chart->axisZ()->setRange(5.0f, 15.0f); + m_chart->axisX()->setTitle("Axis X"); + m_chart->axisZ()->setTitle("Axis Z"); + m_chart->axisX()->setTitleVisible(true); + m_chart->axisZ()->setTitleVisible(true); m_chart->addSeries(series0); m_chart->addSeries(series1); } @@ -484,9 +491,9 @@ void ScatterDataModifier::addData() m_chart->axisX()->setRange(-50.0f, 50.0f); m_chart->axisY()->setRange(-1.0f, 1.2f); m_chart->axisZ()->setRange(-50.0f, 50.0f); - m_chart->axisX()->setSegmentCount(6); + m_chart->axisX()->setSegmentCount(5); m_chart->axisY()->setSegmentCount(4); - m_chart->axisZ()->setSegmentCount(9); + m_chart->axisZ()->setSegmentCount(10); m_chart->axisX()->setSubSegmentCount(2); m_chart->axisY()->setSubSegmentCount(3); m_chart->axisZ()->setSubSegmentCount(1); @@ -717,8 +724,24 @@ void ScatterDataModifier::changeBunch() if (m_targetSeries->dataProxy()->array()->size()) { int amount = qMin(m_targetSeries->dataProxy()->array()->size(), 100); QScatterDataArray items(amount); - for (int i = 0; i < items.size(); i++) + for (int i = 0; i < items.size(); i++) { items[i].setPosition(randVector()); + // Change the Y-values of first few items to exact gradient boundaries + if (i == 0) + items[i].setY(0.65f); + else if (i == 1) + items[i].setY(0.1f); + else if (i == 2) + items[i].setY(-0.45f); + else if (i == 3) + items[i].setY(-1.0f); + else if (i == 4) + items[i].setY(1.2f); +// else +// items[i].setY(0.1001f - (0.00001f * float(i))); + + } + m_targetSeries->dataProxy()->setItems(0, items); qDebug() << m_loopCounter << "Changed bunch, array size:" << m_targetSeries->dataProxy()->array()->size(); } @@ -934,6 +957,11 @@ void ScatterDataModifier::changeLabelRotation(int rotation) m_chart->axisZ()->setLabelAutoRotation(float(rotation)); } +void ScatterDataModifier::changeRadialLabelOffset(int offset) +{ + m_chart->setRadialLabelOffset(float(offset) / 100.0f); +} + void ScatterDataModifier::toggleAxisTitleVisibility(bool enabled) { m_chart->axisX()->setTitleVisible(enabled); @@ -970,6 +998,54 @@ void ScatterDataModifier::renderToImage() } } +void ScatterDataModifier::togglePolar(bool enable) +{ + m_chart->setPolar(enable); +} + +void ScatterDataModifier::toggleStatic(bool enable) +{ + if (enable) + m_chart->setOptimizationHints(QAbstract3DGraph::OptimizationStatic); + else + m_chart->setOptimizationHints(QAbstract3DGraph::OptimizationDefault); +} + +void ScatterDataModifier::toggleOrtho(bool enable) +{ + m_chart->setOrthoProjection(enable); +} + +void ScatterDataModifier::setCameraTargetX(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setX(float(value) / 100.0f); + m_chart->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void ScatterDataModifier::setCameraTargetY(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setY(float(value) / 100.0f); + m_chart->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void ScatterDataModifier::setCameraTargetZ(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setZ(float(value) / 100.0f); + m_chart->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void ScatterDataModifier::setGraphMargin(int value) +{ + m_chart->setMargin(qreal(value) / 100.0); + qDebug() << "Setting margin:" << m_chart->margin() << value; +} + void ScatterDataModifier::changeShadowQuality(int quality) { QAbstract3DGraph::ShadowQuality sq = QAbstract3DGraph::ShadowQuality(quality); @@ -1019,10 +1095,16 @@ void ScatterDataModifier::setMaxZ(int max) void ScatterDataModifier::setAspectRatio(int ratio) { - float aspectRatio = float(ratio) / 10.0f; + qreal aspectRatio = qreal(ratio) / 10.0; m_chart->setAspectRatio(aspectRatio); } +void ScatterDataModifier::setHorizontalAspectRatio(int ratio) +{ + qreal aspectRatio = qreal(ratio) / 100.0; + m_chart->setHorizontalAspectRatio(aspectRatio); +} + QVector3D ScatterDataModifier::randVector() { QVector3D retvec = QVector3D( diff --git a/tests/scattertest/scatterchart.h b/tests/scattertest/scatterchart.h index 97c3b1f9..a2b0f58e 100644 --- a/tests/scattertest/scatterchart.h +++ b/tests/scattertest/scatterchart.h @@ -53,6 +53,7 @@ public: void setMaxY(int max); void setMaxZ(int max); void setAspectRatio(int ratio); + void setHorizontalAspectRatio(int ratio); void start(); void massiveDataTest(); void massiveTestScroll(); @@ -90,9 +91,17 @@ public slots: void handleAxisZChanged(QValue3DAxis *axis); void handleFpsChange(qreal fps); void changeLabelRotation(int rotation); + void changeRadialLabelOffset(int offset); void toggleAxisTitleVisibility(bool enabled); void toggleAxisTitleFixed(bool enabled); void renderToImage(); + void togglePolar(bool enable); + void toggleStatic(bool enable); + void toggleOrtho(bool enable); + void setCameraTargetX(int value); + void setCameraTargetY(int value); + void setCameraTargetZ(int value); + void setGraphMargin(int value); signals: void shadowQualityChanged(int quality); @@ -113,6 +122,8 @@ private: QScatter3DSeries *m_targetSeries; QScatterDataArray m_massiveTestCacheArray; QLabel *m_fpsLabel; + QVector3D m_cameraTarget; + }; #endif diff --git a/tests/surfacetest/graphmodifier.cpp b/tests/surfacetest/graphmodifier.cpp index ed86f03c..7192f0dc 100644 --- a/tests/surfacetest/graphmodifier.cpp +++ b/tests/surfacetest/graphmodifier.cpp @@ -21,18 +21,21 @@ #include <QtDataVisualization/QSurfaceDataProxy> #include <QtDataVisualization/QSurface3DSeries> #include <QtDataVisualization/Q3DTheme> +#include <QtDataVisualization/Q3DInputHandler> #include <qmath.h> #include <QLinearGradient> #include <QDebug> #include <QComboBox> - +#ifndef QT_NO_CURSOR +#include <QtGui/QCursor> +#endif using namespace QtDataVisualization; //#define JITTER_PLANE //#define WONKY_PLANE -GraphModifier::GraphModifier(Q3DSurface *graph) +GraphModifier::GraphModifier(Q3DSurface *graph, QWidget *parentWidget) : m_graph(graph), m_series1(new QSurface3DSeries), m_series2(new QSurface3DSeries), @@ -48,12 +51,12 @@ GraphModifier::GraphModifier(Q3DSurface *graph) m_zCount(24), m_activeSample(0), m_fontSize(40), - m_rangeX(16.0), + m_rangeX(34.0), m_rangeY(16.0), - m_rangeZ(16.0), - m_minX(-8.0), + m_rangeZ(34.0), + m_minX(-17.0), m_minY(-8.0), - m_minZ(-8.0), + m_minZ(-17.0), m_addRowCounter(m_zCount), m_insertTestZPos(0), m_insertTestIndexPos(1), @@ -63,7 +66,10 @@ GraphModifier::GraphModifier(Q3DSurface *graph) m_drawMode2(QSurface3DSeries::DrawSurfaceAndWireframe), m_drawMode3(QSurface3DSeries::DrawSurfaceAndWireframe), m_drawMode4(QSurface3DSeries::DrawSurfaceAndWireframe), - m_offset(4.0f) + m_offset(4.0f), + m_parentWidget(parentWidget), + m_ascendingX(true), + m_ascendingZ(true) { m_graph->setAxisX(new QValue3DAxis); m_graph->axisX()->setTitle("X-Axis"); @@ -97,6 +103,8 @@ GraphModifier::GraphModifier(Q3DSurface *graph) m_graph->axisY()->setRange(m_minY, m_minY + m_rangeY); m_graph->axisZ()->setRange(m_minZ, m_minZ + m_rangeZ); + static_cast<Q3DInputHandler *>(m_graph->activeInputHandler())->setZoomAtTargetEnabled(true); + for (int i = 0; i < 4; i++) { m_multiseries[i] = new QSurface3DSeries; m_multiseries[i]->setName(QStringLiteral("Series %1").arg(i+1)); @@ -109,6 +117,7 @@ GraphModifier::GraphModifier(Q3DSurface *graph) m_theSeries->setItemLabelFormat(QStringLiteral("@seriesName: (@xLabel, @zLabel): @yLabel")); connect(&m_timer, &QTimer::timeout, this, &GraphModifier::timeout); + connect(&m_graphPositionQueryTimer, &QTimer::timeout, this, &GraphModifier::graphQueryTimeout); connect(m_theSeries, &QSurface3DSeries::selectedPointChanged, this, &GraphModifier::selectedPointChanged); QObject::connect(m_graph, &Q3DSurface::axisXChanged, this, @@ -119,6 +128,8 @@ GraphModifier::GraphModifier(Q3DSurface *graph) &GraphModifier::handleAxisZChanged); QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, &GraphModifier::handleFpsChange); + + //m_graphPositionQueryTimer.start(100); } GraphModifier::~GraphModifier() @@ -159,7 +170,6 @@ void GraphModifier::fillSeries() (*newRow[s])[j].setPosition(QVector3D(x, y, z)); } } - qDebug() << newRow[0]->at(0).z(); *dataArray1 << newRow[0]; *dataArray2 << newRow[1]; *dataArray3 << newRow[2]; @@ -606,19 +616,30 @@ void GraphModifier::adjustZMin(int min) void GraphModifier::gradientPressed() { + static Q3DTheme::ColorStyle colorStyle = Q3DTheme::ColorStyleUniform; + + if (colorStyle == Q3DTheme::ColorStyleRangeGradient) { + colorStyle = Q3DTheme::ColorStyleObjectGradient; + qDebug() << "Color style: ColorStyleObjectGradient"; + } else if (colorStyle == Q3DTheme::ColorStyleObjectGradient) { + colorStyle = Q3DTheme::ColorStyleUniform; + qDebug() << "Color style: ColorStyleUniform"; + } else { + colorStyle = Q3DTheme::ColorStyleRangeGradient; + qDebug() << "Color style: ColorStyleRangeGradient"; + } + QLinearGradient gradient; gradient.setColorAt(0.0, Qt::black); gradient.setColorAt(0.33, Qt::blue); gradient.setColorAt(0.67, Qt::red); gradient.setColorAt(1.0, Qt::yellow); -// m_graph->seriesList().at(0)->setBaseGradient(gradient); -// m_graph->seriesList().at(0)->setSingleHighlightColor(Qt::red); -// m_graph->seriesList().at(0)->setColorStyle(Q3DTheme::ColorStyleRangeGradient); QList<QLinearGradient> gradients; gradients << gradient; m_graph->activeTheme()->setBaseGradients(gradients); - m_graph->activeTheme()->setColorStyle(Q3DTheme::ColorStyleRangeGradient); + m_graph->activeTheme()->setColorStyle(colorStyle); + } void GraphModifier::changeFont(const QFont &font) @@ -680,6 +701,16 @@ void GraphModifier::timeout() m_theSeries->dataProxy()->resetArray(m_planeArray); } +void GraphModifier::graphQueryTimeout() +{ +#ifndef QT_NO_CURSOR + m_graph->scene()->setGraphPositionQuery(m_parentWidget->mapFromGlobal(QCursor::pos())); + qDebug() << "pos: " << (m_parentWidget->mapFromGlobal(QCursor::pos())); +#else + m_graph->scene()->setGraphPositionQuery(QPoint(100, 100)); +#endif +} + void GraphModifier::handleAxisXChanged(QValue3DAxis *axis) { qDebug() << __FUNCTION__ << axis << axis->orientation() << (axis == m_graph->axisX()); @@ -723,6 +754,8 @@ void GraphModifier::toggleAxisTitleFixed(bool enabled) void GraphModifier::toggleXAscending(bool enabled) { + m_ascendingX = enabled; + // Flip data array contents if necessary foreach (QSurface3DSeries *series, m_graph->seriesList()) { QSurfaceDataArray *array = const_cast<QSurfaceDataArray *>(series->dataProxy()->array()); @@ -751,6 +784,8 @@ void GraphModifier::toggleXAscending(bool enabled) void GraphModifier::toggleZAscending(bool enabled) { + m_ascendingZ = enabled; + // Flip data array contents if necessary foreach (QSurface3DSeries *series, m_graph->seriesList()) { QSurfaceDataArray *array = const_cast<QSurfaceDataArray *>(series->dataProxy()->array()); @@ -777,6 +812,41 @@ void GraphModifier::toggleZAscending(bool enabled) } } +void GraphModifier::togglePolar(bool enabled) +{ + m_graph->setPolar(enabled); +} + +void GraphModifier::setCameraTargetX(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setX(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setCameraTargetY(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setY(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setCameraTargetZ(int value) +{ + // Value is (-100, 100), normalize + m_cameraTarget.setZ(float(value) / 100.0f); + m_graph->scene()->activeCamera()->setTarget(m_cameraTarget); + qDebug() << "m_cameraTarget:" << m_cameraTarget; +} + +void GraphModifier::setGraphMargin(int value) +{ + m_graph->setMargin(qreal(value) / 100.0); + qDebug() << "Setting margin:" << m_graph->margin() << value; +} + void GraphModifier::resetArrayAndSliders(QSurfaceDataArray *array, float minZ, float maxZ, float minX, float maxX) { m_axisMinSliderX->setValue(minX); @@ -831,7 +901,10 @@ void GraphModifier::changeRow() int row = rand() % m_zCount; QSurfaceDataRow *newRow = createMultiRow(row, changeRowSeries, true); - m_multiseries[changeRowSeries]->dataProxy()->setRow(row, newRow); + if (m_ascendingZ) + m_multiseries[changeRowSeries]->dataProxy()->setRow(row, newRow); + else + m_multiseries[changeRowSeries]->dataProxy()->setRow((m_zCount - 1) - row, newRow); changeRowSeries++; if (changeRowSeries > 3) @@ -848,11 +921,20 @@ QSurfaceDataRow *GraphModifier::createMultiRow(int row, int series, bool change) float i = float(row); QSurfaceDataRow *newRow = new QSurfaceDataRow(m_xCount); float z = float(i) - m_limitZ + 0.5f + m_multiSampleOffsetZ[series]; - for (int j = 0; j < m_xCount; j++) { - float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series]; - float angle = (z * x) / float(full) * 1.57f; - float y = qSin(angle * float(qPow(1.3f, series))) + 0.2f * float(change) + 1.1f *series; - (*newRow)[j].setPosition(QVector3D(x, y, z)); + if (m_ascendingX) { + for (int j = 0; j < m_xCount; j++) { + float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series]; + float angle = (z * x) / float(full) * 1.57f; + float y = qSin(angle * float(qPow(1.3f, series))) + 0.2f * float(change) + 1.1f *series; + (*newRow)[j].setPosition(QVector3D(x, y, z)); + } + } else { + for (int j = m_xCount - 1; j >= 0 ; j--) { + float x = float(j) - m_limitX + 0.5f + m_multiSampleOffsetX[series]; + float angle = (z * x) / float(full) * 1.57f; + float y = qSin(angle * float(qPow(1.3f, series))) + 0.2f * float(change) + 1.1f *series; + (*newRow)[(m_xCount - 1) - j].setPosition(QVector3D(x, y, z)); + } } return newRow; @@ -961,7 +1043,16 @@ void GraphModifier::changeItem() float angle = (z * x) / float(full) * 1.57f; float y = qSin(angle * float(qPow(1.3f, changeItemSeries))) + 0.2f + 1.1f *changeItemSeries; QSurfaceDataItem newItem(QVector3D(x, y, z)); - m_multiseries[changeItemSeries]->dataProxy()->setItem(int(i), int(j), newItem); + + if (m_ascendingZ && m_ascendingX) + m_multiseries[changeItemSeries]->dataProxy()->setItem(int(i), int(j), newItem); + else if (!m_ascendingZ && m_ascendingX) + m_multiseries[changeItemSeries]->dataProxy()->setItem(m_zCount - 1 - int(i), int(j), newItem); + else if (m_ascendingZ && !m_ascendingX) + m_multiseries[changeItemSeries]->dataProxy()->setItem(int(i), m_xCount - 1 - int(j), newItem); + else + m_multiseries[changeItemSeries]->dataProxy()->setItem(m_zCount - 1 - int(i), m_xCount - 1 - int(j), newItem); + //m_multiseries[changeItemSeries]->setSelectedPoint(QPoint(i, j)); changeItemSeries++; if (changeItemSeries > 3) changeItemSeries = 0; @@ -1228,12 +1319,12 @@ void GraphModifier::resetArray() void GraphModifier::resetArrayEmpty() { - QSurfaceDataArray *emptryArray = new QSurfaceDataArray; + QSurfaceDataArray *emptyArray = new QSurfaceDataArray; #ifdef MULTI_SERIES int series = rand() % 4; - m_multiseries[series]->dataProxy()->resetArray(emptryArray); + m_multiseries[series]->dataProxy()->resetArray(emptyArray); #else - m_theSeries->dataProxy()->resetArray(emptryArray); + m_theSeries->dataProxy()->resetArray(emptyArray); #endif } @@ -1569,6 +1660,20 @@ void GraphModifier::updateSamples() void GraphModifier::setAspectRatio(int ratio) { - float aspectRatio = float(ratio) / 10.0f; + qreal aspectRatio = qreal(ratio) / 10.0; m_graph->setAspectRatio(aspectRatio); } + +void GraphModifier::setHorizontalAspectRatio(int ratio) +{ + qreal aspectRatio = qreal(ratio) / 100.0; + m_graph->setHorizontalAspectRatio(aspectRatio); +} + +void GraphModifier::setSurfaceTexture(bool enabled) +{ + if (enabled) + m_multiseries[3]->setTexture(QImage(":/maps/mapimage")); + else + m_multiseries[3]->setTexture(QImage()); +} diff --git a/tests/surfacetest/graphmodifier.h b/tests/surfacetest/graphmodifier.h index 5f1a252a..223a6854 100644 --- a/tests/surfacetest/graphmodifier.h +++ b/tests/surfacetest/graphmodifier.h @@ -41,7 +41,7 @@ public: Map }; - explicit GraphModifier(Q3DSurface *graph); + explicit GraphModifier(Q3DSurface *graph, QWidget *parentWidget); ~GraphModifier(); void toggleSeries1(bool enabled); @@ -112,8 +112,9 @@ public: void massiveTestAppendAndScroll(); void testAxisReverse(); void testDataOrdering(); - void setAspectRatio(int ratio); + void setHorizontalAspectRatio(int ratio); + void setSurfaceTexture(bool enabled); public slots: void changeShadowQuality(int quality); @@ -121,6 +122,7 @@ public slots: void flipViews(); void changeSelectionMode(int mode); void timeout(); + void graphQueryTimeout(); void handleAxisXChanged(QValue3DAxis *axis); void handleAxisYChanged(QValue3DAxis *axis); @@ -131,6 +133,11 @@ public slots: void toggleAxisTitleFixed(bool enabled); void toggleXAscending(bool enabled); void toggleZAscending(bool enabled); + void togglePolar(bool enabled); + void setCameraTargetX(int value); + void setCameraTargetY(int value); + void setCameraTargetZ(int value); + void setGraphMargin(int value); private: void fillSeries(); @@ -184,6 +191,11 @@ private: float m_multiSampleOffsetX[4]; float m_multiSampleOffsetZ[4]; QSurfaceDataArray m_massiveTestCacheArray; + QVector3D m_cameraTarget; + QWidget *m_parentWidget; + QTimer m_graphPositionQueryTimer; + bool m_ascendingX; + bool m_ascendingZ; }; #endif diff --git a/tests/surfacetest/main.cpp b/tests/surfacetest/main.cpp index 5806d7b0..d1328e4e 100644 --- a/tests/surfacetest/main.cpp +++ b/tests/surfacetest/main.cpp @@ -46,8 +46,10 @@ int main(int argc, char *argv[]) QHBoxLayout *hLayout = new QHBoxLayout(widget); QVBoxLayout *vLayout = new QVBoxLayout(); QVBoxLayout *vLayout2 = new QVBoxLayout(); + QVBoxLayout *vLayout3 = new QVBoxLayout(); vLayout->setAlignment(Qt::AlignTop); vLayout2->setAlignment(Qt::AlignTop); + vLayout3->setAlignment(Qt::AlignTop); Q3DSurface *surfaceGraph = new Q3DSurface(); QSize screenSize = surfaceGraph->screen()->size(); @@ -56,7 +58,7 @@ int main(int argc, char *argv[]) surfaceGraph->activeTheme()->setType(Q3DTheme::Theme(initialTheme)); QWidget *container = QWidget::createWindowContainer(surfaceGraph); - container->setMinimumSize(QSize(screenSize.width() / 4, screenSize.height() / 4)); + container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 4)); container->setMaximumSize(screenSize); container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); container->setFocusPolicy(Qt::StrongFocus); @@ -66,6 +68,7 @@ int main(int argc, char *argv[]) hLayout->addWidget(container, 1); hLayout->addLayout(vLayout); hLayout->addLayout(vLayout2); + hLayout->addLayout(vLayout3); QCheckBox *smoothCB = new QCheckBox(widget); smoothCB->setText(QStringLiteral("Flat Surface")); @@ -183,7 +186,7 @@ int main(int argc, char *argv[]) QSlider *axisRangeSliderX = new QSlider(Qt::Horizontal, widget); axisRangeSliderX->setTickInterval(1); axisRangeSliderX->setMinimum(1); - axisRangeSliderX->setValue(16); + axisRangeSliderX->setValue(34); axisRangeSliderX->setMaximum(100); axisRangeSliderX->setEnabled(true); QSlider *axisRangeSliderY = new QSlider(Qt::Horizontal, widget); @@ -195,14 +198,14 @@ int main(int argc, char *argv[]) QSlider *axisRangeSliderZ = new QSlider(Qt::Horizontal, widget); axisRangeSliderZ->setTickInterval(1); axisRangeSliderZ->setMinimum(1); - axisRangeSliderZ->setValue(16); + axisRangeSliderZ->setValue(34); axisRangeSliderZ->setMaximum(100); axisRangeSliderZ->setEnabled(true); QSlider *axisMinSliderX = new QSlider(Qt::Horizontal, widget); axisMinSliderX->setTickInterval(1); axisMinSliderX->setMinimum(-100); - axisMinSliderX->setValue(-8); + axisMinSliderX->setValue(-17); axisMinSliderX->setMaximum(100); axisMinSliderX->setEnabled(true); QSlider *axisMinSliderY = new QSlider(Qt::Horizontal, widget); @@ -214,7 +217,7 @@ int main(int argc, char *argv[]) QSlider *axisMinSliderZ = new QSlider(Qt::Horizontal, widget); axisMinSliderZ->setTickInterval(1); axisMinSliderZ->setMinimum(-100); - axisMinSliderZ->setValue(-8); + axisMinSliderZ->setValue(-17); axisMinSliderZ->setMaximum(100); axisMinSliderZ->setEnabled(true); @@ -223,6 +226,11 @@ int main(int argc, char *argv[]) aspectRatioSlider->setValue(20); aspectRatioSlider->setMaximum(100); + QSlider *horizontalAspectRatioSlider = new QSlider(Qt::Horizontal, widget); + horizontalAspectRatioSlider->setMinimum(0); + horizontalAspectRatioSlider->setValue(0); + horizontalAspectRatioSlider->setMaximum(300); + QLinearGradient gr(0, 0, 100, 1); gr.setColorAt(0.0, Qt::black); gr.setColorAt(0.33, Qt::blue); @@ -393,6 +401,35 @@ int main(int argc, char *argv[]) zAscendingCB->setText(QStringLiteral("Z Ascending")); zAscendingCB->setChecked(true); + QCheckBox *polarCB = new QCheckBox(widget); + polarCB->setText(QStringLiteral("Polar")); + polarCB->setChecked(false); + + QCheckBox *surfaceTextureCB = new QCheckBox(widget); + surfaceTextureCB->setText(QStringLiteral("Map texture")); + surfaceTextureCB->setChecked(false); + + QSlider *cameraTargetSliderX = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderX->setTickInterval(1); + cameraTargetSliderX->setMinimum(-100); + cameraTargetSliderX->setValue(0); + cameraTargetSliderX->setMaximum(100); + QSlider *cameraTargetSliderY = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderY->setTickInterval(1); + cameraTargetSliderY->setMinimum(-100); + cameraTargetSliderY->setValue(0); + cameraTargetSliderY->setMaximum(100); + QSlider *cameraTargetSliderZ = new QSlider(Qt::Horizontal, widget); + cameraTargetSliderZ->setTickInterval(1); + cameraTargetSliderZ->setMinimum(-100); + cameraTargetSliderZ->setValue(0); + cameraTargetSliderZ->setMaximum(100); + + QSlider *marginSlider = new QSlider(Qt::Horizontal, widget); + marginSlider->setMinimum(-1); + marginSlider->setValue(-1); + marginSlider->setMaximum(100); + // Add controls to the layout #ifdef MULTI_SERIES vLayout->addWidget(series1CB); @@ -420,6 +457,7 @@ int main(int argc, char *argv[]) vLayout->addWidget(surfaceGridS4CB); vLayout->addWidget(surfaceS4CB); vLayout->addWidget(series4VisibleCB); + vLayout->addWidget(surfaceTextureCB, 1, Qt::AlignTop); #endif #ifndef MULTI_SERIES vLayout->addWidget(new QLabel(QStringLiteral("Select surface sample"))); @@ -429,60 +467,71 @@ int main(int argc, char *argv[]) vLayout->addWidget(new QLabel(QStringLiteral("Adjust sample count"))); vLayout->addWidget(gridSlidersLockCB); vLayout->addWidget(gridSliderX); - vLayout->addWidget(gridSliderZ); + vLayout->addWidget(gridSliderZ, 1, Qt::AlignTop); #endif - vLayout->addWidget(new QLabel(QStringLiteral("Adjust aspect ratio"))); - vLayout->addWidget(aspectRatioSlider); - vLayout->addWidget(new QLabel(QStringLiteral("Adjust axis range"))); - vLayout->addWidget(axisRangeSliderX); - vLayout->addWidget(axisRangeSliderY); - vLayout->addWidget(axisRangeSliderZ); - vLayout->addWidget(new QLabel(QStringLiteral("Adjust axis minimum"))); - vLayout->addWidget(axisMinSliderX); - vLayout->addWidget(axisMinSliderY); - vLayout->addWidget(axisMinSliderZ); - vLayout->addWidget(xAscendingCB); - vLayout->addWidget(zAscendingCB); + + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust vertical aspect ratio"))); + vLayout2->addWidget(aspectRatioSlider); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust horizontal aspect ratio"))); + vLayout2->addWidget(horizontalAspectRatioSlider); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust axis range"))); + vLayout2->addWidget(axisRangeSliderX); + vLayout2->addWidget(axisRangeSliderY); + vLayout2->addWidget(axisRangeSliderZ); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust axis minimum"))); + vLayout2->addWidget(axisMinSliderX); + vLayout2->addWidget(axisMinSliderY); + vLayout2->addWidget(axisMinSliderZ); + vLayout2->addWidget(xAscendingCB); + vLayout2->addWidget(zAscendingCB); + vLayout2->addWidget(polarCB); vLayout2->addWidget(new QLabel(QStringLiteral("Change font"))); vLayout2->addWidget(fontList); - vLayout2->addWidget(labelButton); - vLayout2->addWidget(meshButton); vLayout2->addWidget(new QLabel(QStringLiteral("Change theme"))); vLayout2->addWidget(themeList); vLayout2->addWidget(new QLabel(QStringLiteral("Adjust shadow quality"))); vLayout2->addWidget(shadowQuality); vLayout2->addWidget(new QLabel(QStringLiteral("Selection Mode"))); vLayout2->addWidget(selectionMode); + vLayout2->addWidget(new QLabel(QStringLiteral("Camera target"))); + vLayout2->addWidget(cameraTargetSliderX); + vLayout2->addWidget(cameraTargetSliderY); + vLayout2->addWidget(cameraTargetSliderZ); + vLayout2->addWidget(new QLabel(QStringLiteral("Adjust margin")), 0, Qt::AlignTop); + vLayout2->addWidget(marginSlider, 1, Qt::AlignTop); + + vLayout3->addWidget(labelButton); + vLayout3->addWidget(meshButton); #ifndef MULTI_SERIES - vLayout2->addWidget(selectButton); - vLayout2->addWidget(selectionInfoLabel); - vLayout2->addWidget(flipViewsButton); + vLayout3->addWidget(selectButton); + vLayout3->addWidget(selectionInfoLabel); + vLayout3->addWidget(flipViewsButton); #endif - vLayout2->addWidget(colorPB); - vLayout2->addWidget(changeRowButton); - vLayout2->addWidget(changeRowsButton); - vLayout2->addWidget(changeMultipleRowsButton); - vLayout2->addWidget(changeItemButton); - vLayout2->addWidget(changeMultipleItemButton); - vLayout2->addWidget(addRowButton); - vLayout2->addWidget(addRowsButton); - vLayout2->addWidget(insertRowButton); - vLayout2->addWidget(insertRowsButton); - vLayout2->addWidget(removeRowButton); - vLayout2->addWidget(resetArrayButton); - vLayout2->addWidget(resetArrayEmptyButton); - vLayout2->addWidget(massiveDataTestButton); - vLayout2->addWidget(testReverseButton); - vLayout2->addWidget(testDataOrderingButton); - vLayout2->addWidget(axisTitlesVisibleCB); - vLayout2->addWidget(axisTitlesFixedCB); - vLayout2->addWidget(new QLabel(QStringLiteral("Axis label rotation"))); - vLayout2->addWidget(axisLabelRotationSlider, 1, Qt::AlignTop); + vLayout3->addWidget(colorPB); + vLayout3->addWidget(changeRowButton); + vLayout3->addWidget(changeRowsButton); + vLayout3->addWidget(changeMultipleRowsButton); + vLayout3->addWidget(changeItemButton); + vLayout3->addWidget(changeMultipleItemButton); + vLayout3->addWidget(addRowButton); + vLayout3->addWidget(addRowsButton); + vLayout3->addWidget(insertRowButton); + vLayout3->addWidget(insertRowsButton); + vLayout3->addWidget(removeRowButton); + vLayout3->addWidget(resetArrayButton); + vLayout3->addWidget(resetArrayEmptyButton); + vLayout3->addWidget(massiveDataTestButton); + vLayout3->addWidget(testReverseButton); + vLayout3->addWidget(testDataOrderingButton); + vLayout3->addWidget(axisTitlesVisibleCB); + vLayout3->addWidget(axisTitlesFixedCB); + vLayout3->addWidget(new QLabel(QStringLiteral("Axis label rotation"))); + vLayout3->addWidget(axisLabelRotationSlider, 1, Qt::AlignTop); widget->show(); - GraphModifier *modifier = new GraphModifier(surfaceGraph); + GraphModifier *modifier = new GraphModifier(surfaceGraph, container); // Connect controls to slots on modifier QObject::connect(smoothCB, &QCheckBox::stateChanged, @@ -650,9 +699,23 @@ int main(int argc, char *argv[]) modifier, &GraphModifier::toggleXAscending); QObject::connect(zAscendingCB, &QCheckBox::stateChanged, modifier, &GraphModifier::toggleZAscending); + QObject::connect(polarCB, &QCheckBox::stateChanged, + modifier, &GraphModifier::togglePolar); QObject::connect(aspectRatioSlider, &QSlider::valueChanged, modifier, &GraphModifier::setAspectRatio); + QObject::connect(horizontalAspectRatioSlider, &QSlider::valueChanged, + modifier, &GraphModifier::setHorizontalAspectRatio); + QObject::connect(surfaceTextureCB, &QCheckBox::stateChanged, + modifier, &GraphModifier::setSurfaceTexture); + QObject::connect(cameraTargetSliderX, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetX); + QObject::connect(cameraTargetSliderY, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetY); + QObject::connect(cameraTargetSliderZ, &QSlider::valueChanged, modifier, + &GraphModifier::setCameraTargetZ); + QObject::connect(marginSlider, &QSlider::valueChanged, modifier, + &GraphModifier::setGraphMargin); #ifdef MULTI_SERIES modifier->setSeries1CB(series1CB); diff --git a/tests/surfacetest/mapimage.png b/tests/surfacetest/mapimage.png Binary files differnew file mode 100644 index 00000000..079d0407 --- /dev/null +++ b/tests/surfacetest/mapimage.png diff --git a/tests/surfacetest/surfacetest.qrc b/tests/surfacetest/surfacetest.qrc index c18da2c4..266ed7e0 100644 --- a/tests/surfacetest/surfacetest.qrc +++ b/tests/surfacetest/surfacetest.qrc @@ -1,5 +1,6 @@ <RCC> <qresource prefix="/maps"> <file alias="map">Heightmap.png</file> + <file alias="mapimage">mapimage.png</file> </qresource> </RCC> diff --git a/tests/tests.pro b/tests/tests.pro index 8bbdc3f2..ccecd486 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -7,22 +7,33 @@ contains(QT_CONFIG, opengles1) { TEMPLATE = subdirs -SUBDIRS += barstest \ - scattertest \ - surfacetest \ - qmlcamera \ - qmldynamicdata \ - multigraphs \ - directional \ - qmlmultiwindow \ - itemmodeltest \ - qmlmultitest +SUBDIRS += auto -#SUBDIRS += kinectsurface +qtHaveModule(quick) { + SUBDIRS += qmlcamera \ + qmldynamicdata \ + qmlmultiwindow \ + qmlmultitest \ + qmlvolume \ + qmlperf +} -qtHaveModule(multimedia):!android:!static: SUBDIRS += spectrum +!android:!ios { + SUBDIRS += barstest \ + scattertest \ + surfacetest \ + multigraphs \ + directional \ + itemmodeltest \ + volumetrictest + + # For testing code snippets of minimal applications + SUBDIRS += minimalbars \ + minimalscatter \ + minimalsurface -# For testing code snippets of minimal applications -SUBDIRS += minimalbars \ - minimalscatter \ - minimalsurface + # Requires Kinect drivers + #SUBDIRS += kinectsurface +} + +qtHaveModule(multimedia):!android:!static: SUBDIRS += spectrum diff --git a/tests/volumetrictest/cubeFilledFlat.obj b/tests/volumetrictest/cubeFilledFlat.obj new file mode 100644 index 00000000..108cf7ac --- /dev/null +++ b/tests/volumetrictest/cubeFilledFlat.obj @@ -0,0 +1,54 @@ +# Blender v2.66 (sub 0) OBJ File: 'cube_filled.blend' +# www.blender.org +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +vt 0.666667 0.332314 +vt 0.334353 0.333333 +vt 0.665647 0.000000 +vt 0.001020 0.333333 +vt 0.000000 0.001020 +vt 0.333333 0.332314 +vt 0.333333 0.665647 +vt 0.001019 0.666667 +vt 0.000000 0.334353 +vt 0.334353 0.666667 +vt 0.333333 0.334353 +vt 0.665647 0.333333 +vt 0.333333 0.667686 +vt 0.665647 0.666667 +vt 0.666667 0.998980 +vt 0.667686 0.333333 +vt 0.666667 0.001019 +vt 0.998980 0.000000 +vt 0.333333 0.001019 +vt 0.332314 0.000000 +vt 0.332314 0.333333 +vt 0.666667 0.665647 +vt 0.334353 1.000000 +vt 1.000000 0.332314 +vn -1.000000 0.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 1.000000 -0.000000 0.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn -0.000000 -1.000000 -0.000000 +s off +f 5/1/1 6/2/1 1/3/1 +f 6/4/2 7/5/2 2/6/2 +f 7/7/3 8/8/3 4/9/3 +f 8/10/4 5/11/4 1/12/4 +f 8/13/5 7/14/5 6/15/5 +f 2/16/6 3/17/6 4/18/6 +f 6/2/1 2/19/1 1/3/1 +f 7/5/2 3/20/2 2/6/2 +f 3/21/3 7/7/3 4/9/3 +f 4/22/4 8/10/4 1/12/4 +f 5/23/5 8/13/5 6/15/5 +f 1/24/6 2/16/6 4/18/6 diff --git a/tests/volumetrictest/logo.png b/tests/volumetrictest/logo.png Binary files differnew file mode 100644 index 00000000..1e7ed4cf --- /dev/null +++ b/tests/volumetrictest/logo.png diff --git a/tests/volumetrictest/logo_no_padding.png b/tests/volumetrictest/logo_no_padding.png Binary files differnew file mode 100644 index 00000000..714234aa --- /dev/null +++ b/tests/volumetrictest/logo_no_padding.png diff --git a/tests/volumetrictest/main.cpp b/tests/volumetrictest/main.cpp new file mode 100644 index 00000000..7b18ceab --- /dev/null +++ b/tests/volumetrictest/main.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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 <QtWidgets/QApplication> +#include <QtWidgets/QWidget> +#include <QtWidgets/QHBoxLayout> +#include <QtWidgets/QVBoxLayout> +#include <QtWidgets/QRadioButton> +#include <QtWidgets/QSlider> +#include <QtWidgets/QCheckBox> +#include <QtWidgets/QLabel> +#include <QtWidgets/QPushButton> +#include <QtGui/QScreen> + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + //Q3DScatter *graph = new Q3DScatter(); + //Q3DSurface *graph = new Q3DSurface(); + Q3DBars *graph = new Q3DBars(); + QWidget *container = QWidget::createWindowContainer(graph); + + QSize screenSize = graph->screen()->size(); + container->setMinimumSize(QSize(screenSize.width() / 4, screenSize.height() / 4)); + 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")); + widget->resize(QSize(screenSize.width() / 1.5, screenSize.height() / 1.5)); + + 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); + + QLabel *sliceImageXLabel = new QLabel(widget); + QLabel *sliceImageYLabel = new QLabel(widget); + QLabel *sliceImageZLabel = new QLabel(widget); + sliceImageXLabel->setMinimumSize(QSize(200, 100)); + sliceImageYLabel->setMinimumSize(QSize(200, 200)); + sliceImageZLabel->setMinimumSize(QSize(200, 100)); + sliceImageXLabel->setMaximumSize(QSize(200, 100)); + sliceImageYLabel->setMaximumSize(QSize(200, 200)); + sliceImageZLabel->setMaximumSize(QSize(200, 100)); + sliceImageXLabel->setFrameShape(QFrame::Box); + sliceImageYLabel->setFrameShape(QFrame::Box); + sliceImageZLabel->setFrameShape(QFrame::Box); + sliceImageXLabel->setScaledContents(true); + sliceImageYLabel->setScaledContents(true); + sliceImageZLabel->setScaledContents(true); + + QPushButton *testSubTextureSetting = new QPushButton(widget); + testSubTextureSetting->setText(QStringLiteral("Test subtexture settings")); + + QLabel *rangeSliderLabel = new QLabel(QStringLiteral("Adjust ranges:"), widget); + + QSlider *rangeXSlider = new QSlider(Qt::Horizontal, widget); + rangeXSlider->setMinimum(0); + rangeXSlider->setMaximum(1024); + rangeXSlider->setValue(512); + rangeXSlider->setEnabled(true); + QSlider *rangeYSlider = new QSlider(Qt::Horizontal, widget); + rangeYSlider->setMinimum(0); + rangeYSlider->setMaximum(1024); + rangeYSlider->setValue(512); + rangeYSlider->setEnabled(true); + QSlider *rangeZSlider = new QSlider(Qt::Horizontal, widget); + rangeZSlider->setMinimum(0); + rangeZSlider->setMaximum(1024); + rangeZSlider->setValue(512); + rangeZSlider->setEnabled(true); + + QPushButton *testBoundsSetting = new QPushButton(widget); + testBoundsSetting->setText(QStringLiteral("Test data bounds")); + + vLayout->addWidget(fpsLabel); + vLayout->addWidget(sliceXCheckBox); + vLayout->addWidget(sliceXSlider); + vLayout->addWidget(sliceImageXLabel); + vLayout->addWidget(sliceYCheckBox); + vLayout->addWidget(sliceYSlider); + vLayout->addWidget(sliceImageYLabel); + vLayout->addWidget(sliceZCheckBox); + vLayout->addWidget(sliceZSlider); + vLayout->addWidget(sliceImageZLabel); + vLayout->addWidget(rangeSliderLabel); + vLayout->addWidget(rangeXSlider); + vLayout->addWidget(rangeYSlider); + vLayout->addWidget(rangeZSlider); + vLayout->addWidget(testBoundsSetting); + vLayout->addWidget(testSubTextureSetting, 1, Qt::AlignTop); + + VolumetricModifier *modifier = new VolumetricModifier(graph); + modifier->setFpsLabel(fpsLabel); + modifier->setSliceLabels(sliceImageXLabel, sliceImageYLabel, sliceImageZLabel); + + 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); + QObject::connect(testSubTextureSetting, &QPushButton::clicked, modifier, + &VolumetricModifier::testSubtextureSetting); + QObject::connect(rangeXSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustRangeX); + QObject::connect(rangeYSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustRangeY); + QObject::connect(rangeZSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustRangeZ); + QObject::connect(testBoundsSetting, &QPushButton::clicked, modifier, + &VolumetricModifier::testBoundsSetting); + + widget->show(); + return app.exec(); +} diff --git a/tests/volumetrictest/volumetrictest.cpp b/tests/volumetrictest/volumetrictest.cpp new file mode 100644 index 00000000..24ecac3d --- /dev/null +++ b/tests/volumetrictest/volumetrictest.cpp @@ -0,0 +1,716 @@ +/**************************************************************************** +** +** 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 <QtDataVisualization/qbar3dseries.h> +#include <QtDataVisualization/qvalue3daxis.h> +#include <QtDataVisualization/q3dscene.h> +#include <QtDataVisualization/q3dcamera.h> +#include <QtDataVisualization/q3dtheme.h> +#include <QtDataVisualization/qcustom3dlabel.h> +#include <QtCore/qmath.h> +#include <QtGui/QRgb> +#include <QtGui/QImage> +#include <QtWidgets/QLabel> +#include <QtCore/QDebug> + +using namespace QtDataVisualization; + +const int imageCount = 512; +const float xMiddle = 100.0f; +const float yMiddle = 2.5f; +const float zMiddle = 100.0f; +const float xRange = 40.0f; +const float yRange = 7.5f; +const float zRange = 20.0f; + +VolumetricModifier::VolumetricModifier(QAbstract3DGraph *scatter) + : m_graph(scatter), + m_volumeItem(0), + m_volumeItem2(0), + m_volumeItem3(0), + m_sliceIndexX(0), + m_sliceIndexY(0), + m_sliceIndexZ(0) +{ + m_graph->activeTheme()->setType(Q3DTheme::ThemeQt); + //m_graph->activeTheme()->setType(Q3DTheme::ThemeIsabelle); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone); + m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); + m_graph->setOrthoProjection(true); + //m_graph->scene()->activeCamera()->setTarget(QVector3D(-2.0f, 1.0f, 2.0f)); + m_scatterGraph = qobject_cast<Q3DScatter *>(m_graph); + m_surfaceGraph = qobject_cast<Q3DSurface *>(m_graph); + m_barGraph = qobject_cast<Q3DBars *>(m_graph); + if (m_scatterGraph) { + m_scatterGraph->axisX()->setRange(xMiddle - xRange, xMiddle + xRange); + m_scatterGraph->axisX()->setSegmentCount(8); + m_scatterGraph->axisY()->setRange(yMiddle - yRange, yMiddle + yRange); + m_scatterGraph->axisY()->setSegmentCount(3); + m_scatterGraph->axisZ()->setRange(zMiddle - zRange, zMiddle + zRange); + m_scatterGraph->axisZ()->setSegmentCount(8); + } else if (m_surfaceGraph) { + m_surfaceGraph->axisX()->setRange(xMiddle - xRange, xMiddle + xRange); + m_surfaceGraph->axisX()->setSegmentCount(8); + m_surfaceGraph->axisY()->setRange(yMiddle - yRange, yMiddle + yRange); + m_surfaceGraph->axisY()->setSegmentCount(3); + m_surfaceGraph->axisZ()->setRange(zMiddle - zRange, zMiddle + zRange); + m_surfaceGraph->axisZ()->setSegmentCount(8); + } else if (m_barGraph) { + QStringList rowLabels; + QStringList columnLabels; + for (int i = 0; i < xMiddle + xRange; i++) { + if (i % 5 == 0) + columnLabels << QString::number(i); + else + columnLabels << QString(); + } + for (int i = 0; i < zMiddle + zRange; i++) { + if (i % 5 == 0) + rowLabels << QString::number(i); + else + rowLabels << QString(); + } + + QBar3DSeries *series = new QBar3DSeries; + QBarDataArray *array = new QBarDataArray(); + array->reserve(zRange * 2 + 1); + for (int i = 0; i < zRange * 2 + 1; i++) + array->append(new QBarDataRow(xRange * 2 + 1)); + + series->dataProxy()->resetArray(array, rowLabels, columnLabels); + m_barGraph->addSeries(series); + + m_barGraph->columnAxis()->setRange(xMiddle - xRange, xMiddle + xRange); + m_barGraph->valueAxis()->setRange(yMiddle - yRange, yMiddle + yRange); + m_barGraph->rowAxis()->setRange(zMiddle - zRange, zMiddle + zRange); + //m_barGraph->setReflection(true); + } + m_graph->activeTheme()->setBackgroundEnabled(false); + + createVolume(); + createAnotherVolume(); + createYetAnotherVolume(); + +// m_volumeItem->setUseHighDefShader(false); +// m_volumeItem2->setUseHighDefShader(false); +// m_volumeItem3->setUseHighDefShader(false); + + m_volumeItem->setScalingAbsolute(false); + m_volumeItem2->setScalingAbsolute(false); + m_volumeItem3->setScalingAbsolute(false); + m_volumeItem->setPositionAbsolute(false); + m_volumeItem2->setPositionAbsolute(false); + m_volumeItem3->setPositionAbsolute(false); + + + m_plainItem = new QCustom3DItem; + QImage texture(2, 2, QImage::Format_ARGB32); + texture.fill(QColor(200, 200, 200, 130)); + m_plainItem->setMeshFile(QStringLiteral(":/mesh")); + m_plainItem->setTextureImage(texture); + m_plainItem->setRotation(m_volumeItem->rotation()); + m_plainItem->setPosition(QVector3D(xMiddle + xRange / 2.0f, yMiddle + yRange / 2.0f, zMiddle)); + m_plainItem->setScaling(QVector3D(20.0f, 5.0f, 10.0f)); + m_plainItem->setScalingAbsolute(false); + + m_graph->addCustomItem(m_volumeItem); + m_graph->addCustomItem(m_volumeItem2); + m_graph->addCustomItem(m_volumeItem3); + m_graph->addCustomItem(m_plainItem); + //m_graph->setMeasureFps(true); + + // Create label to cut through the volume 3 + QCustom3DLabel *label = new QCustom3DLabel; + label->setText(QStringLiteral("FOO BAR - FOO BAR - FOO BAR")); + QFont font; + font.setPixelSize(100); + label->setFont(font); + label->setScaling(QVector3D(2.0f, 2.0f, 0.0f)); + label->setRotationAxisAndAngle(QVector3D(0.0f, 1.0f, 0.0f), 45.0f); + label->setPosition(m_volumeItem3->position()); + label->setPositionAbsolute(false); + label->setScalingAbsolute(true); + + m_graph->addCustomItem(label); + + QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, + &VolumetricModifier::handleFpsChange); +// QObject::connect(m_graph->scene(), &Q3DScene::viewportChanged, 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); + } + m_sliceLabelX->setPixmap(QPixmap::fromImage( + m_volumeItem->renderSlice(Qt::XAxis, 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); + } + m_sliceLabelY->setPixmap(QPixmap::fromImage( + m_volumeItem->renderSlice(Qt::YAxis, 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); + } + m_sliceLabelZ->setPixmap(QPixmap::fromImage( + m_volumeItem->renderSlice(Qt::ZAxis, m_sliceIndexZ))); +} + +void VolumetricModifier::handleFpsChange() +{ + const QString fpsFormat = QStringLiteral("Fps: %1"); + int fps10 = int(m_graph->currentFps() * 10.0); + m_fpsLabel->setText(fpsFormat.arg(QString::number(qreal(fps10) / 10.0))); +// const QString sceneDimensionsFormat = QStringLiteral("%1 x %2"); +// m_fpsLabel->setText(sceneDimensionsFormat +// .arg(m_graph->scene()->viewport().width()) + // .arg(m_graph->scene()->viewport().height())); +} + +void VolumetricModifier::testSubtextureSetting() +{ + // Setting the rendered Slice as subtexture should result in identical volume + QVector<uchar> dataBefore = *m_volumeItem->textureData(); + dataBefore[0] = dataBefore.at(0); // Make sure we are detached + + checkRenderCase(1, Qt::XAxis, 56, dataBefore, m_volumeItem); + checkRenderCase(2, Qt::YAxis, 64, dataBefore, m_volumeItem); + checkRenderCase(3, Qt::ZAxis, 87, dataBefore, m_volumeItem); + + checkRenderCase(4, Qt::XAxis, 0, dataBefore, m_volumeItem); + checkRenderCase(5, Qt::YAxis, 0, dataBefore, m_volumeItem); + checkRenderCase(6, Qt::ZAxis, 0, dataBefore, m_volumeItem); + + checkRenderCase(7, Qt::XAxis, m_volumeItem->textureWidth() - 1, dataBefore, m_volumeItem); + checkRenderCase(8, Qt::YAxis, m_volumeItem->textureHeight() - 1, dataBefore, m_volumeItem); + checkRenderCase(9, Qt::ZAxis, m_volumeItem->textureDepth() - 1, dataBefore, m_volumeItem); + + dataBefore = *m_volumeItem2->textureData(); + dataBefore[0] = dataBefore.at(0); // Make sure we are detached + + checkRenderCase(11, Qt::XAxis, 56, dataBefore, m_volumeItem2); + checkRenderCase(12, Qt::YAxis, 64, dataBefore, m_volumeItem2); + checkRenderCase(13, Qt::ZAxis, 87, dataBefore, m_volumeItem2); + + checkRenderCase(14, Qt::XAxis, 0, dataBefore, m_volumeItem2); + checkRenderCase(15, Qt::YAxis, 0, dataBefore, m_volumeItem2); + checkRenderCase(16, Qt::ZAxis, 0, dataBefore, m_volumeItem2); + + checkRenderCase(17, Qt::XAxis, m_volumeItem2->textureWidth() - 1, dataBefore, m_volumeItem2); + checkRenderCase(18, Qt::YAxis, m_volumeItem2->textureHeight() - 1, dataBefore, m_volumeItem2); + checkRenderCase(19, Qt::ZAxis, m_volumeItem2->textureDepth() - 1, dataBefore, m_volumeItem2); + + // Do some visible swaps on volume 3 + QImage slice = m_volumeItem3->renderSlice(Qt::XAxis, 144); + slice = slice.mirrored(); + m_volumeItem3->setSubTextureData(Qt::XAxis, 144, slice); + + slice = m_volumeItem3->renderSlice(Qt::YAxis, 80); + slice = slice.mirrored(); + m_volumeItem3->setSubTextureData(Qt::YAxis, 80, slice); + + slice = m_volumeItem3->renderSlice(Qt::ZAxis, 190); + slice = slice.mirrored(true, false); + m_volumeItem3->setSubTextureData(Qt::ZAxis, 190, slice); +} + +void VolumetricModifier::adjustRangeX(int value) +{ + float adjustment = float(value - 512) / 10.0f; + if (m_scatterGraph) + m_scatterGraph->axisX()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange); + if (m_surfaceGraph) + m_surfaceGraph->axisX()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange); + if (m_barGraph) + m_barGraph->columnAxis()->setRange(xMiddle + adjustment - xRange, xMiddle + adjustment + xRange); +} + +void VolumetricModifier::adjustRangeY(int value) +{ + float adjustment = float(value - 512) / 10.0f; + if (m_scatterGraph) + m_scatterGraph->axisY()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange); + if (m_surfaceGraph) + m_surfaceGraph->axisY()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange); + if (m_barGraph) + m_barGraph->valueAxis()->setRange(yMiddle + adjustment - yRange, yMiddle + adjustment + yRange); +} + +void VolumetricModifier::adjustRangeZ(int value) +{ + float adjustment = float(value - 512) / 10.0f; + if (m_scatterGraph) + m_scatterGraph->axisZ()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange); + if (m_surfaceGraph) + m_surfaceGraph->axisZ()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange); + if (m_barGraph) + m_barGraph->rowAxis()->setRange(zMiddle + adjustment - zRange, zMiddle + adjustment + zRange); +} + +void VolumetricModifier::testBoundsSetting() +{ + static QVector3D scaling1 = m_volumeItem->scaling(); + static QVector3D scaling2 = m_volumeItem2->scaling(); + static QVector3D scaling3 = m_volumeItem3->scaling(); + static QVector3D scaleVector = QVector3D(0.5f, 0.3f, 0.2f); + + if (m_volumeItem->isScalingAbsolute()) { + m_volumeItem->setScalingAbsolute(false); + m_volumeItem2->setScalingAbsolute(false); + m_volumeItem3->setScalingAbsolute(false); + + m_volumeItem->setScaling(scaling1); + m_volumeItem2->setScaling(scaling2); + m_volumeItem3->setScaling(scaling3); + } else { + m_volumeItem->setScalingAbsolute(true); + m_volumeItem2->setScalingAbsolute(true); + m_volumeItem3->setScalingAbsolute(true); + + m_volumeItem->setScaling(scaleVector); + m_volumeItem2->setScaling(scaleVector); + m_volumeItem3->setScaling(scaleVector); + } +} + +void VolumetricModifier::checkRenderCase(int id, Qt::Axis axis, int index, + const QVector<uchar> &dataBefore, + QCustom3DVolume *volumeItem) +{ + QImage slice = volumeItem->renderSlice(axis, index); + volumeItem->setSubTextureData(axis, index, slice); + + if (dataBefore == *volumeItem->textureData()) + qDebug() << __FUNCTION__ << "Case:" << id << "Vectors identical"; + else + qDebug() << __FUNCTION__ << "Case:" << id << "BUG: VECTORS DIFFER!"; +} + +void VolumetricModifier::createVolume() +{ + m_volumeItem = new QCustom3DVolume; + m_volumeItem->setTextureFormat(QImage::Format_ARGB32); + m_volumeItem->setRotation(QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 0.0f, 20.0f)); + m_volumeItem->setPosition(QVector3D(xMiddle - (xRange / 2.0f), yMiddle + (yRange / 2.0f), zMiddle)); + //m_volumeItem->setPosition(QVector3D(xMiddle, yMiddle, zMiddle)); + m_volumeItem->setDrawSliceFrames(true); + m_volumeItem->setDrawSlices(true); + + QImage logo; + logo.load(QStringLiteral(":/logo_no_padding.png")); + //logo.load(QStringLiteral(":/logo.png")); + qDebug() << "image dimensions:" << logo.width() << logo.height() + << logo.byteCount() << (logo.width() * logo.height()) + << logo.bytesPerLine(); + + QVector<QImage *> imageArray(imageCount); + for (int i = 0; i < imageCount; i++) { + QImage *newImage = new QImage(logo); + imageArray[i] = newImage; + } + + m_volumeItem->createTextureData(imageArray); + + for (int i = 0; i < imageCount; i++) + delete imageArray[i]; + + m_sliceIndexX = m_volumeItem->textureWidth() / 2; + m_sliceIndexY = m_volumeItem->textureWidth() / 2; + m_sliceIndexZ = m_volumeItem->textureWidth() / 2; + + QVector<QRgb> colorTable = m_volumeItem->colorTable(); + + // Hack some alpha to the picture + for (int i = 0; i < colorTable.size(); i++) { + if (qAlpha(colorTable.at(i)) > 0) { + colorTable[i] = qRgba(qRed(colorTable.at(i)), + qGreen(colorTable.at(i)), + qBlue(colorTable.at(i)), + 50); + } + } + + colorTable.append(qRgba(0, 0, 0, 0)); + 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 alphaIndex = colorTable.size() - 4; + 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(xRange, yRange, zRange) / 2.0f); + + uchar *data = m_volumeItem->textureData()->data(); + uchar *p = data; + + // Change one picture using subtexture replacement + QImage flipped = logo.mirrored(); + m_volumeItem->setSubTextureData(Qt::ZAxis, 100, flipped); + + // Clean up the two extra pixels + p = data + width - 1; + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + *p = alphaIndex; + p += width; + } + } + p = data + width - 2; + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + *p = alphaIndex; + p += width; + } + } + // Red first subtexture + p = data; + 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 second to last subtexture +// p = data + frameSize * (imageCount - 2); +// for (int j = 0; j < height; j++) { +// for (int i = 0; i < width; i++) { +// *p = blueIndex; +// 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 x = max - 1 + p = data + width - 2; + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + *p = greenIndex; + 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(xMiddle + (xRange / 2.0f), yMiddle - (yRange / 2.0f), zMiddle)); + + QImage logo; + logo.load(QStringLiteral(":/logo_no_padding.png")); + //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<QImage *> imageArray(imageCount); + for (int i = 0; i < imageCount; i++) { + QImage *newImage = new QImage(logo); + imageArray[i] = newImage; + } + + m_volumeItem2->createTextureData(imageArray); + + for (int i = 0; i < imageCount; i++) + delete imageArray[i]; + + 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) * xRange * 2.0f, + float(height) / float(depth) * yRange * 2.0f, + zRange * 2.0f)); + + // Change one picture using subtexture replacement + QImage flipped = logo.mirrored(); + m_volumeItem2->setSubTextureData(Qt::ZAxis, 100, flipped); + //m_volumeItem2->setAlphaMultiplier(0.2f); + m_volumeItem2->setPreserveOpacity(false); +} + +void VolumetricModifier::createYetAnotherVolume() +{ + m_volumeItem3 = new QCustom3DVolume; + m_volumeItem3->setTextureFormat(QImage::Format_Indexed8); +// m_volumeItem2->setRotation(QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 0.0f, 10.0f)); + m_volumeItem3->setPosition(QVector3D(xMiddle - (xRange / 2.0f), yMiddle - (yRange / 2.0f), zMiddle)); + +// m_volumeItem3->setTextureDimensions(m_volumeItem->textureDataWidth(), +// m_volumeItem->textureHeight(), +// m_volumeItem->textureDepth()); + m_volumeItem3->setTextureDimensions(200, 200, 200); + + QVector<uchar> *tdata = new QVector<uchar>(m_volumeItem3->textureDataWidth() + * m_volumeItem3->textureHeight() + * m_volumeItem3->textureDepth()); + tdata->fill(0); + m_volumeItem3->setTextureData(tdata); + + m_sliceIndexX = m_volumeItem3->textureWidth() / 2; + m_sliceIndexY = m_volumeItem3->textureWidth() / 2; + m_sliceIndexZ = m_volumeItem3->textureWidth() / 2; + + QVector<QRgb> colorTable = m_volumeItem->colorTable(); + colorTable[0] = qRgba(0, 0, 0, 0); + m_volumeItem3->setColorTable(colorTable); + int redIndex = colorTable.size() - 3; + int greenIndex = colorTable.size() - 2; + int blueIndex = colorTable.size() - 1; + int width = m_volumeItem3->textureDataWidth(); + int height = m_volumeItem3->textureHeight(); + int depth = m_volumeItem3->textureDepth(); + int frameSize = width * height; + qDebug() << width << height << depth << m_volumeItem3->textureData()->size(); + m_volumeItem3->setScaling(m_volumeItem->scaling()); + + uchar *data = tdata->data(); + uchar *p = data; + + // 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 * (depth - 1); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + *p = redIndex; + p++; + } + } + + // green edge + p = data + frameSize * (depth - 1); + for (int i = 0; i < width; i++) { + *p = greenIndex; + p++; + } + for (int j = 2; j < height; j++) { + *p = greenIndex; + p += width - 1; + *p = j % 7 ? greenIndex : blueIndex; + p++; + } + for (int i = 0; i < width; i++) { + *p = greenIndex; + p++; + } + +// // Blue second to last subtexture +// p = data + frameSize * (depth - 2); +// for (int j = 0; j < height; j++) { +// for (int i = 0; i < width; i++) { +// *p = blueIndex; +// p++; +// } +// } + +// // green third to last subtexture +// p = data + frameSize * (depth - 3); +// for (int j = 0; j < height; j++) { +// for (int i = 0; i < width; i++) { +// *p = greenIndex; +// 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); + } + + +// // Fill with alternating pixels +// p = data; +// for (int k = 0; k < (depth * width * height / 4); k++) { +// *p = greenIndex; +// p++; +// *p = greenIndex; +// p++; +// *p = blueIndex; +// p++; +// *p = redIndex; +// p++; +// } + + +} + +void VolumetricModifier::setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel) +{ + m_sliceLabelX = xLabel; + m_sliceLabelY = yLabel; + m_sliceLabelZ = zLabel; + + adjustSliceX(512); + adjustSliceY(512); + adjustSliceZ(512); +} diff --git a/tests/volumetrictest/volumetrictest.h b/tests/volumetrictest/volumetrictest.h new file mode 100644 index 00000000..9029f7b9 --- /dev/null +++ b/tests/volumetrictest/volumetrictest.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 <QtDataVisualization/qcustom3dvolume.h> +#include <QtDataVisualization/qcustom3ditem.h> +#include <QtDataVisualization/q3dscatter.h> +#include <QtDataVisualization/q3dsurface.h> +#include <QtDataVisualization/q3dbars.h> + +class QLabel; + +using namespace QtDataVisualization; + +class VolumetricModifier : public QObject +{ + Q_OBJECT +public: + explicit VolumetricModifier(QAbstract3DGraph *scatter); + ~VolumetricModifier(); + + void setFpsLabel(QLabel *fpsLabel); + void setSliceLabels(QLabel *xLabel, QLabel *yLabel, QLabel *zLabel); + +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 handleFpsChange(); + void testSubtextureSetting(); + void adjustRangeX(int value); + void adjustRangeY(int value); + void adjustRangeZ(int value); + void testBoundsSetting(); + +private: + void createVolume(); + void createAnotherVolume(); + void createYetAnotherVolume(); + void checkRenderCase(int id, Qt::Axis axis, int index, const QVector<uchar> &dataBefore, + QCustom3DVolume *volumeItem); + + QAbstract3DGraph *m_graph; + Q3DScatter *m_scatterGraph; + Q3DSurface *m_surfaceGraph; + Q3DBars *m_barGraph; + QCustom3DVolume *m_volumeItem; + QCustom3DVolume *m_volumeItem2; + QCustom3DVolume *m_volumeItem3; + QCustom3DItem *m_plainItem; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + QLabel *m_fpsLabel; + QLabel *m_sliceLabelX; + QLabel *m_sliceLabelY; + QLabel *m_sliceLabelZ; +}; + +#endif diff --git a/tests/volumetrictest/volumetrictest.pro b/tests/volumetrictest/volumetrictest.pro new file mode 100644 index 00000000..df3ef0dd --- /dev/null +++ b/tests/volumetrictest/volumetrictest.pro @@ -0,0 +1,14 @@ +!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..7cd8533b --- /dev/null +++ b/tests/volumetrictest/volumetrictest.qrc @@ -0,0 +1,7 @@ +<RCC> + <qresource prefix="/"> + <file>logo.png</file> + <file alias="mesh">cubeFilledFlat.obj</file> + <file>logo_no_padding.png</file> + </qresource> +</RCC> diff --git a/tools/blender/arrow.blend b/tools/blender/arrow.blend Binary files differnew file mode 100644 index 00000000..9c0ef46b --- /dev/null +++ b/tools/blender/arrow.blend diff --git a/tools/blender/narrowArrow.blend b/tools/blender/narrowArrow.blend Binary files differnew file mode 100644 index 00000000..3fd3dff9 --- /dev/null +++ b/tools/blender/narrowArrow.blend |