1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtCore/QMutexLocker>
5#include "private/qquick3drepeater_p.h"
6#include "q3dscene.h"
7#include "qabstractdataproxy.h"
8#include "qquickgraphssurface_p.h"
9#include "qgraphs3dlogging_p.h"
10
11#include "qcategory3daxis_p.h"
12#include "qgraphsinputhandler_p.h"
13#include "qquickgraphssurface_p.h"
14#include "qquickgraphstexturedata_p.h"
15#include "qsurface3dseries_p.h"
16#include "qsurfacedataproxy_p.h"
17#include "qvalue3daxis_p.h"
18
19#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
20#include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
21#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
22
23#include <QtQuick/qquickitemgrabresult.h>
24#include <qtgraphs_tracepoints_p.h>
25
26QT_BEGIN_NAMESPACE
27
28Q_TRACE_PREFIX(qtgraphs,
29 "QT_BEGIN_NAMESPACE" \
30 "class QQuickGraphsSurface;" \
31 "class QSurface3DSeries;" \
32 "QT_END_NAMESPACE"
33 )
34
35Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceModelUpdate_entry, void *model);
36Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceModelUpdate_exit);
37
38Q_TRACE_POINT(qtgraphs, QGraphs3DSurfacePointSelectionUpdate_entry, void *model);
39Q_TRACE_POINT(qtgraphs, QGraphs3DSurfacePointSelectionUpdate_exit);
40
41Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceDoPicking_entry, float posX, float posY);
42Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceDoPicking_exit);
43
44Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceDoRayPicking_entry, float originX, float originY,
45 float originZ, float directionX, float directionY, float directionZ);
46Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceDoRayPicking_exit);
47
48Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceCreateSliceView_entry);
49Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceCreateSliceView_exit);
50
51Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceCreateOffscreenSliceView_entry, int index, int requestedIndex, int sliceType);
52Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceCreateOffscreenSliceView_exit);
53
54Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddModel_entry, QSurface3DSeries *series);
55Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddModel_exit);
56
57Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddSliceModel_entry);
58Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddSliceModel_exit);
59
60Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddFillModel_entry);
61Q_TRACE_POINT(qtgraphs, QGraphs3DSurfaceAddFillModel_exit);
62
63/*!
64 * \qmltype Surface3D
65 * \inherits GraphsItem3D
66 * \inqmlmodule QtGraphs
67 * \ingroup graphs_qml_3D
68 * \brief Describes the usage of the 3D surface graph.
69 *
70 * This type enables developers to render surface plots in 3D with Qt Quick.
71 *
72 * You will need to import the Qt Graphs module to use this type:
73 *
74 * \snippet doc_src_qmlgraphs.cpp 0
75 *
76 * After that you can use Surface3D in your qml files:
77 *
78 * \snippet doc_src_qmlgraphs.cpp 3
79 *
80 * See \l{Surface Graph Gallery} for more thorough usage example.
81 *
82 * \sa Surface3DSeries, ItemModelSurfaceDataProxy, Bars3D, Scatter3D,
83 * {Qt Graphs C++ Classes for 3D}
84 */
85
86/*!
87 * \qmlproperty Value3DAxis Surface3D::axisX
88 * The active x-axis.
89 *
90 * If an axis is not given, a temporary default axis with no labels and an
91 * automatically adjusting range is created.
92 * This temporary axis is destroyed if another axis is explicitly set to the
93 * same orientation.
94 */
95
96/*!
97 * \qmlproperty Value3DAxis Surface3D::axisY
98 * The active y-axis.
99 *
100 * If an axis is not given, a temporary default axis with no labels and an
101 * automatically adjusting range is created.
102 * This temporary axis is destroyed if another axis is explicitly set to the
103 * same orientation.
104 */
105
106/*!
107 * \qmlproperty Value3DAxis Surface3D::axisZ
108 * The active z-axis.
109 *
110 * If an axis is not given, a temporary default axis with no labels and an
111 * automatically adjusting range is created.
112 * This temporary axis is destroyed if another axis is explicitly set to the
113 * same orientation.
114 */
115
116/*!
117 * \qmlproperty Surface3DSeries Surface3D::selectedSeries
118 * \readonly
119 *
120 * The selected series or null. If \l {GraphsItem3D::selectionMode}{selectionMode}
121 * has the \c MultiSeries flag set, this property holds the series
122 * which owns the selected point.
123 */
124
125/*!
126 * \qmlproperty list<Surface3DSeries> Surface3D::seriesList
127 * \qmldefault
128 * This property holds the series of the graph.
129 * By default, this property contains an empty list.
130 * To set the series, either use the addSeries() function or define them as
131 * children of the graph.
132 */
133
134/*!
135 * \qmlproperty bool Surface3D::flipHorizontalGrid
136 *
137 * In some use cases the horizontal axis grid is mostly covered by the surface,
138 * so it can be more useful to display the horizontal axis grid on top of the
139 * graph rather than on the bottom. A typical use case for this is showing 2D
140 * spectrograms using orthoGraphic projection with a top-down viewpoint.
141 *
142 * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal
143 * background of the graph.
144 * If \c{true}, the horizontal axis grid and labels are drawn on the opposite
145 * side of the graph from the horizontal background.
146 * Defaults to \c{false}.
147 */
148
149/*!
150 * \qmlmethod void Surface3D::addSeries(Surface3DSeries series)
151 * Adds the \a series to the graph.
152 * \sa GraphsItem3D::hasSeries()
153 */
154
155/*!
156 * \qmlmethod void Surface3D::removeSeries(Surface3DSeries series)
157 * Removes the \a series from the graph.
158 * \sa GraphsItem3D::hasSeries()
159 */
160
161/*!
162 * \qmlmethod void Surface3D::removeSeries(Surface3DSeries series)
163 * Removes the \a series from the graph.
164 * \sa GraphsItem3D::hasSeries()
165 */
166
167/*!
168 * \qmlmethod void Surface3D::renderSliceToImage(int index, int requestedIndex, QtGraphs3D::SliceCaptureType sliceType, QUrl filePath)
169 * \since 6.10
170 *
171 * Exports a 2d slice from series at \a index and saves the result to an image
172 * at a specified \a filePath.
173 * To export all series, set \a index to -1.
174 * The exported slice includes lines of row or column, which is defined by
175 * \a sliceType at a given \a requestedIndex.
176 */
177
178/*!
179 * \qmlsignal Surface3D::axisXChanged(ValueAxis3D axis)
180 *
181 * This signal is emitted when axisX changes to \a axis.
182 */
183
184/*!
185 * \qmlsignal Surface3D::axisYChanged(ValueAxis3D axis)
186 *
187 * This signal is emitted when axisY changes to \a axis.
188 */
189
190/*!
191 * \qmlsignal Surface3D::axisZChanged(ValueAxis3D axis)
192 *
193 * This signal is emitted when axisZ changes to \a axis.
194 */
195
196/*!
197 * \qmlsignal Surface3D::selectedSeriesChanged(Surface3DSeries series)
198 *
199 * This signal is emitted when selectedSeries changes to \a series.
200 */
201
202/*!
203 * \qmlsignal Surface3D::flipHorizontalGridChanged(bool flip)
204 *
205 * This signal is emitted when flipHorizontalGrid changes to \a flip.
206 */
207
208QQuickGraphsSurface::QQuickGraphsSurface(QQuickItem *parent)
209 : QQuickGraphsItem(parent)
210{
211 m_graphType = QAbstract3DSeries::SeriesType::Surface;
212 setAxisX(0);
213 setAxisY(0);
214 setAxisZ(0);
215 setAcceptedMouseButtons(Qt::AllButtons);
216 clearSelection();
217}
218
219QQuickGraphsSurface::~QQuickGraphsSurface()
220{
221 QMutexLocker locker(m_nodeMutex.data());
222 const QMutexLocker locker2(mutex());
223 for (const auto &model : std::as_const(t&: m_model))
224 delete model;
225 if (m_grabresult)
226 delete m_grabresult;
227}
228
229void QQuickGraphsSurface::setAxisX(QValue3DAxis *axis)
230{
231 QQuickGraphsItem::setAxisX(axis);
232}
233
234QValue3DAxis *QQuickGraphsSurface::axisX() const
235{
236 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX());
237}
238
239void QQuickGraphsSurface::setAxisY(QValue3DAxis *axis)
240{
241 QQuickGraphsItem::setAxisY(axis);
242}
243
244QValue3DAxis *QQuickGraphsSurface::axisY() const
245{
246 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY());
247}
248
249void QQuickGraphsSurface::setAxisZ(QValue3DAxis *axis)
250{
251 QQuickGraphsItem::setAxisZ(axis);
252}
253
254QValue3DAxis *QQuickGraphsSurface::axisZ() const
255{
256 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ());
257}
258
259void QQuickGraphsSurface::handleShadingChanged()
260{
261 auto series = static_cast<QSurface3DSeries *>(sender());
262 for (auto model : std::as_const(t&: m_model)) {
263 if (model->series == series) {
264 updateModel(model);
265 break;
266 }
267 }
268}
269
270void QQuickGraphsSurface::handleWireframeColorChanged()
271{
272 for (auto model : std::as_const(t&: m_model)) {
273 QQmlListReference gridMaterialRef(model->gridModel, "materials");
274 auto gridMaterial = gridMaterialRef.at(0);
275 QColor gridColor = model->series->wireframeColor();
276 gridMaterial->setProperty(name: "gridColor", value: gridColor);
277
278 if (sliceView()) {
279 QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
280 auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
281 gridMaterial->setBaseColor(gridColor);
282 }
283 }
284}
285
286void QQuickGraphsSurface::handleMeshTypeChanged(QAbstract3DSeries::Mesh mesh)
287{
288 QSurface3DSeries *sender = qobject_cast<QSurface3DSeries *>(object: QObject::sender());
289 changePointerMeshTypeForSeries(mesh, series: sender);
290 if (sliceView())
291 changeSlicePointerMeshTypeForSeries(mesh, series: sender);
292}
293
294void QQuickGraphsSurface::changePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh,
295 QSurface3DSeries *series)
296{
297 changePointerForSeries(filename: getMeshFileName(mesh, series), series);
298}
299
300void QQuickGraphsSurface::changeSlicePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh,
301 QSurface3DSeries *series)
302{
303 changeSlicePointerForSeries(filename: getMeshFileName(mesh, series), series);
304}
305
306void QQuickGraphsSurface::handleLightingModeChanged()
307{
308 auto series = static_cast<QSurface3DSeries *>(QObject::sender());
309 for (auto model : std::as_const(t&: m_model)) {
310 if (model->series == series) {
311 updateMaterial(model);
312 break;
313 }
314 }
315}
316
317QString QQuickGraphsSurface::getMeshFileName(QAbstract3DSeries::Mesh mesh,
318 QSurface3DSeries *series) const
319{
320 QString fileName = {};
321 switch (mesh) {
322 case QAbstract3DSeries::Mesh::Sphere:
323 fileName = QStringLiteral("defaultMeshes/sphereMesh");
324 break;
325 case QAbstract3DSeries::Mesh::Bar:
326 case QAbstract3DSeries::Mesh::Cube:
327 fileName = QStringLiteral("defaultMeshes/barMesh");
328 break;
329 case QAbstract3DSeries::Mesh::Pyramid:
330 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
331 break;
332 case QAbstract3DSeries::Mesh::Cone:
333 fileName = QStringLiteral("defaultMeshes/coneMesh");
334 break;
335 case QAbstract3DSeries::Mesh::Cylinder:
336 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
337 break;
338 case QAbstract3DSeries::Mesh::BevelBar:
339 case QAbstract3DSeries::Mesh::BevelCube:
340 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
341 break;
342 case QAbstract3DSeries::Mesh::UserDefined:
343 fileName = series->userDefinedMesh();
344 break;
345 default:
346 fileName = QStringLiteral("defaultMeshes/sphereMesh");
347 }
348 return fileName;
349}
350
351void QQuickGraphsSurface::handlePointerChanged(const QString &filename)
352{
353 QSurface3DSeries *sender = qobject_cast<QSurface3DSeries *>(object: QObject::sender());
354 changePointerForSeries(filename, series: sender);
355 changeSlicePointerForSeries(filename, series: sender);
356}
357
358void QQuickGraphsSurface::changePointerForSeries(const QString &filename, QSurface3DSeries *series)
359{
360 if (!filename.isEmpty()) {
361 // Selection pointer
362 QQuick3DNode *parent = graphNode();
363
364 QQuick3DPrincipledMaterial *pointerMaterial = nullptr;
365 QQuick3DModel *pointer = m_selectionPointers.value(key: series);
366
367 if (pointer) {
368 // Retrieve the already created material
369 QQmlListReference materialRef(pointer, "materials");
370 pointerMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(object: materialRef.at(0));
371 // Delete old model
372 delete pointer;
373 } else {
374 // No pointer yet; create material for it
375 pointerMaterial = new QQuick3DPrincipledMaterial();
376 pointerMaterial->setParent(this);
377 pointerMaterial->setBaseColor(theme()->singleHighlightColor());
378 }
379 // Create new pointer
380 pointer = new QQuick3DModel();
381 pointer->setParent(parent);
382 pointer->setParentItem(parent);
383 pointer->setSource(QUrl(filename));
384 pointer->setScale(QVector3D(0.05f, 0.05f, 0.05f));
385 // Insert to the map
386 m_selectionPointers.insert(key: series, value: pointer);
387 // Set material
388 QQmlListReference materialRef(pointer, "materials");
389 materialRef.append(pointerMaterial);
390 }
391}
392
393void QQuickGraphsSurface::changeSlicePointerForSeries(const QString &filename,
394 QSurface3DSeries *series)
395{
396 if (!filename.isEmpty()) {
397 // Slice selection pointer
398 QQuick3DNode *sliceParent = sliceView()->scene();
399
400 QQuick3DPrincipledMaterial *slicePointerMaterial = nullptr;
401 QQuick3DModel *slicePointer = m_sliceSelectionPointers.value(key: series);
402
403 if (slicePointer) {
404 // Retrieve the already created material
405 QQmlListReference sliceMaterialRef(slicePointer, "materials");
406 slicePointerMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(
407 object: sliceMaterialRef.at(0));
408 // Delete old model
409 delete slicePointer;
410 } else {
411 // No pointer yet; create material for it
412 slicePointerMaterial = new QQuick3DPrincipledMaterial();
413 slicePointerMaterial->setParent(sliceParent);
414 slicePointerMaterial->setBaseColor(theme()->singleHighlightColor());
415 }
416 // Create new pointer
417 slicePointer = new QQuick3DModel();
418 slicePointer->setParent(sliceParent);
419 slicePointer->setParentItem(sliceParent);
420 slicePointer->setSource(QUrl(filename));
421 slicePointer->setScale(QVector3D(0.05f, 0.05f, 0.05f));
422 // Insert to the map
423 m_sliceSelectionPointers.insert(key: series, value: slicePointer);
424 // Set material
425 QQmlListReference sliceMaterialRef(slicePointer, "materials");
426 sliceMaterialRef.append(slicePointerMaterial);
427 }
428}
429
430void QQuickGraphsSurface::handleFlipHorizontalGridChanged(bool flip)
431{
432 float factor = -1.0f;
433 if (isGridUpdated())
434 factor = flip ? -1.0f : 1.0f;
435
436 for (int i = 0; i < repeaterX()->count(); i++) {
437 QQuick3DNode *obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
438 QVector3D pos = obj->position();
439 pos.setY(pos.y() * factor);
440 obj->setPosition(pos);
441 }
442
443 for (int i = 0; i < repeaterZ()->count(); i++) {
444 QQuick3DNode *obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
445 QVector3D pos = obj->position();
446 pos.setY(pos.y() * factor);
447 obj->setPosition(pos);
448 }
449
450 QVector3D pos = titleLabelX()->position();
451 pos.setY(pos.y() * factor);
452 titleLabelX()->setPosition(pos);
453
454 pos = titleLabelZ()->position();
455 pos.setY(pos.y() * factor);
456 titleLabelZ()->setPosition(pos);
457
458 setGridUpdated(false);
459 emit flipHorizontalGridChanged(flip);
460 setFlipHorizontalGridChanged(false);
461}
462
463void QQuickGraphsSurface::adjustAxisRanges()
464{
465 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
466 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
467 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
468 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
469 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
470 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
471 bool first = true;
472
473 if (adjustX || adjustY || adjustZ) {
474 float minValueX = 0.0f;
475 float maxValueX = 0.0f;
476 float minValueY = 0.0f;
477 float maxValueY = 0.0f;
478 float minValueZ = 0.0f;
479 float maxValueZ = 0.0f;
480 qsizetype seriesCount = m_seriesList.size();
481 for (int series = 0; series < seriesCount; series++) {
482 const QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(
483 m_seriesList.at(i: series));
484 const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
485 if (surfaceSeries->isVisible() && proxy) {
486 QVector3D minLimits;
487 QVector3D maxLimits;
488 proxy->d_func()->limitValues(minValues&: minLimits,
489 maxValues&: maxLimits,
490 axisX: valueAxisX,
491 axisY: valueAxisY,
492 axisZ: valueAxisZ);
493 if (adjustX) {
494 if (first) {
495 // First series initializes the values
496 minValueX = minLimits.x();
497 maxValueX = maxLimits.x();
498 } else {
499 minValueX = qMin(a: minValueX, b: minLimits.x());
500 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
501 }
502 }
503 if (adjustY) {
504 if (first) {
505 // First series initializes the values
506 minValueY = minLimits.y();
507 maxValueY = maxLimits.y();
508 } else {
509 minValueY = qMin(a: minValueY, b: minLimits.y());
510 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
511 }
512 }
513 if (adjustZ) {
514 if (first) {
515 // First series initializes the values
516 minValueZ = minLimits.z();
517 maxValueZ = maxLimits.z();
518 } else {
519 minValueZ = qMin(a: minValueZ, b: minLimits.z());
520 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
521 }
522 }
523 first = false;
524 }
525 }
526
527 static const float adjustmentRatio = 20.0f;
528 static const float defaultAdjustment = 1.0f;
529
530 if (adjustX) {
531 // If all points at same coordinate, need to default to some valid range
532 float adjustment = 0.0f;
533 if (minValueX == maxValueX) {
534 if (adjustZ) {
535 // X and Z are linked to have similar unit size, so choose the valid
536 // range based on it
537 if (minValueZ == maxValueZ)
538 adjustment = defaultAdjustment;
539 else
540 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
541 } else {
542 if (valueAxisZ)
543 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
544 else
545 adjustment = defaultAdjustment;
546 }
547 }
548 valueAxisX->d_func()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
549 }
550 if (adjustY) {
551 // If all points at same coordinate, need to default to some valid range
552 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
553 float adjustment = 0.0f;
554 if (minValueY == maxValueY)
555 adjustment = defaultAdjustment;
556 valueAxisY->d_func()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
557 }
558 if (adjustZ) {
559 // If all points at same coordinate, need to default to some valid range
560 float adjustment = 0.0f;
561 if (minValueZ == maxValueZ) {
562 if (adjustX) {
563 // X and Z are linked to have similar unit size, so choose the valid
564 // range based on it
565 if (minValueX == maxValueX)
566 adjustment = defaultAdjustment;
567 else
568 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
569 } else {
570 if (valueAxisX)
571 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
572 else
573 adjustment = defaultAdjustment;
574 }
575 }
576 valueAxisZ->d_func()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
577 }
578 }
579}
580
581void QQuickGraphsSurface::handleArrayReset()
582{
583 QSurface3DSeries *series;
584 if (qobject_cast<QSurfaceDataProxy *>(object: sender()))
585 series = static_cast<QSurfaceDataProxy *>(sender())->series();
586 else
587 series = static_cast<QSurface3DSeries *>(sender());
588
589 if (series->isVisible()) {
590 adjustAxisRanges();
591 setDataDirty(true);
592 }
593 if (!m_changedSeriesList.contains(t: series))
594 m_changedSeriesList.append(t: series);
595
596 // Clear selection unless still valid
597 if (m_selectedPoint != invalidSelectionPosition())
598 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
599 series->d_func()->markItemLabelDirty();
600 emitNeedRender();
601}
602
603void QQuickGraphsSurface::handleFlatShadingSupportedChange(bool supported)
604{
605 // Handle renderer flat surface support indicator signal. This happens exactly
606 // once per renderer.
607 if (m_flatShadingSupported != supported) {
608 m_flatShadingSupported = supported;
609 // Emit the change for all added surfaces
610 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
611 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
612 emit surfaceSeries->flatShadingSupportedChanged(enabled: m_flatShadingSupported);
613 }
614 }
615}
616
617void QQuickGraphsSurface::handleRowsChanged(qsizetype startIndex, qsizetype count)
618{
619 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
620 qsizetype oldChangeCount = m_changedRows.size();
621 if (!oldChangeCount)
622 m_changedRows.reserve(asize: count);
623
624 int selectedRow = m_selectedPoint.x();
625 for (qsizetype i = 0; i < count; i++) {
626 bool newItem = true;
627 qsizetype candidate = startIndex + i;
628 for (qsizetype j = 0; j < oldChangeCount; j++) {
629 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
630 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
631 newItem = false;
632 break;
633 }
634 }
635 if (newItem) {
636 ChangeRow newChangeItem = {.series: series, .row: candidate};
637 m_changedRows.append(t: newChangeItem);
638 if (series == m_selectedSeries && selectedRow == candidate)
639 series->d_func()->markItemLabelDirty();
640 }
641 }
642 if (count) {
643 m_changeTracker.rowsChanged = true;
644 setDataDirty(true);
645
646 if (series->isVisible())
647 adjustAxisRanges();
648 emitNeedRender();
649 }
650}
651
652void QQuickGraphsSurface::handleItemChanged(qsizetype rowIndex, qsizetype columnIndex)
653{
654 QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
655 QSurface3DSeries *series = sender->series();
656
657 bool newItem = true;
658 QPoint candidate((int(rowIndex)), (int(columnIndex)));
659 for (ChangeItem item : std::as_const(t&: m_changedItems)) {
660 if (item.point == candidate && item.series == series) {
661 newItem = false;
662 break;
663 }
664 }
665 if (newItem) {
666 ChangeItem newItem = {.series: series, .point: candidate};
667 m_changedItems.append(t: newItem);
668 m_changeTracker.itemChanged = true;
669 setDataDirty(true);
670
671 if (series == m_selectedSeries && m_selectedPoint == candidate)
672 series->d_func()->markItemLabelDirty();
673
674 if (series->isVisible())
675 adjustAxisRanges();
676 emitNeedRender();
677 }
678}
679
680void QQuickGraphsSurface::handleRowsAdded(qsizetype startIndex, qsizetype count)
681{
682 Q_UNUSED(startIndex);
683 Q_UNUSED(count);
684 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
685 if (series->isVisible()) {
686 adjustAxisRanges();
687 setDataDirty(true);
688 }
689 if (!m_changedSeriesList.contains(t: series))
690 m_changedSeriesList.append(t: series);
691 emitNeedRender();
692}
693
694void QQuickGraphsSurface::handleRowsInserted(qsizetype startIndex, qsizetype count)
695{
696 Q_UNUSED(startIndex);
697 Q_UNUSED(count);
698 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
699 if (series == m_selectedSeries) {
700 // If rows inserted to selected series before the selection, adjust the selection
701 int selectedRow = m_selectedPoint.x();
702 if (startIndex <= selectedRow) {
703 selectedRow += count;
704 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
705 }
706 }
707
708 if (series->isVisible()) {
709 adjustAxisRanges();
710 setDataDirty(true);
711 }
712 if (!m_changedSeriesList.contains(t: series))
713 m_changedSeriesList.append(t: series);
714
715 emitNeedRender();
716}
717
718void QQuickGraphsSurface::handleRowsRemoved(qsizetype startIndex, qsizetype count)
719{
720 Q_UNUSED(startIndex);
721 Q_UNUSED(count);
722 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
723 if (series == m_selectedSeries) {
724 // If rows removed from selected series before the selection, adjust the selection
725 int selectedRow = m_selectedPoint.x();
726 if (startIndex <= selectedRow) {
727 if ((startIndex + count) > selectedRow)
728 selectedRow = -1; // Selected row removed
729 else
730 selectedRow -= count; // Move selected row down by amount of rows removed
731
732 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
733 }
734 }
735
736 if (series->isVisible()) {
737 adjustAxisRanges();
738 setDataDirty(true);
739 }
740 if (!m_changedSeriesList.contains(t: series))
741 m_changedSeriesList.append(t: series);
742
743 emitNeedRender();
744}
745
746QPoint QQuickGraphsSurface::invalidSelectionPosition()
747{
748 static QPoint invalidSelectionPoint(-1, -1);
749 return invalidSelectionPoint;
750}
751
752void QQuickGraphsSurface::setSelectedPoint(const QPoint position,
753 QSurface3DSeries *series,
754 bool enterSlice)
755{
756 // If the selection targets non-existent point, clear selection instead.
757 QPoint pos = position;
758
759 // Series may already have been removed, so check it before setting the selection.
760 if (!m_seriesList.contains(t: series))
761 series = 0;
762
763 const QSurfaceDataProxy *proxy = 0;
764 if (series)
765 proxy = series->dataProxy();
766
767 if (!proxy)
768 pos = invalidSelectionPosition();
769
770 if (pos != invalidSelectionPosition()) {
771 qsizetype maxRow = proxy->rowCount() - 1;
772 qsizetype maxCol = proxy->columnCount() - 1;
773
774 if (pos.y() < 0 || pos.y() > maxRow || pos.x() < 0 || pos.x() > maxCol)
775 pos = invalidSelectionPosition();
776 }
777
778 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
779 if (pos == invalidSelectionPosition() || !series->isVisible()) {
780 scene()->setSlicingActive(false);
781 } else {
782 // If the selected point is outside data window, or there is no selected
783 // point, disable slicing
784 float axisMinX = m_axisX->min();
785 float axisMaxX = m_axisX->max();
786 float axisMinZ = m_axisZ->min();
787 float axisMaxZ = m_axisZ->max();
788
789 QSurfaceDataItem item = series->dataArray().at(i: pos.y()).at(i: pos.x());
790 if (item.x() < axisMinX || item.x() > axisMaxX || item.z() < axisMinZ
791 || item.z() > axisMaxZ) {
792 scene()->setSlicingActive(false);
793 } else if (enterSlice) {
794 scene()->setSlicingActive(true);
795 }
796 }
797 emitNeedRender();
798 }
799
800 if (pos != m_selectedPoint || series != m_selectedSeries) {
801 bool seriesChanged = (series != m_selectedSeries);
802 m_selectedPoint = pos;
803 m_selectedSeries = series;
804 m_changeTracker.selectedPointChanged = true;
805
806 // Clear selection from other series and finally set new selection to the
807 // specified series
808 for (QAbstract3DSeries *otherSeries : std::as_const(t&: m_seriesList)) {
809 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
810 if (surfaceSeries != m_selectedSeries)
811 surfaceSeries->d_func()->setSelectedPoint(invalidSelectionPosition());
812 }
813 if (m_selectedSeries)
814 m_selectedSeries->d_func()->setSelectedPoint(m_selectedPoint);
815
816 if (seriesChanged)
817 emit selectedSeriesChanged(series: m_selectedSeries);
818
819 emitNeedRender();
820 }
821}
822
823void QQuickGraphsSurface::setSelectionMode(QtGraphs3D::SelectionFlags mode)
824{
825 // Currently surface only supports row and column modes when also slicing
826 if ((mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
827 || mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))
828 && !mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
829 qCWarning(lcProperties3D, "%s unsupported selection mode",
830 qUtf8Printable(QLatin1String(__FUNCTION__)));
831 return;
832 } else if (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
833 && (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
834 == mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
835 qCWarning(lcProperties3D, "%s must specify one of either row or column selection mode"
836 "in conjunction with slicing mode.", qUtf8Printable(QLatin1String(__FUNCTION__)));
837 } else {
838 QtGraphs3D::SelectionFlags oldMode = selectionMode();
839
840 QQuickGraphsItem::setSelectionMode(mode);
841
842 if (mode != oldMode) {
843 // Refresh selection upon mode change to ensure slicing is correctly
844 // updated according to series the visibility.
845 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: true);
846
847 // Special case: Always deactivate slicing when changing away from slice
848 // automanagement, as this can't be handled in setSelectedBar.
849 if (!mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
850 && oldMode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
851 scene()->setSlicingActive(false);
852 }
853 }
854 }
855}
856
857void QQuickGraphsSurface::handleAxisAutoAdjustRangeChangedInOrientation(
858 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
859{
860 Q_UNUSED(orientation);
861 Q_UNUSED(autoAdjust);
862
863 adjustAxisRanges();
864}
865
866void QQuickGraphsSurface::handleAxisRangeChangedBySender(QObject *sender)
867{
868 QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
869
870 // Update selected point - may be moved offscreen
871 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
872}
873
874void QQuickGraphsSurface::handleSeriesVisibilityChangedBySender(QObject *sender)
875{
876 QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
877
878 setSeriesVisibilityDirty(true);
879 // Visibility changes may require disabling slicing,
880 // so just reset selection to ensure everything is still valid.
881 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
882}
883
884void QQuickGraphsSurface::setFlipHorizontalGrid(bool flip)
885{
886 if (m_flipHorizontalGrid != flip) {
887 m_flipHorizontalGrid = flip;
888 m_changeTracker.flipHorizontalGridChanged = true;
889 emit flipHorizontalGridChanged(flip);
890 emitNeedRender();
891 }
892}
893
894bool QQuickGraphsSurface::flipHorizontalGrid() const
895{
896 return m_flipHorizontalGrid;
897}
898
899bool QQuickGraphsSurface::isFlatShadingSupported()
900{
901 return m_flatShadingSupported;
902}
903
904QList<QSurface3DSeries *> QQuickGraphsSurface::surfaceSeriesList()
905{
906 QList<QSurface3DSeries *> surfaceSeriesList;
907 for (QAbstract3DSeries *abstractSeries : std::as_const(t&: m_seriesList)) {
908 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
909 if (surfaceSeries)
910 surfaceSeriesList.append(t: surfaceSeries);
911 }
912
913 return surfaceSeriesList;
914}
915
916void QQuickGraphsSurface::updateSurfaceTexture(QSurface3DSeries *series)
917{
918 m_changeTracker.surfaceTextureChanged = true;
919
920 if (!m_changedTextures.contains(t: series))
921 m_changedTextures.append(t: series);
922
923 emitNeedRender();
924}
925
926QQmlListProperty<QSurface3DSeries> QQuickGraphsSurface::seriesList()
927{
928 return QQmlListProperty<QSurface3DSeries>(this,
929 this,
930 &QQuickGraphsSurface::appendSeriesFunc,
931 &QQuickGraphsSurface::countSeriesFunc,
932 &QQuickGraphsSurface::atSeriesFunc,
933 &QQuickGraphsSurface::clearSeriesFunc);
934}
935
936void QQuickGraphsSurface::appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
937 QSurface3DSeries *series)
938{
939 reinterpret_cast<QQuickGraphsSurface *>(list->data)->addSeries(series);
940}
941
942qsizetype QQuickGraphsSurface::countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
943{
944 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().size();
945}
946
947QSurface3DSeries *QQuickGraphsSurface::atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
948 qsizetype index)
949{
950 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().at(i: index);
951}
952
953void QQuickGraphsSurface::clearSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
954{
955 QQuickGraphsSurface *declSurface = reinterpret_cast<QQuickGraphsSurface *>(list->data);
956 QList<QSurface3DSeries *> realList = declSurface->surfaceSeriesList();
957 qsizetype count = realList.size();
958 for (qsizetype i = 0; i < count; i++)
959 declSurface->removeSeries(series: realList.at(i));
960}
961
962void QQuickGraphsSurface::addSeries(QSurface3DSeries *series)
963{
964 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Surface);
965
966 QQuickGraphsItem::addSeriesInternal(series);
967
968 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
969 if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
970 setSelectedPoint(position: surfaceSeries->selectedPoint(), series: surfaceSeries, enterSlice: false);
971
972 if (!surfaceSeries->texture().isNull())
973 updateSurfaceTexture(series: surfaceSeries);
974
975 if (isReady())
976 addModel(series);
977}
978
979void QQuickGraphsSurface::removeSeries(QSurface3DSeries *series)
980{
981 bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
982
983 QQuickGraphsItem::removeSeriesInternal(series);
984
985 if (m_selectedSeries == series)
986 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
987
988 if (wasVisible)
989 adjustAxisRanges();
990
991 series->setParent(this); // Reparent as removing will leave series parentless
992 for (int i = 0; i < m_model.size();) {
993 if (m_model[i]->series == series) {
994 m_model[i]->model->deleteLater();
995 m_model[i]->gridModel->deleteLater();
996 m_model[i]->fillModel->deleteLater();
997 m_model[i]->heights.clear();
998 m_fillDirty.remove(key: m_model.at(i));
999 if (sliceView()) {
1000 m_model[i]->sliceModel->deleteLater();
1001 m_model[i]->sliceGridModel->deleteLater();
1002 }
1003 delete m_model[i];
1004 m_model.removeAt(i);
1005 } else {
1006 ++i;
1007 }
1008 }
1009}
1010
1011void QQuickGraphsSurface::clearSelection()
1012{
1013 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
1014 for (auto model : std::as_const(t&: m_model))
1015 model->picked = false;
1016}
1017
1018void QQuickGraphsSurface::handleAxisXChanged(QAbstract3DAxis *axis)
1019{
1020 emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis));
1021}
1022
1023void QQuickGraphsSurface::handleAxisYChanged(QAbstract3DAxis *axis)
1024{
1025 emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis));
1026}
1027
1028void QQuickGraphsSurface::handleAxisZChanged(QAbstract3DAxis *axis)
1029{
1030 emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis));
1031}
1032
1033void QQuickGraphsSurface::componentComplete()
1034{
1035 QQuickGraphsItem::componentComplete();
1036
1037 auto serieslist = surfaceSeriesList();
1038 for (auto series : std::as_const(t&: serieslist)) {
1039 addModel(series);
1040 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
1041 }
1042
1043 graphsInputHandler()->setGraphsItem(this);
1044 qCDebug(lcGraphs3D, " QQuickGraphsSurface::componentComplete");
1045}
1046
1047void QQuickGraphsSurface::synchData()
1048{
1049 qCDebug(lcGraphs3D, "%s start syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
1050
1051 if (isFlipHorizontalGridChanged())
1052 setHorizontalFlipFactor(flipHorizontalGrid() ? -1 : 1);
1053
1054 QQuickGraphsItem::synchData();
1055
1056 if (isSelectedPointChanged()) {
1057 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
1058 updateSelectedPoint();
1059 setSelectedPointChanged(false);
1060 }
1061
1062 if (isGridUpdated() || isFlipHorizontalGridChanged())
1063 handleFlipHorizontalGridChanged(flip: flipHorizontalGrid());
1064
1065 if (isSurfaceTextureChanged()) {
1066 if (!isChangedTexturesEmpty()) {
1067 for (auto model : std::as_const(t&: m_model)) {
1068 if (hasSeriesToChangeTexture(series: model->series))
1069 updateMaterial(model);
1070 }
1071 }
1072 setSurfaceTextureChanged(false);
1073 }
1074
1075 if (gridLineType() == QtGraphs3D::GridLineType::Shader) {
1076 if (!m_topGrid) {
1077 //add horizontal top grid
1078 QUrl topGridUrl = QUrl(QStringLiteral(":/defaultMeshes/barMeshFull"));
1079 m_topGrid = new QQuick3DModel();
1080 m_topGridScale = new QQuick3DNode();
1081 m_topGridRotation = new QQuick3DNode();
1082
1083 m_topGridScale->setParent(graphNode());
1084 m_topGridScale->setParentItem(graphNode());
1085
1086 m_topGridRotation->setParent(m_topGridScale);
1087 m_topGridRotation->setParentItem(m_topGridScale);
1088
1089 m_topGrid->setObjectName("Top Grid");
1090 m_topGrid->setParent(m_topGridRotation);
1091 m_topGrid->setParentItem(m_topGridRotation);
1092
1093 m_topGrid->setSource(topGridUrl);
1094 m_topGrid->setPickable(false);
1095 }
1096 auto min = qMin(a: scaleWithBackground().x() + backgroundScaleMargin().x(),
1097 b: scaleWithBackground().z() + backgroundScaleMargin().z());
1098 m_topGridScale->setScale(QVector3D(scaleWithBackground().x() + backgroundScaleMargin().x(),
1099 min * gridOffset(),
1100 scaleWithBackground().z() + backgroundScaleMargin().z()));
1101 m_topGridScale->setPosition(
1102 QVector3D(0.0f, scaleWithBackground().y() + backgroundScaleMargin().y(), 0.0f));
1103
1104 m_topGrid->setVisible(m_flipHorizontalGrid);
1105 QQmlListReference materialsRefF(m_topGrid, "materials");
1106 QQmlListReference bbRef(background(), "materials");
1107 QQuick3DCustomMaterial *bgMatFloor;
1108 if (!materialsRefF.size() && bbRef.size()) {
1109 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(bbRef.at(0));
1110 materialsRefF.append(bgMatFloor);
1111 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1112 } else if (materialsRefF.size()) {
1113 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(materialsRefF.at(0));
1114 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1115 }
1116 }
1117
1118 qCDebug(lcGraphs3D, "%s end syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
1119}
1120
1121void QQuickGraphsSurface::updateGraph()
1122{
1123 for (auto model : std::as_const(t&: m_model)) {
1124 bool seriesVisible = model->series->isVisible();
1125 if (isSeriesVisibilityDirty()) {
1126 if (!seriesVisible) {
1127 model->model->setVisible(seriesVisible);
1128 model->gridModel->setVisible(seriesVisible);
1129 if (sliceView()) {
1130 model->sliceModel->setVisible(seriesVisible);
1131 model->sliceGridModel->setVisible(seriesVisible);
1132
1133 if (m_selectedSeries == model->series) {
1134 clearSelection();
1135 setSliceActivatedChanged(true);
1136 m_selectionDirty = !seriesVisible;
1137 }
1138 }
1139 continue;
1140 }
1141 }
1142
1143 if (model->model->visible() != seriesVisible)
1144 model->model->setVisible(seriesVisible);
1145
1146 model->gridModel->setVisible(
1147 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe) && seriesVisible);
1148 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1149 model->model->setLocalOpacity(1.f);
1150 else
1151 model->model->setLocalOpacity(.0f);
1152
1153 if (sliceView() && sliceView()->isVisible()) {
1154 model->sliceGridModel->setVisible(
1155 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1156 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1157 model->sliceModel->setLocalOpacity(1.f);
1158 else
1159 model->sliceModel->setLocalOpacity(.0f);
1160 }
1161 updateMaterial(model);
1162 }
1163
1164 setSeriesVisibilityDirty(false);
1165 if (isDataDirty() || isSeriesVisualsDirty()) {
1166 if (hasChangedSeriesList()) {
1167 handleChangedSeries();
1168 } else {
1169 for (auto model : std::as_const(t&: m_model)) {
1170 bool visible = model->series->isVisible();
1171 if (visible)
1172 updateModel(model);
1173 }
1174 }
1175
1176 if (isSliceEnabled()) {
1177 if (!sliceView())
1178 createSliceView();
1179
1180 if (sliceView()->isVisible()) {
1181 if (!m_selectedSeries) {
1182 m_selectionDirty = true;
1183 setSliceActivatedChanged(true);
1184 }
1185 toggleSliceGraph();
1186 }
1187 }
1188
1189 setDataDirty(false);
1190 setSeriesVisualsDirty(false);
1191 }
1192
1193 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
1194 updateSelectedPoint();
1195}
1196
1197void QQuickGraphsSurface::calculateSceneScalingFactors()
1198{
1199 float scaleX, scaleY, scaleZ;
1200 float marginH, marginV;
1201
1202 if (margin() < 0.0f) {
1203 marginH = .1f;
1204 marginV = .1f;
1205 } else {
1206 marginH = margin();
1207 marginV = margin();
1208 }
1209
1210 if (isPolar()) {
1211 float polarMargin = calculatePolarBackgroundMargin();
1212 marginH = qMax(a: marginH, b: polarMargin);
1213 }
1214 float hAspectRatio;
1215 if (isPolar())
1216 hAspectRatio = 1.0f;
1217 else
1218 hAspectRatio = horizontalAspectRatio();
1219
1220 QSizeF areaSize;
1221 if (qFuzzyIsNull(f: hAspectRatio)) {
1222 areaSize.setHeight(axisZ()->max() - axisZ()->min());
1223 areaSize.setWidth(axisX()->max() - axisX()->min());
1224 } else {
1225 areaSize.setHeight(1.0);
1226 areaSize.setWidth(hAspectRatio);
1227 }
1228
1229 float horizontalMaxDimension;
1230 if (aspectRatio() > 2.0f) {
1231 horizontalMaxDimension = 2.0f;
1232 scaleY = 2.0f / aspectRatio();
1233 } else {
1234 horizontalMaxDimension = aspectRatio();
1235 scaleY = 1.0f;
1236 }
1237
1238 if (isPolar())
1239 m_polarRadius = horizontalMaxDimension;
1240
1241 float scaleFactor = qMax(a: areaSize.width(), b: areaSize.height());
1242 scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
1243 scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
1244
1245 setScale(QVector3D(scaleX, scaleY, scaleZ));
1246 setScaleWithBackground(QVector3D(scaleX, scaleY, scaleZ));
1247 setBackgroundScaleMargin(QVector3D(marginH, marginV, marginH));
1248}
1249
1250void QQuickGraphsSurface::handleChangedSeries()
1251{
1252 auto changedSeries = changedSeriesList();
1253 for (auto series : std::as_const(t&: changedSeries)) {
1254 for (auto model : std::as_const(t&: m_model)) {
1255 if (model->series == series) {
1256 updateModel(model);
1257 }
1258 }
1259 }
1260}
1261
1262inline static float getDataValue(const QSurfaceDataArray &array, bool searchRow, qsizetype index)
1263{
1264 if (searchRow)
1265 return array.at(i: 0).at(i: index).x();
1266 else
1267 return array.at(i: index).at(i: 0).z();
1268}
1269
1270inline static int binarySearchArray(const QSurfaceDataArray &array,
1271 qsizetype maxIndex,
1272 float limitValue,
1273 bool searchRow,
1274 bool lowBound,
1275 bool ascending)
1276{
1277 qsizetype min = 0;
1278 qsizetype max = maxIndex;
1279 qsizetype mid = 0;
1280 qsizetype retVal;
1281
1282 while (max >= min) {
1283 mid = (min + max) / 2;
1284 float arrayValue = getDataValue(array, searchRow, index: mid);
1285 if (arrayValue == limitValue)
1286 return int(mid);
1287 if (ascending) {
1288 if (arrayValue < limitValue)
1289 min = mid + 1;
1290 else
1291 max = mid - 1;
1292 } else {
1293 if (arrayValue > limitValue)
1294 min = mid + 1;
1295 else
1296 max = mid - 1;
1297 }
1298 }
1299
1300 if (lowBound == ascending) {
1301 if (mid > max)
1302 retVal = mid;
1303 else
1304 retVal = min;
1305 } else {
1306 if (mid > max)
1307 retVal = max;
1308 else
1309 retVal = mid;
1310 }
1311
1312 if (retVal < 0 || retVal > maxIndex) {
1313 retVal = -1;
1314 } else if (lowBound) {
1315 if (getDataValue(array, searchRow, index: retVal) < limitValue)
1316 retVal = -1;
1317 } else {
1318 if (getDataValue(array, searchRow, index: retVal) > limitValue)
1319 retVal = -1;
1320 }
1321 return int(retVal);
1322}
1323
1324QRect QQuickGraphsSurface::calculateSampleSpace(SurfaceModel *model)
1325{
1326 QRect sampleSpace;
1327 const QSurfaceDataArray &array = model->series->dataArray();
1328 if (array.size() > 0) {
1329 if (array.size() >= 1 && array.at(i: 0).size() >= 1) {
1330 const qsizetype maxRow = array.size() - 1;
1331 const qsizetype maxColumn = array.at(i: 0).size() - 1;
1332
1333 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
1334 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1335
1336 // Check if Z is filled before X. If it is, or there's something else that is fishy,
1337 // print out a warning about incorrectly formed data.
1338 bool incorrectDataFormat = false;
1339 qreal val = array.at(i: 0).at(i: 0).z();
1340 qreal step = array.at(i: 1).at(i: 0).z() - array.at(i: 0).at(i: 0).z();
1341 if (maxRow > 1) {
1342 if ((val + step * maxRow == array.at(i: maxRow).at(i: 0).z() && !ascendingZ)
1343 || (val - step * maxRow == array.at(i: maxRow).at(i: 0).z() && ascendingZ)) {
1344 incorrectDataFormat = true;
1345 }
1346 }
1347 val = array.at(i: 0).at(i: 0).x();
1348 step = array.at(i: 0).at(i: 1).x() - array.at(i: 0).at(i: 0).x();
1349 if (maxColumn > 1) {
1350 if ((val + step * maxColumn == array.at(i: 0).at(i: maxColumn).x() && !ascendingX)
1351 || (val - step * maxColumn == array.at(i: 0).at(i: maxColumn).x() && ascendingX)) {
1352 incorrectDataFormat = true;
1353 }
1354 }
1355
1356 if (incorrectDataFormat) {
1357 qCWarning(lcProperties3D,
1358 "Data might be in an incorrect format. If the graph looks wrong or "
1359 "is displayed only partially, verify that rows are filled first, "
1360 "and columns after.");
1361 }
1362
1363 if (model->ascendingX != ascendingX) {
1364 setIndexDirty(true);
1365 model->ascendingX = ascendingX;
1366 }
1367 if (model->ascendingZ != ascendingZ) {
1368 setIndexDirty(true);
1369 model->ascendingZ = ascendingZ;
1370 }
1371
1372 int idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->min(), searchRow: true, lowBound: true, ascending: ascendingX);
1373 if (idx != -1) {
1374 if (ascendingX)
1375 sampleSpace.setLeft(idx);
1376 else
1377 sampleSpace.setRight(idx);
1378 } else {
1379 sampleSpace.setWidth(-1);
1380 return sampleSpace;
1381 }
1382
1383 idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->max(), searchRow: true, lowBound: false, ascending: ascendingX);
1384 if (idx != -1) {
1385 if (ascendingX)
1386 sampleSpace.setRight(idx);
1387 else
1388 sampleSpace.setLeft(idx);
1389 } else {
1390 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1391 return sampleSpace;
1392 }
1393
1394 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->min(), searchRow: false, lowBound: true, ascending: ascendingZ);
1395 if (idx != -1) {
1396 if (ascendingZ)
1397 sampleSpace.setTop(idx);
1398 else
1399 sampleSpace.setBottom(idx);
1400 } else {
1401 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1402 return sampleSpace;
1403 }
1404
1405 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->max(), searchRow: false, lowBound: false, ascending: ascendingZ);
1406 if (idx != -1) {
1407 if (ascendingZ)
1408 sampleSpace.setBottom(idx);
1409 else
1410 sampleSpace.setTop(idx);
1411 } else {
1412 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1413 return sampleSpace;
1414 }
1415 }
1416 }
1417 return sampleSpace;
1418}
1419
1420void QQuickGraphsSurface::updateModel(SurfaceModel *model)
1421{
1422 const QSurfaceDataArray &array = model->series->dataArray();
1423
1424 if (!array.isEmpty()) {
1425 Q_TRACE(QGraphs3DSurfaceModelUpdate_entry, static_cast<void *>(model));
1426 qsizetype rowCount = array.size();
1427 qsizetype columnCount = array.at(i: 0).size();
1428
1429 const qsizetype maxSize = 4096; // maximum texture size
1430 columnCount = qMin(a: maxSize, b: columnCount);
1431 rowCount = qMin(a: maxSize, b: rowCount);
1432
1433 bool lineData = (rowCount == 1 || columnCount == 1);
1434
1435 if (model->rowCount != rowCount) {
1436 model->rowCount = rowCount;
1437 setIndexDirty(true);
1438 }
1439 if (model->columnCount != columnCount) {
1440 model->columnCount = columnCount;
1441 setIndexDirty(true);
1442 }
1443
1444 bool dimensionsChanged = false;
1445 QRect sampleSpace = calculateSampleSpace(model);
1446 if (sampleSpace != model->sampleSpace) {
1447 dimensionsChanged = true;
1448 model->sampleSpace = sampleSpace;
1449 }
1450 int rowStart = sampleSpace.top();
1451 int columnStart = sampleSpace.left();
1452 int rowLimit = sampleSpace.bottom() + 1;
1453 int columnLimit = sampleSpace.right() + 1;
1454
1455 QPoint selC = model->selectedVertex.coord;
1456 selC.setX(qMin(a: selC.x(), b: int(columnCount) - 1));
1457 selC.setY(qMin(a: selC.y(), b: int(rowCount) - 1));
1458 QVector3D selP = array.at(i: selC.y()).at(i: selC.x()).position();
1459
1460 bool pickOutOfRange = false;
1461 if (selP.x() < axisX()->min() || selP.x() > axisX()->max() || selP.z() < axisZ()->min()
1462 || selP.z() > axisZ()->max()) {
1463 pickOutOfRange = true;
1464 }
1465
1466 if (m_isIndexDirty || pickOutOfRange) {
1467 model->selectedVertex = SurfaceVertex();
1468 if (sliceView() && sliceView()->isVisible() && model->series == m_selectedSeries) {
1469 setSlicingActive(false);
1470 setSliceActivatedChanged(true);
1471 m_selectionDirty = true;
1472 }
1473 }
1474 qsizetype totalSize = rowCount * columnCount * 2;
1475 float uvX = 1.0f / float(columnCount - 1);
1476 float uvY = 1.0f / float(rowCount - 1);
1477
1478 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1479
1480 QVector3D boundsMin = QVector3D();
1481 QVector3D boundsMax = QVector3D();
1482
1483 model->heights.clear();
1484 model->heights.reserve(asize: totalSize);
1485
1486 QQmlListReference materialRef(model->model, "materials");
1487 auto material = materialRef.at(0);
1488 QVariant heightInputAsVariant = material->property(name: "height");
1489 QQuick3DShaderUtilsTextureInput *heightInput
1490 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1491 QQuick3DTexture *heightMap = heightInput->texture();
1492 QQuick3DTextureData *heightMapData = nullptr;
1493 if (!heightMap) {
1494 heightMap = new QQuick3DTexture();
1495 heightMap->setParent(this);
1496 heightMap->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1497 heightMap->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1498 heightMap->setMinFilter(QQuick3DTexture::Nearest);
1499 heightMap->setMagFilter(QQuick3DTexture::Nearest);
1500 heightMapData = new QQuick3DTextureData();
1501 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1502 heightMapData->setFormat(QQuick3DTextureData::RGBA32F);
1503 heightMapData->setParent(heightMap);
1504 heightMapData->setParentItem(heightMap);
1505 } else {
1506 heightMapData = heightMap->textureData();
1507 if (dimensionsChanged)
1508 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1509 }
1510
1511 if (heightMapData->size().width() < 1 || heightMapData->size().height() < 1) {
1512 heightMapData->setTextureData(QByteArray());
1513 heightMap->setTextureData(heightMapData);
1514 heightInput->setTexture(heightMap);
1515 model->heightTexture = heightMap;
1516 Q_TRACE(QGraphs3DSurfaceModelUpdate_exit);
1517 return;
1518 }
1519
1520 material->setProperty(name: "xDiff", value: 1.0f / float(sampleSpace.width() - 1));
1521 material->setProperty(name: "yDiff", value: 1.0f / float(sampleSpace.height() - 1));
1522 material->setProperty(name: "flatShading", value: flatShading);
1523 material->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1524 material->setProperty(name: "uvOffset", value: QVector2D(columnStart, rowStart));
1525 material->setProperty(name: "size", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1526 material->setProperty(name: "vertCount", value: QVector2D(columnCount, rowCount));
1527 material->setProperty(name: "flipU", value: !model->ascendingX);
1528 material->setProperty(name: "flipV", value: !model->ascendingZ);
1529 material->setProperty(name: "fill", value: model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface));
1530 material->setProperty(name: "lineData", value: lineData);
1531 for (int i = 0; i < m_seriesList.size(); i++) {
1532 if (m_seriesList.at(i) == model->series)
1533 material->setProperty(name: "order", value: i);
1534 }
1535
1536 model->vertices.clear();
1537 model->vertices.reserve(asize: totalSize);
1538
1539 for (int i = rowStart; i < rowLimit; i++) {
1540 const QSurfaceDataRow &row = array.at(i);
1541 for (int j = columnStart; j < columnLimit; j++) {
1542 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1543 model->heights.push_back(t: QVector4D(pos, .0f));
1544 SurfaceVertex vertex;
1545 vertex.position = pos;
1546 vertex.uv = QVector2D(j * uvX, i * uvY);
1547 vertex.coord = QPoint(j, i);
1548 model->vertices.push_back(t: vertex);
1549 if (!qIsNaN(f: pos.y()) && !qIsInf(f: pos.y())) {
1550 if (boundsMin.isNull()) {
1551 boundsMin = pos;
1552 } else {
1553 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: pos.x()),
1554 qMin(a: boundsMin.y(), b: pos.y()),
1555 qMin(a: boundsMin.z(), b: pos.z()));
1556 }
1557 }
1558 if (boundsMax.isNull()) {
1559 boundsMax = pos;
1560 } else {
1561 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: pos.x()),
1562 qMax(a: boundsMax.y(), b: pos.y()),
1563 qMax(a: boundsMax.z(), b: pos.z()));
1564 }
1565 }
1566 }
1567
1568 if (model->boundsMin != boundsMin || model->boundsMax != boundsMax) {
1569 model->boundsMin = boundsMin;
1570 model->boundsMax = boundsMax;
1571 float range = boundsMax.y() - boundsMin.y();
1572 material->setProperty(name: "gradientMin", value: -(boundsMin.y() / range));
1573 material->setProperty(name: "gradientHeight", value: 1.0f / range);
1574 }
1575 QByteArray heightData = QByteArray(reinterpret_cast<char *>(model->heights.data()),
1576 model->heights.size() * sizeof(QVector4D));
1577 heightMapData->setTextureData(heightData);
1578 heightMap->setTextureData(heightMapData);
1579 heightInput->setTexture(heightMap);
1580 model->heightTexture = heightMap;
1581
1582 if (m_isIndexDirty) {
1583 QVector<SurfaceVertex> vertices;
1584 for (int i = 0; i < rowCount; i++) {
1585 QSurfaceDataRow row = array.at(i);
1586 for (int j = 0; j < columnCount; j++) {
1587 SurfaceVertex vertex;
1588 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1589 vertex.position = pos;
1590 float uStep = model->ascendingX ? j * uvX : 1 - (j * uvX);
1591 float vStep = model->ascendingZ ? i * uvY : 1 - (i * uvY);
1592
1593 vertex.uv = QVector2D(uStep, vStep);
1594 vertex.coord = QPoint(j, i);
1595 vertices.push_back(t: vertex);
1596 }
1597 }
1598 if (lineData) {
1599 createLineIndices(model, pointCount: rowCount);
1600 model->model->geometry()->setPrimitiveType(QQuick3DGeometry::PrimitiveType::LineStrip);
1601
1602 } else {
1603 createIndices(model, columnCount, rowCount);
1604 model->model->geometry()->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
1605 }
1606
1607 auto geometry = model->model->geometry();
1608 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()),
1609 vertices.size() * sizeof(SurfaceVertex));
1610 geometry->setVertexData(vertexBuffer);
1611 QByteArray indexBuffer(reinterpret_cast<char *>(model->indices.data()),
1612 model->indices.size() * sizeof(quint32));
1613 geometry->setIndexData(indexBuffer);
1614 geometry->setBounds(min: boundsMin, max: boundsMax);
1615 geometry->update();
1616
1617 createGridlineIndices(model, x: 0, y: 0, endX: columnCount, endY: rowCount);
1618 auto gridGeometry = model->gridModel->geometry();
1619 gridGeometry->setVertexData(vertexBuffer);
1620 QByteArray gridIndexBuffer(reinterpret_cast<char *>(model->gridIndices.data()),
1621 model->gridIndices.size() * sizeof(quint32));
1622 gridGeometry->setIndexData(gridIndexBuffer);
1623 gridGeometry->setBounds(min: boundsMin, max: boundsMax);
1624 gridGeometry->update();
1625 m_fillDirty[model] = true;
1626 m_isIndexDirty = false;
1627 }
1628 QQmlListReference gridMaterialRef(model->gridModel, "materials");
1629 auto gridMaterial = gridMaterialRef.at(0);
1630 QVariant gridHeightInputAsVariant = gridMaterial->property(name: "height");
1631 QQuick3DShaderUtilsTextureInput *gridHeightInput
1632 = gridHeightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1633 gridHeightInput->setTexture(heightMap);
1634 QColor gridColor = model->series->wireframeColor();
1635 gridMaterial->setProperty(name: "gridColor", value: gridColor);
1636 gridMaterial->setProperty(name: "range", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1637 gridMaterial->setProperty(name: "vertices", value: QVector2D(columnCount, rowCount));
1638 gridMaterial->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1639 gridMaterial->setProperty(name: "fill", value: model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface));
1640
1641 qCDebug(lcGraphs3D) << "surface info"
1642 << "\n vertices:" << model->vertices.count()
1643 << "\n indices:" << model->indices.count()
1644 << "\n dataArray dimensions:" << QVector2D(model->series->dataArray().size(), model->series->dataArray().at(i: 0).size())
1645 << "\n plane size:" << QVector2D(sampleSpace.width(), sampleSpace.height())
1646 << "\n vertCount:" << QVector2D(columnCount, rowCount);
1647
1648
1649 if (lineData)
1650 updateLineFill(model);
1651 else
1652 updateFill(model);
1653 Q_TRACE(QGraphs3DSurfaceModelUpdate_exit);
1654 }
1655 updateMaterial(model);
1656 updateSelectedPoint();
1657}
1658
1659void QQuickGraphsSurface::updateFill(SurfaceModel *model)
1660{
1661 bool fillVisible = model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface);
1662 if (m_fillDirty[model] && fillVisible) {
1663 QVector<QVector<SurfaceVertex>> sideVertsList(4);
1664 qsizetype rowCount = model->rowCount;
1665 qsizetype colCount = model->columnCount;
1666
1667 float uvX = 1.0f / float(colCount - 1);
1668 float uvY = 1.0f / float(rowCount - 1);
1669 for (int i = 0; i < rowCount; i++) {
1670 for (int j = 0; j < colCount; j++) {
1671 SurfaceVertex vertex;
1672 QVector3D pos = QVector3D(0, 0, 0);
1673 vertex.position = pos;
1674
1675 vertex.uv = QVector2D(j * uvX, i * uvY);
1676 vertex.coord = QPoint(j, i);
1677 float base = -scaleWithBackground().y();
1678
1679 auto addVerts = [base](SurfaceVertex vertex,
1680 QVector<SurfaceVertex> &sideVerts,
1681 QVector2D uvOffset,
1682 bool rev = false) {
1683 // add small offset to uv to distinguish them in the shaders
1684 vertex.uv += uvOffset * 0.1f;
1685 SurfaceVertex botVert = vertex;
1686 botVert.position.setY(base);
1687 botVert.uv += uvOffset;
1688 if (rev) {
1689 sideVerts.push_front(t: botVert);
1690 sideVerts.push_front(t: vertex);
1691 } else {
1692 sideVerts.push_back(t: vertex);
1693 sideVerts.push_back(t: botVert);
1694 }
1695 };
1696
1697 if (i == 0) { //Bottom row
1698 QVector<SurfaceVertex> &sideVerts = sideVertsList[0];
1699 addVerts(vertex, sideVerts, QVector2D(0.0f, -0.1f));
1700 } else if (j == colCount - 1) {
1701 QVector<SurfaceVertex> &sideVerts = sideVertsList[1];
1702 addVerts(vertex, sideVerts, QVector2D(0.1f, 0.0f));
1703 } else if (i == rowCount - 1) {
1704 QVector<SurfaceVertex> &sideVerts = sideVertsList[2];
1705 addVerts(vertex, sideVerts, QVector2D(0.0f, 0.1f), true);
1706 } else if (j == 0) {
1707 QVector<SurfaceVertex> &sideVerts = sideVertsList[3];
1708 addVerts(vertex, sideVerts, QVector2D(-0.1f, 0.0f), true);
1709 }
1710 }
1711 }
1712
1713 QVector<SurfaceVertex> sideVerts;
1714 for (const auto &side : std::as_const(t&: sideVertsList)) {
1715 for (auto vert : side)
1716 sideVerts.append(t: vert);
1717 }
1718 auto sideGeom = model->fillModel->geometry();
1719 QByteArray fillVertBuffer(reinterpret_cast<char *>(sideVerts.data()),
1720 sideVerts.size() * sizeof(SurfaceVertex));
1721 sideGeom->setVertexData(fillVertBuffer);
1722 sideGeom->setBounds(min: model->boundsMin, max: model->boundsMax);
1723 sideGeom->update();
1724
1725 QVector<quint32> indices;
1726 int totVerts = (rowCount * 2 + colCount * 2) - 4;
1727 int idxCount = 6 * (totVerts);
1728 indices.reserve(asize: idxCount);
1729
1730 //side 1
1731 for (int i = 0; i < totVerts - 1; i++) {
1732 quint32 start = i * 2;
1733 indices.push_back(t: start + 2);
1734 indices.push_back(t: start);
1735 indices.push_back(t: start + 1);
1736
1737 indices.push_back(t: start + 2);
1738 indices.push_back(t: start + 1);
1739 indices.push_back(t: start + 3);
1740 }
1741
1742 //Connecting triangles
1743 quint32 start = (totVerts - 1) * 2;
1744 indices.push_back(t: 0);
1745 indices.push_back(t: start);
1746 indices.push_back(t: start + 1);
1747
1748 indices.push_back(t: 0);
1749 indices.push_back(t: start + 1);
1750 indices.push_back(t: 1);
1751
1752 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
1753 indices.size() * sizeof(quint32));
1754
1755 auto geometry = model->fillModel->geometry();
1756 geometry->setIndexData(indexBuffer);
1757
1758 m_fillDirty[model] = false;
1759 }
1760
1761 bool surfaceVisible = model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface)
1762 && model->series->isVisible();
1763 model->fillModel->setVisible(
1764 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface) && surfaceVisible);
1765}
1766
1767void QQuickGraphsSurface::updateLineFill(SurfaceModel *model)
1768{
1769 bool fillVisible = model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface);
1770 if (m_fillDirty[model] && fillVisible) {
1771 qsizetype rowCount = model->rowCount;
1772 qsizetype colCount = model->columnCount;
1773
1774 bool oneRow = rowCount == 1;
1775
1776 float uvX = 1.0f / qMax(a: float(colCount - 1), b: 1.0f);
1777 float uvY = 1.0f / qMax(a: float(rowCount - 1), b: 1.0f);
1778 QVector<SurfaceVertex> vertices;
1779 for (int i = 0; i < rowCount; i++) {
1780 for (int j = 0; j < colCount; j++) {
1781 SurfaceVertex vertex;
1782 QVector3D pos = QVector3D(0, 0, 0);
1783 vertex.position = pos;
1784
1785 vertex.uv = QVector2D(j * uvX, i * uvY);
1786 vertex.coord = QPoint(j, i);
1787 float base = -scaleWithBackground().y();
1788
1789 QVector2D uvOffset;
1790 if (oneRow)
1791 uvOffset = QVector2D(0.0f, -0.1f);
1792 else
1793 uvOffset = QVector2D(-0.1f, 0.0f);
1794 vertex.uv += uvOffset * 0.1f;
1795
1796 SurfaceVertex botVert = vertex;
1797 botVert.position.setY(base);
1798 botVert.uv += uvOffset;
1799
1800 vertices.push_back(t: vertex);
1801 vertices.push_back(t: botVert);
1802
1803 }
1804 }
1805 QVector<quint32> indices;
1806
1807 int totVerts = oneRow? colCount : rowCount;
1808 int idxCount = 6 * (totVerts);
1809 indices.reserve(asize: idxCount);
1810
1811 for (int i = 0; i < totVerts - 1; i++) {
1812 quint32 start = i * 2 ;
1813 indices.push_back(t: start + 2);
1814 indices.push_back(t: start);
1815 indices.push_back(t: start + 1);
1816
1817 indices.push_back(t: start + 2);
1818 indices.push_back(t: start + 1);
1819 indices.push_back(t: start + 3);
1820 }
1821
1822 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
1823 indices.size() * sizeof(quint32));
1824
1825 auto sideGeom = model->fillModel->geometry();
1826 QByteArray fillVertBuffer(reinterpret_cast<char *>(vertices.data()),
1827 vertices.size() * sizeof(SurfaceVertex));
1828 sideGeom->setVertexData(fillVertBuffer);
1829 sideGeom->setBounds(min: model->boundsMin, max: model->boundsMax);
1830 sideGeom->setIndexData(indexBuffer);
1831 sideGeom->update();
1832 m_fillDirty[model] = false;
1833
1834 }
1835
1836 bool surfaceVisible = model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface)
1837 && model->series->isVisible();
1838 model->fillModel->setVisible(
1839 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawFilledSurface) && surfaceVisible);
1840}
1841
1842void QQuickGraphsSurface::updateMaterial(SurfaceModel *model)
1843{
1844 QQmlListReference materialRef(model->model, "materials");
1845
1846 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
1847
1848 if (!material) {
1849 material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceMaterial"));
1850 model->customMaterial = material;
1851 }
1852
1853 bool textured = !(model->series->texture().isNull() && model->series->textureFile().isEmpty());
1854 material->setProperty(name: "textured", value: textured);
1855
1856 if (isSeriesVisualsDirty()) {
1857 switch (model->series->colorStyle()) {
1858 case (QGraphsTheme::ColorStyle::ObjectGradient):
1859 material->setProperty(name: "colorStyle", value: 0);
1860 break;
1861 case (QGraphsTheme::ColorStyle::RangeGradient):
1862 material->setProperty(name: "colorStyle", value: 1);
1863 break;
1864 case (QGraphsTheme::ColorStyle::Uniform):
1865 material->setProperty(name: "colorStyle", value: 2);
1866 material->setProperty(name: "uniformColor", value: model->series->baseColor());
1867 break;
1868 }
1869
1870 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1871
1872 QVariant textureInputAsVariant = material->property(name: "custex");
1873 QQuick3DShaderUtilsTextureInput *textureInput
1874 = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1875 auto textureData = static_cast<QQuickGraphsTextureData *>(model->gradientTexture->textureData());
1876 textureData->createGradient(gradient: model->series->baseGradient());
1877 textureInput->setTexture(model->gradientTexture);
1878
1879 QVariant heightInputAsVariant = material->property(name: "height");
1880 QQuick3DShaderUtilsTextureInput *heightInput
1881 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1882 heightInput->setTexture(model->heightTexture);
1883 material->setParent(model->model);
1884 material->setParentItem(model->model);
1885 material->setCullMode(QQuick3DMaterial::NoCulling);
1886 material->setProperty(name: "flatShading", value: flatShading);
1887 material->setProperty(name: "shaded",
1888 value: model->series->lightingMode()
1889 == QAbstract3DSeries::LightingMode::Shaded);
1890 }
1891
1892 if (textured) {
1893 QQuick3DShaderUtilsTextureInput *texInput = material->property(name: "baseColor")
1894 .value<QQuick3DShaderUtilsTextureInput *>();
1895 if (!texInput->texture()) {
1896 QQuick3DTexture *texture = new QQuick3DTexture();
1897 texture->setParent(material);
1898 texture->setParentItem(material);
1899 texInput->setTexture(texture);
1900 }
1901 if (!model->series->textureFile().isEmpty()) {
1902 texInput->texture()->setSource(QUrl::fromLocalFile(localfile: model->series->textureFile()));
1903 } else if (!model->series->texture().isNull()) {
1904 QImage image = model->series->texture();
1905 image.convertTo(f: QImage::Format_RGBA32FPx4);
1906 auto textureData = static_cast<QQuickGraphsTextureData *>(model->texture->textureData());
1907 textureData->setFormat(QQuick3DTextureData::RGBA32F);
1908 textureData->setSize(image.size());
1909 textureData->setTextureData(
1910 QByteArray(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes()));
1911 texInput->texture()->setTextureData(textureData);
1912 texInput->texture()->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1913 texInput->texture()->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1914
1915 } else {
1916 texInput->texture()->setSource(QUrl());
1917 }
1918 }
1919 material->setProperty(name: "rootScale", value: rootNode()->scale().y() * scaleWithBackground().y());
1920
1921 bool colorHasTransparency = false;
1922 if (model->series->colorStyle() == QGraphsTheme::ColorStyle::Uniform)
1923 colorHasTransparency = model->series->baseColor().alphaF() < 1.0;
1924 else
1925 colorHasTransparency = model->gradientTexture->textureData()->hasTransparency();
1926
1927 bool textureHasTransparency = model->texture->textureData()->hasTransparency();
1928
1929 material->setProperty(name: "hasTransparency", value: colorHasTransparency || textureHasTransparency);
1930 material->update();
1931}
1932
1933QVector3D QQuickGraphsSurface::getNormalizedVertex(const QSurfaceDataItem &data,
1934 bool polar,
1935 bool flipXZ)
1936{
1937 Q_UNUSED(flipXZ);
1938
1939 QValue3DAxis *axisXValue = static_cast<QValue3DAxis *>(axisX());
1940 QValue3DAxis *axisYValue = static_cast<QValue3DAxis *>(axisY());
1941 QValue3DAxis *axisZValue = static_cast<QValue3DAxis *>(axisZ());
1942
1943 float normalizedX = axisXValue->positionAt(x: data.x());
1944 float normalizedY;
1945 float normalizedZ = axisZValue->positionAt(x: data.z());
1946 // TODO : Need to handle, flipXZ
1947
1948 float scale, translate;
1949 if (polar) {
1950 float angle = normalizedX * M_PI * 2.0f;
1951 float radius = normalizedZ * this->scaleWithBackground().z();
1952 normalizedX = radius * qSin(v: angle) * 1.0f;
1953 normalizedZ = -(radius * qCos(v: angle)) * 1.0f;
1954 } else {
1955 scale = translate = this->scaleWithBackground().x();
1956 normalizedX = normalizedX * scale * 2.0f - translate;
1957 scale = translate = this->scaleWithBackground().z();
1958 normalizedZ = normalizedZ * -scale * 2.0f + translate;
1959 }
1960 scale = translate = this->scale().y();
1961 normalizedY = axisYValue->positionAt(x: data.y()) * scale * 2.0f - translate;
1962 return QVector3D(normalizedX, normalizedY, normalizedZ);
1963}
1964
1965void QQuickGraphsSurface::toggleSliceGraph()
1966{
1967 if (m_selectionDirty)
1968 QQuickGraphsItem::toggleSliceGraph();
1969
1970 setSelectedPointChanged(true);
1971
1972 if (!sliceView()->isVisible())
1973 return;
1974
1975 QPointF worldCoord;
1976 for (auto model : std::as_const(t&: m_model)) {
1977 if (model->picked) {
1978 QPoint coords = model->selectedVertex.coord;
1979 worldCoord = mapCoordsToWorldSpace(model, coords);
1980 }
1981 }
1982
1983 for (auto model : std::as_const(t&: m_model)) {
1984 bool visible = model->series->isVisible();
1985
1986 model->sliceModel->setVisible(visible);
1987 model->sliceGridModel->setVisible(visible);
1988
1989 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked) {
1990 model->sliceModel->setVisible(false);
1991 model->sliceGridModel->setVisible(false);
1992 continue;
1993 } else {
1994 model->sliceGridModel->setVisible(
1995 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1996 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1997 model->sliceModel->setLocalOpacity(1.f);
1998 else
1999 model->sliceModel->setLocalOpacity(.0f);
2000 }
2001
2002 QVector<SurfaceVertex> selectedSeries;
2003
2004 QRect sampleSpace = model->sampleSpace;
2005 int rowStart = sampleSpace.top();
2006 int columnStart = sampleSpace.left();
2007 int rowEnd = sampleSpace.bottom() + 1;
2008 int columnEnd = sampleSpace.right() + 1;
2009 int rowCount = sampleSpace.height();
2010 int columnCount = sampleSpace.width();
2011
2012 QPoint coord;
2013 if (model->picked)
2014 coord = model->selectedVertex.coord;
2015 else
2016 coord = mapCoordsToSampleSpace(model, coords: worldCoord);
2017
2018 int indexCount = 0;
2019 const QSurfaceDataArray &array = model->series->dataArray();
2020 const qsizetype maxRow = array.size() - 1;
2021 const qsizetype maxColumn = array.at(i: 0).size() - 1;
2022 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
2023 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
2024 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row) && coord.y() != -1) {
2025 selectedSeries.reserve(asize: columnCount * 2);
2026 QVector<SurfaceVertex> list;
2027 QSurfaceDataRow row = array.at(i: coord.y());
2028 for (int i = columnStart; i < columnEnd; i++) {
2029 int index = ascendingX ? i : columnEnd - i + columnStart - 1;
2030 QVector3D pos = getNormalizedVertex(data: row.at(i: index), polar: false, flipXZ: false);
2031 SurfaceVertex vertex;
2032 vertex.position = pos;
2033 vertex.position.setY(vertex.position.y() - .025f);
2034 vertex.position.setZ(.0f);
2035 selectedSeries.append(t: vertex);
2036 vertex.position.setY(vertex.position.y() + .05f);
2037 list.append(t: vertex);
2038 }
2039 selectedSeries.append(l: list);
2040 indexCount = columnCount - 1;
2041 }
2042
2043 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column) && coord.x() != -1) {
2044 selectedSeries.reserve(asize: rowCount * 2);
2045 QVector<SurfaceVertex> list;
2046 for (int i = rowStart; i < rowEnd; i++) {
2047 int index = ascendingZ ? i : rowEnd - i + rowStart - 1;
2048 QVector3D pos = getNormalizedVertex(data: array.at(i: index).at(i: coord.x()), polar: false, flipXZ: false);
2049 SurfaceVertex vertex;
2050 vertex.position = pos;
2051 vertex.position.setX(-vertex.position.z());
2052 vertex.position.setY(vertex.position.y() - .025f);
2053 vertex.position.setZ(0);
2054 selectedSeries.append(t: vertex);
2055 vertex.position.setY(vertex.position.y() + .05f);
2056 list.append(t: vertex);
2057 }
2058 selectedSeries.append(l: list);
2059 indexCount = rowCount - 1;
2060
2061 QQmlListReference materialRef(model->sliceModel, "materials");
2062 auto material = materialRef.at(0);
2063 material->setProperty(name: "isColumn", value: true);
2064 }
2065
2066 QVector<quint32> indices;
2067 indices.reserve(asize: indexCount * 6);
2068 for (int i = 0; i < indexCount; i++) {
2069 indices.push_back(t: i + 1);
2070 indices.push_back(t: i + indexCount + 1);
2071 indices.push_back(t: i);
2072 indices.push_back(t: i + indexCount + 2);
2073 indices.push_back(t: i + indexCount + 1);
2074 indices.push_back(t: i + 1);
2075 }
2076
2077 auto geometry = model->sliceModel->geometry();
2078 QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
2079 selectedSeries.size() * sizeof(SurfaceVertex));
2080 geometry->setVertexData(vertexBuffer);
2081 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
2082 indices.size() * sizeof(quint32));
2083 geometry->setIndexData(indexBuffer);
2084 geometry->update();
2085
2086 geometry = model->sliceGridModel->geometry();
2087 geometry->setVertexData(vertexBuffer);
2088
2089 QVector<quint32> gridIndices;
2090 gridIndices.reserve(asize: indexCount * 4);
2091 for (int i = 0; i < indexCount; i++) {
2092 gridIndices.push_back(t: i);
2093 gridIndices.push_back(t: i + indexCount + 1);
2094
2095 gridIndices.push_back(t: i);
2096 gridIndices.push_back(t: i + 1);
2097 }
2098 QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
2099 gridIndices.size() * sizeof(quint32));
2100 geometry->setIndexData(gridIndexBuffer);
2101 geometry->update();
2102
2103 QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
2104 auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
2105 QColor gridColor = model->series->wireframeColor();
2106 gridMaterial->setBaseColor(gridColor);
2107
2108 updateSelectedPoint();
2109 }
2110}
2111
2112QPointF QQuickGraphsSurface::mapCoordsToWorldSpace(SurfaceModel *model, QPointF coords)
2113{
2114 const QSurfaceDataArray &array = model->series->dataArray();
2115 QSurfaceDataItem item = array.at(i: coords.y()).at(i: coords.x());
2116 return QPointF(item.x(), item.z());
2117}
2118
2119QPoint QQuickGraphsSurface::mapCoordsToSampleSpace(SurfaceModel *model, QPointF coords)
2120{
2121 const QSurfaceDataArray &array = model->series->dataArray();
2122 qsizetype maxRow = array.size() - 1;
2123 qsizetype maxCol = array.at(i: 0).size() - 1;
2124 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxCol).x();
2125 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
2126 qsizetype botX = ascendingX ? 0 : maxCol;
2127 qsizetype botZ = ascendingZ ? 0 : maxRow;
2128 qsizetype topX = ascendingX ? maxCol : 0;
2129 qsizetype topZ = ascendingZ ? maxRow : 0;
2130
2131 QPoint point(-1, -1);
2132
2133 QSurfaceDataItem bottomLeft = array.at(i: botZ).at(i: botX);
2134 QSurfaceDataItem topRight = array.at(i: topZ).at(i: topX);
2135
2136 QPointF pointBL(bottomLeft.x(), bottomLeft.z());
2137 QPointF pointTR(topRight.x(), topRight.z());
2138
2139 QPointF pointF = coords - pointBL;
2140 QPointF span = pointTR - pointBL;
2141 QPointF step = QPointF(span.x() / float(maxCol), span.y() / float(maxRow));
2142 QPoint sample = QPoint((pointF.x() + (step.x() / 2.0)) / step.x(),
2143 (pointF.y() + (step.y() / 2.0)) / step.y());
2144
2145 if (bottomLeft.x() <= coords.x() && topRight.x() >= coords.x())
2146 point.setX(ascendingX ? sample.x() : int(maxCol) - sample.x());
2147
2148 if (bottomLeft.z() <= coords.y() && topRight.z() >= coords.y())
2149 point.setY(ascendingZ ? sample.y() : int(maxRow) - sample.y());
2150 return point;
2151}
2152
2153void QQuickGraphsSurface::createIndices(SurfaceModel *model, qsizetype columnCount, qsizetype rowCount)
2154{
2155 qsizetype endX = columnCount - 1;
2156 qsizetype endY = rowCount - 1;
2157
2158 qsizetype indexCount = 6 * endX * endY;
2159 QVector<quint32> *indices = &model->indices;
2160
2161 indices->clear();
2162 indices->reserve(asize: indexCount);
2163
2164 qsizetype rowEnd = endY * columnCount;
2165 for (qsizetype row = 0; row < rowEnd; row += columnCount) {
2166 for (qsizetype j = 0; j < endX; j++) {
2167 indices->push_back(t: int(row + j + 1));
2168 indices->push_back(t: int(row + columnCount + j));
2169 indices->push_back(t: int(row + j));
2170
2171 indices->push_back(t: int(row + columnCount + j + 1));
2172 indices->push_back(t: int(row + columnCount + j));
2173 indices->push_back(t: int(row + j + 1));
2174 }
2175 }
2176}
2177
2178void QQuickGraphsSurface::createLineIndices(SurfaceModel *model, qsizetype pointCount)
2179{
2180 model->indices.clear();
2181 model->gridIndices.reserve(asize: pointCount);
2182 for (int i = 0; i < pointCount; i++)
2183 model->indices.push_back(t: i);
2184}
2185
2186void QQuickGraphsSurface::createGridlineIndices(SurfaceModel *model, qsizetype x, qsizetype y, qsizetype endX, qsizetype endY)
2187{
2188 qsizetype columnCount = model->columnCount;
2189 qsizetype rowCount = model->rowCount;
2190
2191 if (endX >= columnCount)
2192 endX = columnCount - 1;
2193 if (endY >= rowCount)
2194 endY = rowCount - 1;
2195 if (x > endX)
2196 x = endX - 1;
2197 if (y > endY)
2198 y = endY - 1;
2199
2200 qsizetype nColumns = endX - x + 1;
2201 qsizetype nRows = endY - y + 1;
2202
2203 qsizetype gridIndexCount = 2 * nColumns * (nRows - 1) + 2 * nRows * (nColumns - 1);
2204 model->gridIndices.clear();
2205 model->gridIndices.reserve(asize: gridIndexCount);
2206
2207 for (qsizetype i = y, row = columnCount * y; i <= endY; i++, row += columnCount) {
2208 for (qsizetype j = x; j < endX; j++) {
2209 model->gridIndices.push_back(t: int(row + j));
2210 model->gridIndices.push_back(t: int(row + j + 1));
2211 }
2212 }
2213 for (qsizetype i = y, row = columnCount * y; i < endY; i++, row += columnCount) {
2214 for (qsizetype j = x; j <= endX; j++) {
2215 model->gridIndices.push_back(t: int(row + j));
2216 model->gridIndices.push_back(t: int(row + j + columnCount));
2217 }
2218 }
2219}
2220
2221bool QQuickGraphsSurface::doPicking(QPointF position)
2222{
2223 if (!QQuickGraphsItem::doPicking(point: position))
2224 return false;
2225 Q_TRACE(QGraphs3DSurfaceDoPicking_entry, position.x(), position.y());
2226 m_selectionDirty = true;
2227
2228 SurfaceModel *pickedModel = nullptr;
2229 const QVector3D rayOrigin = rootNode()->mapPositionFromScene(scenePosition: mapTo3DScene(viewPos: QVector3D(position.x(), position.y(), 0)));
2230 const QVector3D rayEnd = rootNode()->mapPositionFromScene(scenePosition: mapTo3DScene(viewPos: QVector3D(position.x(), position.y(), 1)));
2231 const QVector3D rayDir = (rayEnd - rayOrigin).normalized();
2232 QVector3D pickedPos = pickSurfaces(rayOrigin, rayDir, pickedModel);
2233
2234 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
2235 if (!sliceView() && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice))
2236 createSliceView();
2237
2238 bool inBounds = qAbs(t: pickedPos.y()) < scaleWithBackground().y();
2239 if (!pickedPos.isNull() && inBounds) {
2240
2241 bool inRange = qAbs(t: pickedPos.x()) < scaleWithBackground().x()
2242 && qAbs(t: pickedPos.z()) < scaleWithBackground().z();
2243
2244 if (!pickedPos.isNull() && inRange) {
2245 for (auto model : std::as_const(t&: m_model)) {
2246 if (!model->series->isVisible()) {
2247 model->picked = false;
2248 continue;
2249 }
2250
2251 model->picked = (model == pickedModel);
2252
2253 SurfaceVertex selectedVertex;
2254 qreal min = std::numeric_limits<qreal>::max();
2255
2256 for (auto vertex : std::as_const(t&: model->vertices)) {
2257 QVector3D pos = vertex.position;
2258 float dist = pickedPos.distanceToPoint(point: pos);
2259 if (dist < min) {
2260 min = dist;
2261 selectedVertex = vertex;
2262 }
2263 }
2264 model->selectedVertex = selectedVertex;
2265 if (!selectedVertex.position.isNull() && model->picked) {
2266 model->series->setSelectedPoint(selectedVertex.coord);
2267 setSlicingActive(false);
2268 qCDebug(lcInput3D) << "pick results:"
2269 << "\n instance position:" << selectedVertex.position
2270 << "\n picked vertices coords:" << selectedVertex.coord
2271 << "\n picked vertices values:" << model->series->dataProxy()->itemAt(position: selectedVertex.coord).position();
2272 if (isSliceEnabled())
2273 setSliceActivatedChanged(true);
2274 }
2275 }
2276 }
2277 } else {
2278 clearSelection();
2279 for (auto model : std::as_const(t&: m_model))
2280 model->picked = false;
2281 }
2282 }
2283 Q_TRACE(QGraphs3DSurfaceDoPicking_exit);
2284 return true;
2285}
2286
2287bool QQuickGraphsSurface::doRayPicking(QVector3D origin, QVector3D direction)
2288{
2289 if (!QQuickGraphsItem::doRayPicking(origin, direction))
2290 return false;
2291
2292 Q_TRACE(QGraphs3DSurfaceDoRayPicking_entry, origin.x(), origin.y(), origin.z(), direction.x(),
2293 direction.y(), direction.z());
2294 m_selectionDirty = true;
2295
2296 SurfaceModel *pickedModel = nullptr;
2297 QVector3D pickedPos = pickSurfaces(rayOrigin: origin, rayDir: direction, pickedModel);
2298
2299 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
2300 if (!sliceView() && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice))
2301 createSliceView();
2302
2303 bool inBounds = qAbs(t: pickedPos.y()) < scaleWithBackground().y();
2304 if (!pickedPos.isNull() && inBounds) {
2305
2306 bool inRange = qAbs(t: pickedPos.x()) < scaleWithBackground().x()
2307 && qAbs(t: pickedPos.z()) < scaleWithBackground().z();
2308
2309 if (!pickedPos.isNull() && inRange) {
2310 for (auto model : std::as_const(t&: m_model)) {
2311 if (!model->series->isVisible()) {
2312 model->picked = false;
2313 continue;
2314 }
2315 model->picked = (model == pickedModel);
2316
2317 SurfaceVertex selectedVertex;
2318 qreal min = std::numeric_limits<qreal>::max();
2319
2320 for (auto vertex : std::as_const(t&: model->vertices)) {
2321 QVector3D pos = vertex.position;
2322 float dist = pickedPos.distanceToPoint(point: pos);
2323 if (dist < min) {
2324 min = dist;
2325 selectedVertex = vertex;
2326 }
2327 }
2328 model->selectedVertex = selectedVertex;
2329 if (!selectedVertex.position.isNull() && model->picked) {
2330 model->series->setSelectedPoint(selectedVertex.coord);
2331 setSlicingActive(false);
2332 qCDebug(lcInput3D) << "pick results:"
2333 << "\n instance position:" << selectedVertex.position
2334 << "\n picked vertices coords:" << selectedVertex.coord
2335 << "\n picked vertices values:" << model->series->dataProxy()->itemAt(position: selectedVertex.coord).position();
2336 if (isSliceEnabled())
2337 setSliceActivatedChanged(true);
2338 }
2339 }
2340 }
2341 } else {
2342 clearSelection();
2343 for (auto model : std::as_const(t&: m_model))
2344 model->picked = false;
2345 }
2346 }
2347 Q_TRACE(QGraphs3DSurfaceDoRayPicking_exit);
2348 return true;
2349}
2350
2351QVector3D QQuickGraphsSurface::pickSurfaces(QVector3D rayOrigin,
2352 QVector3D rayDir,
2353 SurfaceModel *&pickedModel)
2354{
2355 qreal closestDistance = std::numeric_limits<qreal>::max();
2356 qreal closestLineDistance = std::numeric_limits<qreal>::max();
2357
2358 QVector3D pickedPosition;
2359
2360 QVector3D pickedLinePosition;
2361 SurfaceModel *pickedLineModel = nullptr;
2362
2363 for (auto model : m_model) {
2364 qsizetype columnCount = model->sampleSpace.width();
2365 qsizetype rowCount = model->sampleSpace.height();
2366 bool lineData = (rowCount == 1 || columnCount == 1);
2367
2368 if (!model->series->isVisible())
2369 continue;
2370
2371 if (lineData) {
2372 QVector3D linePos;
2373 for (auto vertex : model->vertices) {
2374 QVector3D v = vertex.position - rayOrigin;
2375 qreal t = QVector3D::dotProduct(v1: v, v2: rayDir);
2376 QVector3D closestRayPos = rayOrigin + rayDir * t;
2377 qreal distance = closestRayPos.distanceToPoint(point: vertex.position);
2378 if (distance < closestLineDistance) {
2379 linePos = vertex.position;
2380 closestLineDistance = distance;
2381 }
2382 }
2383 // check distance against reasonable pick radius
2384 /*After testing, 0.05 gives a good error margin for
2385 picking, without casuing false positives
2386 from clicks for surrounding points */
2387
2388 qreal pickRadius = 0.05f;
2389 if (closestLineDistance < pickRadius) {
2390 pickedLinePosition = linePos;
2391 pickedLineModel = model;
2392 }
2393 continue;
2394 }
2395
2396 if (!intersectWithAABB(boundMin: model->boundsMin, boundsMax: model->boundsMax, origin: rayOrigin, dir: rayDir))
2397 continue;
2398
2399 qsizetype endX = columnCount - 1;
2400 qsizetype endY = rowCount - 1;
2401
2402 qsizetype rowEnd = endY * columnCount;
2403 for (qsizetype row = 0; row < rowEnd; row += columnCount) {
2404 for (qsizetype j = 0; j < endX; j++) {
2405 std::array<QVector3D, 3> triangle;
2406 triangle[0] = model->heights.at(i: int(row + j + 1)).toVector3D();
2407 triangle[1] = model->heights.at(i: int(row + columnCount + j)).toVector3D();
2408 triangle[2] = model->heights.at(i: int(row + j)).toVector3D();
2409 QVector3D intersection = triangleIntersection(origin: rayOrigin, dir: rayDir, triangle);
2410 if (!intersection.isNull()) {
2411 qreal distance = rayOrigin.distanceToPoint(point: intersection);
2412 if (distance < closestDistance) {
2413 pickedPosition = intersection;
2414 pickedModel = model;
2415 closestDistance = distance;
2416 }
2417 }
2418
2419 triangle[0] = model->heights.at(i: int(row + columnCount + j + 1)).toVector3D();
2420 triangle[1] = model->heights.at(i: int(row + columnCount + j)).toVector3D();
2421 triangle[2] = model->heights.at(i: int(row + j + 1)).toVector3D();
2422 intersection = triangleIntersection(origin: rayOrigin, dir: rayDir, triangle);
2423 if (!intersection.isNull()) {
2424 qreal distance = rayOrigin.distanceToPoint(point: intersection);
2425 if (distance < closestDistance) {
2426 pickedPosition = intersection;
2427 pickedModel = model;
2428 closestDistance = distance;
2429 }
2430 }
2431 }
2432 }
2433 }
2434 // compare line and surface positions
2435 if (!pickedLinePosition.isNull() && pickedLineModel) {
2436 qreal lineDistance = rayOrigin.distanceToPoint(point: pickedLinePosition);
2437 if (lineDistance < closestDistance) {
2438 pickedPosition = pickedLinePosition;
2439 pickedModel = pickedLineModel;
2440 }
2441 }
2442 return pickedPosition;
2443}
2444
2445// Möller-Trumbore ray-triangle intersection
2446QVector3D QQuickGraphsSurface::triangleIntersection(QVector3D origin,
2447 QVector3D dir,
2448 const std::array<QVector3D, 3> &triangle)
2449{
2450 constexpr qreal epsilon = std::numeric_limits<float>::epsilon();
2451
2452 const QVector3D edge1 = triangle[1] - triangle[0];
2453 const QVector3D edge2 = triangle[2] - triangle[0];
2454
2455 // Compute the vector P as the cross product of the ray direction and edge2
2456 const QVector3D P = QVector3D::crossProduct(v1: dir, v2: edge2);
2457
2458 // Compute the determinant
2459 const float determinant = QVector3D::dotProduct(v1: edge1, v2: P);
2460
2461 QVector3D Q;
2462 qreal u;
2463 qreal v;
2464
2465 if (determinant > epsilon) {
2466 // Compute the vector T from the ray origin to the first vertex of the triangle
2467 const QVector3D T = origin - triangle[0];
2468
2469 // Calculate coordinate u and test bounds
2470 u = QVector3D::dotProduct(v1: T, v2: P);
2471 if (u < 0.0f || u > determinant)
2472 return QVector3D();
2473
2474 // Compute the vector Q as the cross product of vector T and edge1
2475 Q = QVector3D::crossProduct(v1: T, v2: edge1);
2476
2477 // Calculate coordinate v and test bounds
2478 v = QVector3D::dotProduct(v1: dir, v2: Q);
2479 if (v < 0.0f || ((u + v) > determinant))
2480 return QVector3D();
2481 } else if (determinant < -epsilon) { //Backfaces
2482 // Compute the vector T from the ray origin to the first vertex of the triangle
2483 const QVector3D T = origin - triangle[0];
2484
2485 // Calculate coordinate u and test bounds
2486 u = QVector3D::dotProduct(v1: T, v2: P);
2487 if (u > 0.0f || u < determinant)
2488 return QVector3D();
2489
2490 // Compute the vector Q as the cross product of vector T and edge1
2491 Q = QVector3D::crossProduct(v1: T, v2: edge1);
2492
2493 // Calculate coordinate v and test bounds
2494 v = QVector3D::dotProduct(v1: dir, v2: Q);
2495 if (v > 0.0f || ((u + v) < determinant))
2496 return QVector3D();
2497 } else {
2498 // Ray is parallel to the plane of the triangle
2499 return QVector3D();
2500 }
2501
2502 const float invDeterminant = 1.0f / determinant;
2503
2504 // Calculate the value of t, the parameter of the intersection point along the ray
2505 const float t = QVector3D::dotProduct(v1: edge2, v2: Q) * invDeterminant;
2506
2507 if (t > epsilon)
2508 return origin + dir * t;
2509
2510 return QVector3D();
2511}
2512
2513bool QQuickGraphsSurface::intersectWithAABB(QVector3D boundsMin,
2514 QVector3D boundsMax,
2515 QVector3D rayOrigin,
2516 QVector3D rayDir)
2517{
2518 float tmax = std::numeric_limits<float>::max();
2519 float tmin = std::numeric_limits<float>::min();
2520 float origin;
2521
2522 for (int axis = 0; axis < 3; ++axis) {
2523 origin = rayOrigin[axis];
2524 const bool zeroDir = rayDir[axis] == 0;
2525
2526 // Pickray is roughly parallel to the plane of the slab
2527 // so, if the origin is not in the range, we have no intersection
2528 if (zeroDir && (origin < boundsMin[axis] || origin > boundsMax[axis]))
2529 return false;
2530
2531 if (!zeroDir) {
2532 float t1 = (boundsMin[axis] - origin) / rayDir[axis];
2533 float t2 = (boundsMax[axis] - origin) / rayDir[axis];
2534
2535 tmin = qMax(a: tmin, b: qMin(a: t1, b: t2));
2536 tmax = qMin(a: tmax, b: qMax(a: t1, b: t2));
2537 }
2538 }
2539
2540 return tmin < tmax;
2541}
2542
2543void QQuickGraphsSurface::updateSelectedPoint()
2544{
2545 bool labelVisible = false;
2546
2547 auto list = surfaceSeriesList();
2548 for (auto series : std::as_const(t&: list)) {
2549 // If the pointer and its instancing do not exist yet (as will happen in widget case),
2550 // we must create them
2551 if (!m_selectionPointers.value(key: series))
2552 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
2553
2554 if (sliceView() && !m_sliceSelectionPointers.value(key: series))
2555 changeSlicePointerMeshTypeForSeries(mesh: series->mesh(), series);
2556 }
2557
2558 for (auto i = m_selectionPointers.keyValueBegin(); i != m_selectionPointers.keyValueEnd(); i++)
2559 i->second->setVisible(false);
2560
2561 if (sliceView() && sliceView()->isVisible()) {
2562 for (auto i = m_sliceSelectionPointers.keyValueBegin();
2563 i != m_sliceSelectionPointers.keyValueEnd();
2564 i++) {
2565 i->second->setVisible(false);
2566 }
2567 }
2568
2569 QPointF worldCoord;
2570 for (auto model : std::as_const(t&: m_model)) {
2571 if (model->picked) {
2572 QPoint coords = model->selectedVertex.coord;
2573 worldCoord = mapCoordsToWorldSpace(model, coords);
2574 }
2575 }
2576 for (auto model : std::as_const(t&: m_model)) {
2577 Q_TRACE(QGraphs3DSurfacePointSelectionUpdate_entry,static_cast<void *>( model));
2578
2579 if ((!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked)
2580 || model->selectedVertex.position.isNull()) {
2581 Q_TRACE(QGraphs3DSurfacePointSelectionUpdate_exit);
2582 continue;
2583 }
2584 QPoint selectedCoord;
2585 if (model->picked)
2586 selectedCoord = model->selectedVertex.coord;
2587 else
2588 selectedCoord = mapCoordsToSampleSpace(model, coords: worldCoord);
2589 if (selectedCoord.x() == -1 || selectedCoord.y() == -1) {
2590 Q_TRACE(QGraphs3DSurfacePointSelectionUpdate_exit);
2591 continue;
2592 }
2593
2594 const QSurfaceDataItem &dataPos
2595 = model->series->dataArray().at(i: selectedCoord.y()).at(i: selectedCoord.x());
2596 QVector3D pos = getNormalizedVertex(data: dataPos, polar: isPolar(), flipXZ: false);
2597
2598 SurfaceVertex selectedVertex;
2599 selectedVertex.position = pos;
2600 selectedVertex.coord = model->selectedVertex.coord;
2601 if (model->series->isVisible() && !selectedVertex.position.isNull()
2602 && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item)) {
2603 m_selectionPointers.value(key: model->series)->setPosition(selectedVertex.position);
2604 m_selectionPointers.value(key: model->series)->setVisible(true);
2605 QVector3D slicePosition = getNormalizedVertex(data: dataPos, polar: false, flipXZ: false);
2606 if (sliceView() && sliceView()->isVisible()) {
2607 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2608 slicePosition.setX(-slicePosition.z());
2609 slicePosition.setZ(.0f);
2610 m_sliceSelectionPointers.value(key: model->series)->setPosition(slicePosition);
2611 m_sliceSelectionPointers.value(key: model->series)->setVisible(true);
2612 }
2613 if (model->picked) {
2614 QVector3D labelPosition = selectedVertex.position;
2615 QString label = model->series->itemLabel();
2616 setSelectedPoint(position: selectedVertex.coord, series: model->series, enterSlice: false);
2617
2618 updateItemLabel(position: labelPosition);
2619 itemLabel()->setProperty(name: "labelText", value: label);
2620 if (!label.compare(s: QString(hiddenLabelTag)))
2621 itemLabel()->setVisible(false);
2622 labelVisible = model->series->isItemLabelVisible();
2623 if (sliceView() && sliceView()->isVisible())
2624 updateSliceItemLabel(label, position: slicePosition);
2625 }
2626 }
2627 Q_TRACE(QGraphs3DSurfacePointSelectionUpdate_exit);
2628 }
2629 setItemSelected(m_selectedSeries != nullptr);
2630 itemLabel()->setVisible(labelVisible);
2631 if (sliceView() && sliceView()->isVisible())
2632 sliceItemLabel()->setVisible(labelVisible);
2633}
2634
2635void QQuickGraphsSurface::addModel(QSurface3DSeries *series)
2636{
2637 Q_TRACE_SCOPE(QGraphs3DSurfaceAddModel, series);
2638 auto parent = graphNode();
2639 bool visible = series->isVisible();
2640
2641 auto model = new QQuick3DModel();
2642 model->setParent(parent);
2643 model->setParentItem(parent);
2644 model->setObjectName(QStringLiteral("SurfaceModel"));
2645 model->setVisible(visible);
2646 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
2647 model->setPickable(false);
2648 else
2649 model->setPickable(true);
2650
2651 auto geometry = new QQuick3DGeometry();
2652 geometry->setParent(model);
2653 geometry->setStride(sizeof(SurfaceVertex));
2654 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2655 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2656 offset: 0,
2657 componentType: QQuick3DGeometry::Attribute::F32Type);
2658 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2659 offset: sizeof(QVector3D),
2660 componentType: QQuick3DGeometry::Attribute::F32Type);
2661 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2662 offset: 0,
2663 componentType: QQuick3DGeometry::Attribute::U32Type);
2664 model->setGeometry(geometry);
2665
2666 model->setCastsShadows(false); //Disable shadows as they render incorrectly
2667
2668 QQuick3DTexture *texture = new QQuick3DTexture();
2669 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
2670 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
2671 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
2672 textureData->setParent(texture);
2673 textureData->setParentItem(texture);
2674 texture->setTextureData(textureData);
2675
2676 QQuick3DTexture *gradientTexture = new QQuick3DTexture();
2677 gradientTexture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
2678 gradientTexture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
2679 QQuickGraphsTextureData *gradientTextureData = new QQuickGraphsTextureData();
2680 textureData->setParent(gradientTexture);
2681 textureData->setParentItem(gradientTexture);
2682 gradientTexture->setTextureData(gradientTextureData);
2683
2684 QQmlListReference materialRef(model, "materials");
2685
2686 QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
2687 QStringLiteral(":/materials/SurfaceMaterial"));
2688
2689 customMaterial->setParent(model);
2690 customMaterial->setParentItem(model);
2691 customMaterial->setCullMode(QQuick3DMaterial::NoCulling);
2692 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
2693 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
2694 .value<QQuick3DShaderUtilsTextureInput *>();
2695 textureInput->setTexture(texture);
2696
2697 texture->setParent(customMaterial);
2698
2699 materialRef.append(customMaterial);
2700
2701 auto gridModel = new QQuick3DModel();
2702 gridModel->setParent(parent);
2703 gridModel->setParentItem(parent);
2704 gridModel->setObjectName(QStringLiteral("SurfaceModel"));
2705 gridModel->setVisible(visible);
2706 gridModel->setDepthBias(1.0f);
2707 auto gridGeometry = new QQuick3DGeometry();
2708 gridGeometry->setParent(this);
2709 gridGeometry->setStride(sizeof(SurfaceVertex));
2710 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2711 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2712 offset: 0,
2713 componentType: QQuick3DGeometry::Attribute::F32Type);
2714 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2715 offset: sizeof(QVector3D),
2716 componentType: QQuick3DGeometry::Attribute::F32Type);
2717 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2718 offset: 0,
2719 componentType: QQuick3DGeometry::Attribute::U32Type);
2720 gridModel->setGeometry(gridGeometry);
2721 gridModel->setCastsShadows(false);
2722 QQmlListReference gridMaterialRef(gridModel, "materials");
2723 auto gridMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/GridSurfaceMaterial"));
2724 gridMaterial->setParent(gridModel);
2725 gridMaterial->setParentItem(gridModel);
2726 gridMaterialRef.append(gridMaterial);
2727
2728 SurfaceModel *surfaceModel = new SurfaceModel();
2729 surfaceModel->model = model;
2730 surfaceModel->gridModel = gridModel;
2731 surfaceModel->series = series;
2732 surfaceModel->texture = texture;
2733 surfaceModel->gradientTexture = gradientTexture;
2734 surfaceModel->customMaterial = customMaterial;
2735
2736 m_model.push_back(t: surfaceModel);
2737
2738 connect(sender: series,
2739 signal: &QSurface3DSeries::shadingChanged,
2740 context: this,
2741 slot: &QQuickGraphsSurface::handleShadingChanged);
2742 connect(sender: series,
2743 signal: &QSurface3DSeries::wireframeColorChanged,
2744 context: this,
2745 slot: &QQuickGraphsSurface::handleWireframeColorChanged);
2746 connect(sender: series,
2747 signal: &QSurface3DSeries::userDefinedMeshChanged,
2748 context: this,
2749 slot: &QQuickGraphsSurface::handlePointerChanged);
2750 connect(sender: series,
2751 signal: &QSurface3DSeries::meshChanged,
2752 context: this,
2753 slot: &QQuickGraphsSurface::handleMeshTypeChanged);
2754 if (sliceView())
2755 addSliceModel(model: surfaceModel);
2756
2757 addFillModel(model: surfaceModel);
2758}
2759
2760void QQuickGraphsSurface::addFillModel(SurfaceModel *model)
2761{
2762 Q_TRACE_SCOPE(QGraphs3DSurfaceAddFillModel);
2763 auto parent = graphNode();
2764
2765 auto fillModel = new QQuick3DModel();
2766 fillModel->setParent(parent);
2767 fillModel->setParentItem(model->model);
2768 fillModel->setObjectName(QStringLiteral("FillModel"));
2769 fillModel->setVisible(true);
2770 fillModel->setPickable(false);
2771
2772 auto geometry = new QQuick3DGeometry();
2773 geometry->setParent(fillModel);
2774 geometry->setStride(sizeof(SurfaceVertex));
2775 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2776 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2777 offset: 0,
2778 componentType: QQuick3DGeometry::Attribute::F32Type);
2779 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2780 offset: sizeof(QVector3D),
2781 componentType: QQuick3DGeometry::Attribute::F32Type);
2782 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2783 offset: 0,
2784 componentType: QQuick3DGeometry::Attribute::U32Type);
2785 fillModel->setGeometry(geometry);
2786
2787 fillModel->setCastsShadows(false); //Disable shadows as they render incorrectly
2788
2789 QQmlListReference materialRef(fillModel, "materials");
2790
2791 auto *customMaterial = model->customMaterial;
2792 materialRef.append(customMaterial);
2793
2794 model->fillModel = fillModel;
2795 m_fillDirty[model] = false;
2796}
2797
2798void QQuickGraphsSurface::createSliceView()
2799{
2800 setSliceOrthoProjection(true);
2801 QQuickGraphsItem::createSliceView();
2802 Q_TRACE_SCOPE(QGraphs3DSurfaceCreateSliceView);
2803
2804 for (auto surfaceModel : std::as_const(t&: m_model)) {
2805 addSliceModel(model: surfaceModel);
2806 changeSlicePointerMeshTypeForSeries(mesh: surfaceModel->series->mesh(), series: surfaceModel->series);
2807 }
2808}
2809
2810QQuick3DViewport *
2811QQuickGraphsSurface::createOffscreenSliceView(int index, int requestedIndex,
2812 QtGraphs3D::SliceCaptureType sliceType)
2813{
2814 QQuick3DViewport *sliceView = QQuickGraphsItem::createOffscreenSliceView(sliceType);
2815 Q_TRACE_SCOPE(QGraphs3DSurfaceCreateOffscreenSliceView, index, requestedIndex,
2816 static_cast<int>(sliceType));
2817
2818 bool isRow = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)
2819 || sliceType == QtGraphs3D::SliceCaptureType::RowImage);
2820 bool isColumn = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
2821 || sliceType == QtGraphs3D::SliceCaptureType::ColumnImage);
2822
2823 int modelIndex = 0;
2824 for (const auto &model : std::as_const(t&: m_model)) {
2825 if (index > 0 && modelIndex++ != index)
2826 continue;
2827
2828 QRect sampleSpace = model->sampleSpace;
2829 int rowStart = sampleSpace.top();
2830 int columnStart = sampleSpace.left();
2831 int rowEnd = sampleSpace.bottom() + 1;
2832 int columnEnd = sampleSpace.right() + 1;
2833 int rowCount = sampleSpace.height();
2834 int columnCount = sampleSpace.width();
2835
2836 QVector<SurfaceVertex> selectedSeries;
2837 int indexCount = 0;
2838 const QSurfaceDataArray &array = model->series->dataArray();
2839 const qsizetype maxRow = array.size() - 1;
2840 const qsizetype maxColumn = array.at(i: 0).size() - 1;
2841 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
2842 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
2843
2844 if (requestedIndex < 0 || requestedIndex >= maxRow || requestedIndex >= maxColumn) {
2845 qWarning(msg: "The index is out of range. The render stops.");
2846 sliceView->setVisible(false);
2847 sliceView->deleteLater();
2848 return nullptr;
2849 }
2850
2851 if (isRow && requestedIndex != -1) {
2852 selectedSeries.reserve(asize: columnCount * 2);
2853 QVector<SurfaceVertex> list;
2854 QSurfaceDataRow row = array.at(i: requestedIndex);
2855 for (int i = columnStart; i < columnEnd; i++) {
2856 int index = ascendingX ? i : columnEnd - i + columnStart - 1;
2857 QVector3D pos = getNormalizedVertex(data: row.at(i: index), polar: false, flipXZ: false);
2858 SurfaceVertex vertex;
2859 vertex.position = pos;
2860 vertex.position.setY(vertex.position.y() - .025f);
2861 vertex.position.setZ(.0f);
2862 selectedSeries.append(t: vertex);
2863 vertex.position.setY(vertex.position.y() + .05f);
2864 list.append(t: vertex);
2865 }
2866 selectedSeries.append(l: list);
2867 indexCount = columnCount - 1;
2868 }
2869
2870 if (isColumn && requestedIndex != -1) {
2871 selectedSeries.reserve(asize: rowCount * 2);
2872 QVector<SurfaceVertex> list;
2873 for (int i = rowStart; i < rowEnd; i++) {
2874 int index = ascendingZ ? i : rowEnd - i + rowStart - 1;
2875 QVector3D pos =
2876 getNormalizedVertex(data: array.at(i: index).at(i: requestedIndex), polar: false, flipXZ: false);
2877 SurfaceVertex vertex;
2878 vertex.position = pos;
2879 vertex.position.setX(-vertex.position.z());
2880 vertex.position.setY(vertex.position.y() - .025f);
2881 vertex.position.setZ(0);
2882 selectedSeries.append(t: vertex);
2883 vertex.position.setY(vertex.position.y() + .05f);
2884 list.append(t: vertex);
2885 }
2886 selectedSeries.append(l: list);
2887 indexCount = rowCount - 1;
2888
2889 QQmlListReference materialRef(model->sliceModel, "materials");
2890 auto material = materialRef.at(0);
2891 material->setProperty(name: "isColumn", value: true);
2892 }
2893
2894 QVector<quint32> indices;
2895 indices.reserve(asize: indexCount * 6);
2896 for (int i = 0; i < indexCount; i++) {
2897 indices.push_back(t: i + 1);
2898 indices.push_back(t: i + indexCount + 1);
2899 indices.push_back(t: i);
2900 indices.push_back(t: i + indexCount + 2);
2901 indices.push_back(t: i + indexCount + 1);
2902 indices.push_back(t: i + 1);
2903 }
2904
2905 auto surfaceModel = new QQuick3DModel();
2906 surfaceModel->setParent(sliceView->scene());
2907 surfaceModel->setParentItem(sliceView->scene());
2908
2909 auto geometry = new QQuick3DGeometry();
2910 geometry->setParent(surfaceModel);
2911 geometry->setParentItem(surfaceModel);
2912 geometry->setStride(sizeof(SurfaceVertex));
2913 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2914 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, offset: 0,
2915 componentType: QQuick3DGeometry::Attribute::F32Type);
2916 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic, offset: sizeof(QVector3D),
2917 componentType: QQuick3DGeometry::Attribute::F32Type);
2918 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, offset: 0,
2919 componentType: QQuick3DGeometry::Attribute::U32Type);
2920 QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
2921 selectedSeries.size() * sizeof(SurfaceVertex));
2922 geometry->setVertexData(vertexBuffer);
2923 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
2924 indices.size() * sizeof(quint32));
2925 geometry->setIndexData(indexBuffer);
2926 surfaceModel->setGeometry(geometry);
2927
2928 QQmlListReference materialRef(surfaceModel, "materials");
2929 auto material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceSliceMaterial"));
2930 material->setCullMode(QQuick3DMaterial::NoCulling);
2931 QVariant textureInputAsVariant = material->property(name: "custex");
2932 QQuick3DShaderUtilsTextureInput *textureInput =
2933 textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
2934 QQuick3DTexture *texture = model->texture;
2935 textureInput->setTexture(texture);
2936 materialRef.append(material);
2937
2938 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
2939 surfaceModel->setLocalOpacity(1.f);
2940 else
2941 surfaceModel->setLocalOpacity(.0f);
2942
2943 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe)) {
2944 QVector<quint32> gridIndices;
2945 gridIndices.reserve(asize: indexCount * 4);
2946 for (int i = 0; i < indexCount; i++) {
2947 gridIndices.push_back(t: i);
2948 gridIndices.push_back(t: i + indexCount + 1);
2949
2950 gridIndices.push_back(t: i);
2951 gridIndices.push_back(t: i + 1);
2952 }
2953 QQuick3DModel *gridModel = new QQuick3DModel();
2954 gridModel->setParent(sliceView->scene());
2955 gridModel->setParentItem(sliceView->scene());
2956 gridModel->setDepthBias(1.0f);
2957 QQuick3DGeometry *gridGeometry = new QQuick3DGeometry();
2958 gridGeometry->setParent(gridModel);
2959 gridGeometry->setStride(sizeof(SurfaceVertex));
2960 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2961 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic, offset: 0,
2962 componentType: QQuick3DGeometry::Attribute::F32Type);
2963 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic, offset: 0,
2964 componentType: QQuick3DGeometry::Attribute::U32Type);
2965 QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
2966 gridIndices.size() * sizeof(quint32));
2967 gridGeometry->setVertexData(vertexBuffer);
2968 gridGeometry->setIndexData(gridIndexBuffer);
2969 gridGeometry->update();
2970 gridModel->setGeometry(gridGeometry);
2971 QQmlListReference gridMaterialRef(gridModel, "materials");
2972 QQuick3DPrincipledMaterial *gridMaterial = new QQuick3DPrincipledMaterial();
2973 gridMaterial->setParent(gridModel);
2974 gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
2975 gridMaterial->setParent(gridModel);
2976 QColor gridColor = model->series->wireframeColor();
2977 gridMaterial->setBaseColor(gridColor);
2978 gridMaterialRef.append(gridMaterial);
2979 }
2980 }
2981
2982 return sliceView;
2983}
2984
2985void QQuickGraphsSurface::renderSliceToImage(int index,
2986 int requestedIndex,
2987 QtGraphs3D::SliceCaptureType sliceType)
2988{
2989 QQuick3DViewport *sliceView = createOffscreenSliceView(index, requestedIndex, sliceType);
2990
2991 if (!m_grabresult)
2992 m_grabresult = new QImage();
2993
2994 if (sliceView) {
2995 QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
2996 connect(sender: grabbed.data(), signal: &QQuickItemGrabResult::ready, context: this, slot: [&, grabbed, sliceView]() {
2997 sliceView->setVisible(false);
2998 sliceView->deleteLater();
2999 *m_grabresult = grabbed.data()->image();
3000 emit sliceImageChanged(image: *m_grabresult);
3001 });
3002 }
3003}
3004
3005void QQuickGraphsSurface::renderSliceToImage(int index, int requestedIndex,
3006 QtGraphs3D::SliceCaptureType sliceType,
3007 const QUrl &filePath)
3008{
3009 QQuick3DViewport *sliceView = createOffscreenSliceView(index, requestedIndex, sliceType);
3010
3011 if (!sliceView)
3012 return;
3013
3014 if (filePath.isEmpty()) {
3015 qWarning(msg: "Save path is not defined.");
3016 sliceView->setVisible(false);
3017 sliceView->deleteLater();
3018 return;
3019 }
3020
3021 QSharedPointer<QQuickItemGrabResult> grabbed = sliceView->grabToImage();
3022 connect(sender: grabbed.data(), signal: &QQuickItemGrabResult::ready, context: this, slot: [grabbed, sliceView, filePath]() {
3023 if (!grabbed.data()->saveToFile(fileName: filePath))
3024 qWarning(msg: "Saving requested slice view to image failed");
3025 sliceView->setVisible(false);
3026 sliceView->deleteLater();
3027 });
3028}
3029
3030void QQuickGraphsSurface::updateSliceItemLabel(const QString &label, QVector3D position)
3031{
3032 QQuickGraphsItem::updateSliceItemLabel(label, position);
3033
3034 QFontMetrics fm(theme()->labelFont());
3035 float textPadding = 12.0f;
3036 float labelHeight = fm.height() + textPadding;
3037 float labelWidth = fm.horizontalAdvance(label) + textPadding;
3038 sliceItemLabel()->setProperty(name: "labelWidth", value: labelWidth);
3039 sliceItemLabel()->setProperty(name: "labelHeight", value: labelHeight);
3040 QVector3D labelPosition = position;
3041 labelPosition.setZ(.1f);
3042 labelPosition.setY(position.y() + .05f);
3043 sliceItemLabel()->setPosition(labelPosition);
3044 sliceItemLabel()->setProperty(name: "labelText", value: label);
3045 if (!label.compare(s: QString(hiddenLabelTag)))
3046 sliceItemLabel()->setVisible(false);
3047}
3048
3049void QQuickGraphsSurface::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
3050{
3051 checkSliceEnabled();
3052 bool validSlice = mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
3053 && m_selectedPoint != invalidSelectionPosition();
3054 if (sliceView() && sliceView()->isVisible()) {
3055 if (validSlice) {
3056 toggleSliceGraph();
3057 } else {
3058 m_selectionDirty = true;
3059 setSliceActivatedChanged(true);
3060 }
3061 } else if (validSlice) {
3062 m_selectionDirty = true;
3063 setSliceActivatedChanged(true);
3064 }
3065
3066 setSeriesVisualsDirty(true);
3067 itemLabel()->setVisible(false);
3068 if (sliceView() && sliceView()->isVisible())
3069 sliceItemLabel()->setVisible(false);
3070}
3071
3072void QQuickGraphsSurface::addSliceModel(SurfaceModel *model)
3073{
3074 Q_TRACE_SCOPE(QGraphs3DSurfaceAddSliceModel);
3075 QQuick3DViewport *sliceParent = nullptr;
3076
3077 sliceParent = sliceView();
3078
3079 auto surfaceModel = new QQuick3DModel();
3080 surfaceModel->setParent(sliceParent->scene());
3081 surfaceModel->setParentItem(sliceParent->scene());
3082 surfaceModel->setVisible(model->series->isVisible());
3083
3084 auto geometry = new QQuick3DGeometry();
3085 geometry->setParent(surfaceModel);
3086 geometry->setParentItem(surfaceModel);
3087 geometry->setStride(sizeof(SurfaceVertex));
3088 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
3089 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
3090 offset: 0,
3091 componentType: QQuick3DGeometry::Attribute::F32Type);
3092 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
3093 offset: sizeof(QVector3D),
3094 componentType: QQuick3DGeometry::Attribute::F32Type);
3095 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
3096 offset: 0,
3097 componentType: QQuick3DGeometry::Attribute::U32Type);
3098 surfaceModel->setGeometry(geometry);
3099
3100 QQmlListReference materialRef(surfaceModel, "materials");
3101 auto material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceSliceMaterial"));
3102 material->setCullMode(QQuick3DMaterial::NoCulling);
3103 QVariant textureInputAsVariant = material->property(name: "custex");
3104 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
3105 .value<QQuick3DShaderUtilsTextureInput *>();
3106 QQuick3DTexture *texture = model->texture;
3107 textureInput->setTexture(texture);
3108 materialRef.append(material);
3109
3110 model->sliceModel = surfaceModel;
3111
3112 QQuick3DModel *gridModel = new QQuick3DModel();
3113 gridModel->setParent(sliceParent->scene());
3114 gridModel->setParentItem(sliceParent->scene());
3115 gridModel->setVisible(model->series->isVisible());
3116 gridModel->setDepthBias(1.0f);
3117 QQuick3DGeometry *gridGeometry = new QQuick3DGeometry();
3118 gridGeometry->setParent(gridModel);
3119 gridGeometry->setStride(sizeof(SurfaceVertex));
3120 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
3121 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
3122 offset: 0,
3123 componentType: QQuick3DGeometry::Attribute::F32Type);
3124 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
3125 offset: 0,
3126 componentType: QQuick3DGeometry::Attribute::U32Type);
3127 gridModel->setGeometry(gridGeometry);
3128 QQmlListReference gridMaterialRef(gridModel, "materials");
3129 QQuick3DPrincipledMaterial *gridMaterial = new QQuick3DPrincipledMaterial();
3130 gridMaterial->setParent(gridModel);
3131 gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
3132 gridMaterial->setParent(gridModel);
3133 gridMaterialRef.append(gridMaterial);
3134
3135 model->sliceGridModel = gridModel;
3136}
3137
3138void QQuickGraphsSurface::updateSingleHighlightColor()
3139{
3140 auto list = surfaceSeriesList();
3141 for (auto series : std::as_const(t&: list)) {
3142 QQmlListReference pMaterialRef(m_selectionPointers.value(key: series), "materials");
3143 auto pmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: pMaterialRef.at(0));
3144 if (pmat)
3145 pmat->setBaseColor(theme()->singleHighlightColor());
3146 if (sliceView()) {
3147 QQmlListReference spMaterialRef(m_sliceSelectionPointers.value(key: series), "materials");
3148 auto spmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: spMaterialRef.at(0));
3149 spmat->setBaseColor(theme()->singleHighlightColor());
3150 }
3151 }
3152}
3153
3154void QQuickGraphsSurface::updateLightStrength()
3155{
3156 for (auto model : std::as_const(t&: m_model)) {
3157 QQmlListReference materialRef(model->model, "materials");
3158 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
3159 material->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
3160 }
3161}
3162
3163void QQuickGraphsSurface::handleThemeTypeChange()
3164{
3165 for (auto model : std::as_const(t&: m_model))
3166 updateMaterial(model);
3167}
3168
3169QT_END_NAMESPACE
3170

source code of qtgraphs/src/graphs3d/qml/qquickgraphssurface.cpp