// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "surfacegraphmodifier.h" #include "highlightseries.h" #include "topographicseries.h" #include "custominputhandler.h" #include #include #include #include using namespace Qt::StringLiterals; const int sampleCountX = 150; const int sampleCountZ = 150; const int heightMapGridStepX = 6; const int heightMapGridStepZ = 6; const float sampleMin = -8.f; const float sampleMax = 8.f; const float areaWidth = 8000.f; const float areaHeight = 8000.f; const float aspectRatio = 0.1389f; const float minRange = areaWidth * 0.49f; SurfaceGraphModifier::SurfaceGraphModifier(Q3DSurface *surface, QLabel *label, QObject *parent) : QObject(parent), m_graph(surface), m_textField(label) { m_graph->scene()->activeCamera()->setZoomLevel(85.f); m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricRight); m_graph->activeTheme()->setType(Q3DTheme::ThemeRetro); m_graph->setAxisX(new QValue3DAxis); m_graph->setAxisY(new QValue3DAxis); m_graph->setAxisZ(new QValue3DAxis); // // Sqrt Sin // //! [0] m_sqrtSinProxy = new QSurfaceDataProxy(); m_sqrtSinSeries = new QSurface3DSeries(m_sqrtSinProxy); //! [0] fillSqrtSinProxy(); // // Multisurface heightmap // //! [2] // Create the first surface layer QImage heightMapImageOne(":/data/layer_1.png"); m_heightMapProxyOne = new QHeightMapSurfaceDataProxy(heightMapImageOne); m_heightMapSeriesOne = new QSurface3DSeries(m_heightMapProxyOne); m_heightMapSeriesOne->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s); m_heightMapProxyOne->setValueRanges(34.f, 40.f, 18.f, 24.f); //! [2] // Create the other 2 surface layers QImage heightMapImageTwo(":/data/layer_2.png"); m_heightMapProxyTwo = new QHeightMapSurfaceDataProxy(heightMapImageTwo); m_heightMapSeriesTwo = new QSurface3DSeries(m_heightMapProxyTwo); m_heightMapSeriesTwo->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s); m_heightMapProxyTwo->setValueRanges(34.f, 40.f, 18.f, 24.f); QImage heightMapImageThree(":/data/layer_3.png"); m_heightMapProxyThree = new QHeightMapSurfaceDataProxy(heightMapImageThree); m_heightMapSeriesThree = new QSurface3DSeries(m_heightMapProxyThree); m_heightMapSeriesThree->setItemLabelFormat(u"(@xLabel, @zLabel): @yLabel"_s); m_heightMapProxyThree->setValueRanges(34.f, 40.f, 18.f, 24.f); // The images are the same size, so it's enough to get the dimensions from one m_heightMapWidth = heightMapImageOne.width(); m_heightMapHeight = heightMapImageOne.height(); // Set the gradients for multi-surface layers QLinearGradient grOne; grOne.setColorAt(0.f, Qt::black); grOne.setColorAt(0.38f, Qt::darkYellow); grOne.setColorAt(0.39f, Qt::darkGreen); grOne.setColorAt(0.5f, Qt::darkGray); grOne.setColorAt(1.f, Qt::gray); m_heightMapSeriesOne->setBaseGradient(grOne); m_heightMapSeriesOne->setColorStyle(Q3DTheme::ColorStyleRangeGradient); QLinearGradient grTwo; grTwo.setColorAt(0.39f, Qt::blue); grTwo.setColorAt(0.4f, Qt::white); m_heightMapSeriesTwo->setBaseGradient(grTwo); m_heightMapSeriesTwo->setColorStyle(Q3DTheme::ColorStyleRangeGradient); QLinearGradient grThree; grThree.setColorAt(0.f, Qt::white); grThree.setColorAt(0.05f, Qt::black); m_heightMapSeriesThree->setBaseGradient(grThree); m_heightMapSeriesThree->setColorStyle(Q3DTheme::ColorStyleRangeGradient); // Custom items and label connect(m_graph, &QAbstract3DGraph::selectedElementChanged, this, &SurfaceGraphModifier::handleElementSelected); m_selectionAnimation = new QPropertyAnimation(this); m_selectionAnimation->setPropertyName("scaling"); m_selectionAnimation->setDuration(500); m_selectionAnimation->setLoopCount(-1); QFont titleFont = QFont("Century Gothic", 30); titleFont.setBold(true); m_titleLabel = new QCustom3DLabel("Oil Rigs on Imaginary Sea", titleFont, QVector3D(0.f, 1.2f, 0.f), QVector3D(1.f, 1.f, 0.f), QQuaternion()); m_titleLabel->setPositionAbsolute(true); m_titleLabel->setFacingCamera(true); m_titleLabel->setBackgroundColor(QColor(0x66cdaa)); m_graph->addCustomItem(m_titleLabel); m_titleLabel->setVisible(false); // Make two of the custom object visible toggleItemOne(true); toggleItemTwo(true); // // Topographic map // m_topography = new TopographicSeries(); m_topography->setTopographyFile(":/data/topography.png", areaWidth, areaHeight); m_topography->setItemLabelFormat(u"@yLabel m"_s); m_highlight = new HighlightSeries(); m_highlight->setTopographicSeries(m_topography); m_highlight->setMinHeight(minRange * aspectRatio); m_highlight->handleGradientChange(areaWidth * aspectRatio); //! [16] QObject::connect(m_graph->axisY(), &QValue3DAxis::maxChanged, m_highlight, &HighlightSeries::handleGradientChange); //! [16] m_customInputHandler = new CustomInputHandler(m_graph); m_customInputHandler->setHighlightSeries(m_highlight); m_customInputHandler->setAxes(m_graph->axisX(), m_graph->axisY(), m_graph->axisZ()); m_customInputHandler->setLimits(0.f, areaWidth, minRange); m_customInputHandler->setAspectRatio(aspectRatio); } void SurfaceGraphModifier::fillSqrtSinProxy() { float stepX = (sampleMax - sampleMin) / float(sampleCountX - 1); float stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1); //! [1] auto *dataArray = new QSurfaceDataArray; dataArray->reserve(sampleCountZ); for (int i = 0 ; i < sampleCountZ ; ++i) { auto *newRow = new QSurfaceDataRow; newRow->reserve(sampleCountX); // Keep values within range bounds, since just adding step can cause minor drift due // to the rounding errors. float z = qMin(sampleMax, (i * stepZ + sampleMin)); for (int j = 0; j < sampleCountX; ++j) { float x = qMin(sampleMax, (j * stepX + sampleMin)); float R = qSqrt(z * z + x * x) + 0.01f; float y = (qSin(R) / R + 0.24f) * 1.61f; newRow->append(QSurfaceDataItem({x, y, z})); } dataArray->append(newRow); } m_sqrtSinProxy->resetArray(dataArray); //! [1] } void SurfaceGraphModifier::enableSqrtSinModel(bool enable) { if (enable) { //! [3] m_sqrtSinSeries->setDrawMode(QSurface3DSeries::DrawSurfaceAndWireframe); m_sqrtSinSeries->setFlatShadingEnabled(true); m_graph->axisX()->setLabelFormat("%.2f"); m_graph->axisZ()->setLabelFormat("%.2f"); m_graph->axisX()->setRange(sampleMin, sampleMax); m_graph->axisY()->setRange(0.f, 2.f); m_graph->axisZ()->setRange(sampleMin, sampleMax); m_graph->axisX()->setLabelAutoRotation(30.f); m_graph->axisY()->setLabelAutoRotation(90.f); m_graph->axisZ()->setLabelAutoRotation(30.f); m_graph->removeSeries(m_heightMapSeriesOne); m_graph->removeSeries(m_heightMapSeriesTwo); m_graph->removeSeries(m_heightMapSeriesThree); m_graph->removeSeries(m_topography); m_graph->removeSeries(m_highlight); m_graph->addSeries(m_sqrtSinSeries); //! [3] m_titleLabel->setVisible(false); m_graph->axisX()->setTitleVisible(false); m_graph->axisY()->setTitleVisible(false); m_graph->axisZ()->setTitleVisible(false); m_graph->axisX()->setTitle({}); m_graph->axisY()->setTitle({}); m_graph->axisZ()->setTitle({}); m_graph->setActiveInputHandler(m_defaultInputHandler); //! [6] // Reset range sliders for Sqrt & Sin m_rangeMinX = sampleMin; m_rangeMinZ = sampleMin; m_stepX = (sampleMax - sampleMin) / float(sampleCountX - 1); m_stepZ = (sampleMax - sampleMin) / float(sampleCountZ - 1); m_axisMinSliderX->setMinimum(0); m_axisMinSliderX->setMaximum(sampleCountX - 2); m_axisMinSliderX->setValue(0); m_axisMaxSliderX->setMinimum(1); m_axisMaxSliderX->setMaximum(sampleCountX - 1); m_axisMaxSliderX->setValue(sampleCountX - 1); m_axisMinSliderZ->setMinimum(0); m_axisMinSliderZ->setMaximum(sampleCountZ - 2); m_axisMinSliderZ->setValue(0); m_axisMaxSliderZ->setMinimum(1); m_axisMaxSliderZ->setMaximum(sampleCountZ - 1); m_axisMaxSliderZ->setValue(sampleCountZ - 1); //! [6] } } void SurfaceGraphModifier::enableHeightMapModel(bool enable) { if (enable) { m_heightMapSeriesOne->setDrawMode(QSurface3DSeries::DrawSurface); m_heightMapSeriesOne->setFlatShadingEnabled(false); m_heightMapSeriesTwo->setDrawMode(QSurface3DSeries::DrawSurface); m_heightMapSeriesTwo->setFlatShadingEnabled(false); m_heightMapSeriesThree->setDrawMode(QSurface3DSeries::DrawSurface); m_heightMapSeriesThree->setFlatShadingEnabled(false); m_graph->axisX()->setLabelFormat("%.1f N"); m_graph->axisZ()->setLabelFormat("%.1f E"); m_graph->axisX()->setRange(34.f, 40.f); //! [4] m_graph->axisY()->setAutoAdjustRange(true); //! [4] m_graph->axisZ()->setRange(18.f, 24.f); m_graph->axisX()->setTitle(u"Latitude"_s); m_graph->axisY()->setTitle(u"Height"_s); m_graph->axisZ()->setTitle(u"Longitude"_s); m_graph->removeSeries(m_sqrtSinSeries); m_graph->removeSeries(m_topography); m_graph->removeSeries(m_highlight); m_graph->addSeries(m_heightMapSeriesOne); m_graph->addSeries(m_heightMapSeriesTwo); m_graph->addSeries(m_heightMapSeriesThree); m_graph->setActiveInputHandler(m_defaultInputHandler); m_titleLabel->setVisible(true); m_graph->axisX()->setTitleVisible(true); m_graph->axisY()->setTitleVisible(true); m_graph->axisZ()->setTitleVisible(true); // Reset range sliders for height map int mapGridCountX = m_heightMapWidth / heightMapGridStepX; int mapGridCountZ = m_heightMapHeight / heightMapGridStepZ; m_rangeMinX = 34.f; m_rangeMinZ = 18.f; m_stepX = 6.f / float(mapGridCountX - 1); m_stepZ = 6.f / float(mapGridCountZ - 1); m_axisMinSliderX->setMinimum(0); m_axisMinSliderX->setMaximum(mapGridCountX - 2); m_axisMinSliderX->setValue(0); m_axisMaxSliderX->setMinimum(1); m_axisMaxSliderX->setMaximum(mapGridCountX - 1); m_axisMaxSliderX->setValue(mapGridCountX - 1); m_axisMinSliderZ->setMinimum(0); m_axisMinSliderZ->setMaximum(mapGridCountZ - 2); m_axisMinSliderZ->setValue(0); m_axisMaxSliderZ->setMinimum(1); m_axisMaxSliderZ->setMaximum(mapGridCountZ - 1); m_axisMaxSliderZ->setValue(mapGridCountZ - 1); } } void SurfaceGraphModifier::enableTopographyModel(bool enable) { if (enable) { m_graph->axisX()->setLabelFormat("%i"); m_graph->axisZ()->setLabelFormat("%i"); m_graph->axisX()->setRange(0.f, areaWidth); m_graph->axisY()->setRange(100.f, areaWidth * aspectRatio); m_graph->axisZ()->setRange(0.f, areaHeight); m_graph->axisX()->setLabelAutoRotation(30.f); m_graph->axisY()->setLabelAutoRotation(90.f); m_graph->axisZ()->setLabelAutoRotation(30.f); m_graph->removeSeries(m_heightMapSeriesOne); m_graph->removeSeries(m_heightMapSeriesTwo); m_graph->removeSeries(m_heightMapSeriesThree); m_graph->addSeries(m_topography); m_graph->addSeries(m_highlight); m_titleLabel->setVisible(false); m_graph->axisX()->setTitleVisible(false); m_graph->axisY()->setTitleVisible(false); m_graph->axisZ()->setTitleVisible(false); m_graph->axisX()->setTitle({}); m_graph->axisY()->setTitle({}); m_graph->axisZ()->setTitle({}); //! [5] m_graph->setActiveInputHandler(m_customInputHandler); //! [5] // Reset range sliders for topography map m_rangeMinX = 0.f; m_rangeMinZ = 0.f; m_stepX = 1.f; m_stepZ = 1.f; m_axisMinSliderX->setMinimum(0); m_axisMinSliderX->setMaximum(areaWidth - 200); m_axisMinSliderX->setValue(0); m_axisMaxSliderX->setMinimum(200); m_axisMaxSliderX->setMaximum(areaWidth); m_axisMaxSliderX->setValue(areaWidth); m_axisMinSliderZ->setMinimum(0); m_axisMinSliderZ->setMaximum(areaHeight - 200); m_axisMinSliderZ->setValue(0); m_axisMaxSliderZ->setMinimum(200); m_axisMaxSliderZ->setMaximum(areaHeight); m_axisMaxSliderZ->setValue(areaHeight); } } void SurfaceGraphModifier::adjustXMin(int min) { float minX = m_stepX * float(min) + m_rangeMinX; int max = m_axisMaxSliderX->value(); if (min >= max) { max = min + 1; m_axisMaxSliderX->setValue(max); } float maxX = m_stepX * max + m_rangeMinX; setAxisXRange(minX, maxX); } void SurfaceGraphModifier::adjustXMax(int max) { float maxX = m_stepX * float(max) + m_rangeMinX; int min = m_axisMinSliderX->value(); if (max <= min) { min = max - 1; m_axisMinSliderX->setValue(min); } float minX = m_stepX * min + m_rangeMinX; setAxisXRange(minX, maxX); } void SurfaceGraphModifier::adjustZMin(int min) { float minZ = m_stepZ * float(min) + m_rangeMinZ; int max = m_axisMaxSliderZ->value(); if (min >= max) { max = min + 1; m_axisMaxSliderZ->setValue(max); } float maxZ = m_stepZ * max + m_rangeMinZ; setAxisZRange(minZ, maxZ); } void SurfaceGraphModifier::adjustZMax(int max) { float maxX = m_stepZ * float(max) + m_rangeMinZ; int min = m_axisMinSliderZ->value(); if (max <= min) { min = max - 1; m_axisMinSliderZ->setValue(min); } float minX = m_stepZ * min + m_rangeMinZ; setAxisZRange(minX, maxX); } //! [7] void SurfaceGraphModifier::setAxisXRange(float min, float max) { m_graph->axisX()->setRange(min, max); } //! [7] void SurfaceGraphModifier::setAxisZRange(float min, float max) { m_graph->axisZ()->setRange(min, max); } void SurfaceGraphModifier::setBlackToYellowGradient() { //! [8] QLinearGradient gr; gr.setColorAt(0.f, Qt::black); gr.setColorAt(0.33f, Qt::blue); gr.setColorAt(0.67f, Qt::red); gr.setColorAt(1.f, Qt::yellow); m_sqrtSinSeries->setBaseGradient(gr); m_sqrtSinSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); //! [8] } void SurfaceGraphModifier::setGreenToRedGradient() { QLinearGradient gr; gr.setColorAt(0.f, Qt::darkGreen); gr.setColorAt(0.5f, Qt::yellow); gr.setColorAt(0.8f, Qt::red); gr.setColorAt(1.f, Qt::darkRed); m_sqrtSinSeries->setBaseGradient(gr); m_sqrtSinSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); } void SurfaceGraphModifier::toggleItemOne(bool show) { //! [10] QVector3D positionOne = QVector3D(39.f, 77.f, 19.2f); //! [10] QVector3D positionOnePipe = QVector3D(39.f, 45.f, 19.2f); QVector3D positionOneLabel = QVector3D(39.f, 107.f, 19.2f); if (show) { //! [9] QImage color = QImage(2, 2, QImage::Format_RGB32); color.fill(Qt::red); //! [9] //! [11] auto *item = new QCustom3DItem(":/data/oilrig.obj", positionOne, QVector3D(0.025f, 0.025f, 0.025f), QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 45.f), color); //! [11] //! [12] m_graph->addCustomItem(item); //! [12] item = new QCustom3DItem(":/data/pipe.obj", positionOnePipe, QVector3D(0.005f, 0.5f, 0.005f), QQuaternion(), color); item->setShadowCasting(false); m_graph->addCustomItem(item); //! [13] auto *label = new QCustom3DLabel(); label->setText("Oil Rig One"); label->setPosition(positionOneLabel); label->setScaling(QVector3D(1.f, 1.f, 1.f)); m_graph->addCustomItem(label); //! [13] } else { resetSelection(); //! [14] m_graph->removeCustomItemAt(positionOne); //! [14] m_graph->removeCustomItemAt(positionOnePipe); m_graph->removeCustomItemAt(positionOneLabel); } } void SurfaceGraphModifier::toggleItemTwo(bool show) { QVector3D positionTwo = QVector3D(34.5f, 77.f, 23.4f); QVector3D positionTwoPipe = QVector3D(34.5f, 45.f, 23.4f); QVector3D positionTwoLabel = QVector3D(34.5f, 107.f, 23.4f); if (show) { QImage color = QImage(2, 2, QImage::Format_RGB32); color.fill(Qt::red); auto *item = new QCustom3DItem(); item->setMeshFile(":/data/oilrig.obj"); item->setPosition(positionTwo); item->setScaling(QVector3D(0.025f, 0.025f, 0.025f)); item->setRotation(QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 25.f)); item->setTextureImage(color); m_graph->addCustomItem(item); item = new QCustom3DItem(":/data/pipe.obj", positionTwoPipe, QVector3D(0.005f, 0.5f, 0.005f), QQuaternion(), color); item->setShadowCasting(false); m_graph->addCustomItem(item); auto *label = new QCustom3DLabel(); label->setText("Oil Rig Two"); label->setPosition(positionTwoLabel); label->setScaling(QVector3D(1.f, 1.f, 1.f)); m_graph->addCustomItem(label); } else { resetSelection(); m_graph->removeCustomItemAt(positionTwo); m_graph->removeCustomItemAt(positionTwoPipe); m_graph->removeCustomItemAt(positionTwoLabel); } } void SurfaceGraphModifier::toggleItemThree(bool show) { QVector3D positionThree = QVector3D(34.5f, 86.f, 19.1f); QVector3D positionThreeLabel = QVector3D(34.5f, 116.f, 19.1f); if (show) { QImage color = QImage(2, 2, QImage::Format_RGB32); color.fill(Qt::darkMagenta); auto *item = new QCustom3DItem(); item->setMeshFile(":/data/refinery.obj"); item->setPosition(positionThree); item->setScaling(QVector3D(0.04f, 0.04f, 0.04f)); item->setRotation(QQuaternion::fromAxisAndAngle(0.f, 1.f, 0.f, 75.f)); item->setTextureImage(color); m_graph->addCustomItem(item); auto *label = new QCustom3DLabel(); label->setText("Refinery"); label->setPosition(positionThreeLabel); label->setScaling(QVector3D(1.f, 1.f, 1.f)); m_graph->addCustomItem(label); } else { resetSelection(); m_graph->removeCustomItemAt(positionThree); m_graph->removeCustomItemAt(positionThreeLabel); } } void SurfaceGraphModifier::toggleSeeThrough(bool seethrough) { if (seethrough) { m_graph->seriesList().at(0)->setDrawMode(QSurface3DSeries::DrawWireframe); m_graph->seriesList().at(1)->setDrawMode(QSurface3DSeries::DrawWireframe); } else { m_graph->seriesList().at(0)->setDrawMode(QSurface3DSeries::DrawSurface); m_graph->seriesList().at(1)->setDrawMode(QSurface3DSeries::DrawSurface); } } void SurfaceGraphModifier::toggleOilHighlight(bool highlight) { if (highlight) { QLinearGradient grThree; grThree.setColorAt(0.0, Qt::black); grThree.setColorAt(0.05, Qt::red); m_graph->seriesList().at(2)->setBaseGradient(grThree); } else { QLinearGradient grThree; grThree.setColorAt(0.0, Qt::white); grThree.setColorAt(0.05, Qt::black); m_graph->seriesList().at(2)->setBaseGradient(grThree); } } void SurfaceGraphModifier::toggleShadows(bool shadows) { if (shadows) m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityMedium); else m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone); } //! [15] void SurfaceGraphModifier::toggleSurfaceTexture(bool enable) { if (enable) m_topography->setTextureFile(":/data/maptexture.jpg"); else m_topography->setTextureFile(""); } //! [15] void SurfaceGraphModifier::handleElementSelected(QAbstract3DGraph::ElementType type) { resetSelection(); if (type == QAbstract3DGraph::ElementCustomItem) { QCustom3DItem *item = m_graph->selectedCustomItem(); QString text; if (qobject_cast(item) != 0) { text.append("Custom label: "); } else { QStringList split = item->meshFile().split("/"); text.append(split.last()); text.append(": "); } int index = m_graph->selectedCustomItemIndex(); text.append(QString::number(index)); m_textField->setText(text); m_previouslyAnimatedItem = item; m_previousScaling = item->scaling(); m_selectionAnimation->setTargetObject(item); m_selectionAnimation->setStartValue(item->scaling()); m_selectionAnimation->setEndValue(item->scaling() * 1.5f); m_selectionAnimation->start(); } else if (type == QAbstract3DGraph::ElementSeries) { QString text = "Surface ("; QSurface3DSeries *series = m_graph->selectedSeries(); if (series) { QPoint point = series->selectedPoint(); QString posStr; posStr.setNum(point.x()); text.append(posStr); text.append(", "); posStr.setNum(point.y()); text.append(posStr); } text.append(")"); m_textField->setText(text); } else if (type > QAbstract3DGraph::ElementSeries && type < QAbstract3DGraph::ElementCustomItem) { int index = m_graph->selectedLabelIndex(); QString text; if (type == QAbstract3DGraph::ElementAxisXLabel) text.append("Axis X label: "); else if (type == QAbstract3DGraph::ElementAxisYLabel) text.append("Axis Y label: "); else text.append("Axis Z label: "); text.append(QString::number(index)); m_textField->setText(text); } else { m_textField->setText("Nothing"); } } void SurfaceGraphModifier::resetSelection() { m_selectionAnimation->stop(); if (m_previouslyAnimatedItem) m_previouslyAnimatedItem->setScaling(m_previousScaling); m_previouslyAnimatedItem = nullptr; }