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 "qquickgraphssurface_p.h"
8
9#include "qcategory3daxis_p.h"
10#include "qgraphsinputhandler_p.h"
11#include "qquickgraphssurface_p.h"
12#include "qquickgraphstexturedata_p.h"
13#include "qsurface3dseries_p.h"
14#include "qsurfacedataproxy_p.h"
15#include "qvalue3daxis_p.h"
16
17#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
18#include <QtQuick3D/private/qquick3ddefaultmaterial_p.h>
19#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
20
21QT_BEGIN_NAMESPACE
22
23/*!
24 * \qmltype Surface3D
25 * \inherits GraphsItem3D
26 * \inqmlmodule QtGraphs
27 * \ingroup graphs_qml_3D
28 * \brief Describes the usage of the 3D surface graph.
29 *
30 * This type enables developers to render surface plots in 3D with Qt Quick.
31 *
32 * You will need to import the Qt Graphs module to use this type:
33 *
34 * \snippet doc_src_qmlgraphs.cpp 0
35 *
36 * After that you can use Surface3D in your qml files:
37 *
38 * \snippet doc_src_qmlgraphs.cpp 3
39 *
40 * See \l{Surface Graph Gallery} for more thorough usage example.
41 *
42 * \sa Surface3DSeries, ItemModelSurfaceDataProxy, Bars3D, Scatter3D,
43 * {Qt Graphs C++ Classes for 3D}
44 */
45
46/*!
47 * \qmlproperty Value3DAxis Surface3D::axisX
48 * The active x-axis.
49 *
50 * If an axis is not given, a temporary default axis with no labels and an
51 * automatically adjusting range is created.
52 * This temporary axis is destroyed if another axis is explicitly set to the
53 * same orientation.
54 */
55
56/*!
57 * \qmlproperty Value3DAxis Surface3D::axisY
58 * The active y-axis.
59 *
60 * If an axis is not given, a temporary default axis with no labels and an
61 * automatically adjusting range is created.
62 * This temporary axis is destroyed if another axis is explicitly set to the
63 * same orientation.
64 */
65
66/*!
67 * \qmlproperty Value3DAxis Surface3D::axisZ
68 * The active z-axis.
69 *
70 * If an axis is not given, a temporary default axis with no labels and an
71 * automatically adjusting range is created.
72 * This temporary axis is destroyed if another axis is explicitly set to the
73 * same orientation.
74 */
75
76/*!
77 * \qmlproperty Surface3DSeries Surface3D::selectedSeries
78 * \readonly
79 *
80 * The selected series or null. If \l {GraphsItem3D::selectionMode}{selectionMode}
81 * has the \c MultiSeries flag set, this property holds the series
82 * which owns the selected point.
83 */
84
85/*!
86 * \qmlproperty list<Surface3DSeries> Surface3D::seriesList
87 * \qmldefault
88 * This property holds the series of the graph.
89 * By default, this property contains an empty list.
90 * To set the series, either use the addSeries() function or define them as
91 * children of the graph.
92 */
93
94/*!
95 * \qmlproperty bool Surface3D::flipHorizontalGrid
96 *
97 * In some use cases the horizontal axis grid is mostly covered by the surface,
98 * so it can be more useful to display the horizontal axis grid on top of the
99 * graph rather than on the bottom. A typical use case for this is showing 2D
100 * spectrograms using orthoGraphic projection with a top-down viewpoint.
101 *
102 * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal
103 * background of the graph.
104 * If \c{true}, the horizontal axis grid and labels are drawn on the opposite
105 * side of the graph from the horizontal background.
106 * Defaults to \c{false}.
107 */
108
109/*!
110 * \qmlmethod void Surface3D::addSeries(Surface3DSeries series)
111 * Adds the \a series to the graph.
112 * \sa GraphsItem3D::hasSeries()
113 */
114
115/*!
116 * \qmlmethod void Surface3D::removeSeries(Surface3DSeries series)
117 * Removes the \a series from the graph.
118 * \sa GraphsItem3D::hasSeries()
119 */
120
121/*!
122 * \qmlsignal Surface3D::axisXChanged(ValueAxis3D axis)
123 *
124 * This signal is emitted when axisX changes to \a axis.
125 */
126
127/*!
128 * \qmlsignal Surface3D::axisYChanged(ValueAxis3D axis)
129 *
130 * This signal is emitted when axisY changes to \a axis.
131 */
132
133/*!
134 * \qmlsignal Surface3D::axisZChanged(ValueAxis3D axis)
135 *
136 * This signal is emitted when axisZ changes to \a axis.
137 */
138
139/*!
140 * \qmlsignal Surface3D::selectedSeriesChanged(Surface3DSeries series)
141 *
142 * This signal is emitted when selectedSeries changes to \a series.
143 */
144
145/*!
146 * \qmlsignal Surface3D::flipHorizontalGridChanged(bool flip)
147 *
148 * This signal is emitted when flipHorizontalGrid changes to \a flip.
149 */
150
151QQuickGraphsSurface::QQuickGraphsSurface(QQuickItem *parent)
152 : QQuickGraphsItem(parent)
153{
154 m_graphType = QAbstract3DSeries::SeriesType::Surface;
155 setAxisX(0);
156 setAxisY(0);
157 setAxisZ(0);
158 setAcceptedMouseButtons(Qt::AllButtons);
159 clearSelection();
160}
161
162QQuickGraphsSurface::~QQuickGraphsSurface()
163{
164 QMutexLocker locker(m_nodeMutex.data());
165 const QMutexLocker locker2(mutex());
166 for (const auto &model : std::as_const(t&: m_model))
167 delete model;
168}
169
170void QQuickGraphsSurface::setAxisX(QValue3DAxis *axis)
171{
172 QQuickGraphsItem::setAxisX(axis);
173}
174
175QValue3DAxis *QQuickGraphsSurface::axisX() const
176{
177 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX());
178}
179
180void QQuickGraphsSurface::setAxisY(QValue3DAxis *axis)
181{
182 QQuickGraphsItem::setAxisY(axis);
183}
184
185QValue3DAxis *QQuickGraphsSurface::axisY() const
186{
187 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY());
188}
189
190void QQuickGraphsSurface::setAxisZ(QValue3DAxis *axis)
191{
192 QQuickGraphsItem::setAxisZ(axis);
193}
194
195QValue3DAxis *QQuickGraphsSurface::axisZ() const
196{
197 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ());
198}
199
200void QQuickGraphsSurface::handleShadingChanged()
201{
202 auto series = static_cast<QSurface3DSeries *>(sender());
203 for (auto model : m_model) {
204 if (model->series == series) {
205 updateModel(model);
206 break;
207 }
208 }
209}
210
211void QQuickGraphsSurface::handleWireframeColorChanged()
212{
213 for (auto model : m_model) {
214 QQmlListReference gridMaterialRef(model->gridModel, "materials");
215 auto gridMaterial = gridMaterialRef.at(0);
216 QColor gridColor = model->series->wireframeColor();
217 gridMaterial->setProperty(name: "gridColor", value: gridColor);
218
219 if (sliceView()) {
220 QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
221 auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
222 gridMaterial->setBaseColor(gridColor);
223 }
224 }
225}
226
227void QQuickGraphsSurface::handleMeshTypeChanged(QAbstract3DSeries::Mesh mesh)
228{
229 QSurface3DSeries *sender = qobject_cast<QSurface3DSeries *>(object: QObject::sender());
230 changePointerMeshTypeForSeries(mesh, series: sender);
231 if (sliceView())
232 changeSlicePointerMeshTypeForSeries(mesh, series: sender);
233}
234
235void QQuickGraphsSurface::changePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh,
236 QSurface3DSeries *series)
237{
238 changePointerForSeries(filename: getMeshFileName(mesh, series), series);
239}
240
241void QQuickGraphsSurface::changeSlicePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh,
242 QSurface3DSeries *series)
243{
244 changeSlicePointerForSeries(filename: getMeshFileName(mesh, series), series);
245}
246
247QString QQuickGraphsSurface::getMeshFileName(QAbstract3DSeries::Mesh mesh,
248 QSurface3DSeries *series) const
249{
250 QString fileName = {};
251 switch (mesh) {
252 case QAbstract3DSeries::Mesh::Sphere:
253 fileName = QStringLiteral("defaultMeshes/sphereMesh");
254 break;
255 case QAbstract3DSeries::Mesh::Bar:
256 case QAbstract3DSeries::Mesh::Cube:
257 fileName = QStringLiteral("defaultMeshes/barMesh");
258 break;
259 case QAbstract3DSeries::Mesh::Pyramid:
260 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
261 break;
262 case QAbstract3DSeries::Mesh::Cone:
263 fileName = QStringLiteral("defaultMeshes/coneMesh");
264 break;
265 case QAbstract3DSeries::Mesh::Cylinder:
266 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
267 break;
268 case QAbstract3DSeries::Mesh::BevelBar:
269 case QAbstract3DSeries::Mesh::BevelCube:
270 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
271 break;
272 case QAbstract3DSeries::Mesh::UserDefined:
273 fileName = series->userDefinedMesh();
274 break;
275 default:
276 fileName = QStringLiteral("defaultMeshes/sphereMesh");
277 }
278 return fileName;
279}
280
281void QQuickGraphsSurface::handlePointerChanged(const QString &filename)
282{
283 QSurface3DSeries *sender = qobject_cast<QSurface3DSeries *>(object: QObject::sender());
284 changePointerForSeries(filename, series: sender);
285 changeSlicePointerForSeries(filename, series: sender);
286}
287
288void QQuickGraphsSurface::changePointerForSeries(const QString &filename, QSurface3DSeries *series)
289{
290 if (!filename.isEmpty()) {
291 // Selection pointer
292 QQuick3DNode *parent = rootNode();
293
294 QQuick3DPrincipledMaterial *pointerMaterial = nullptr;
295 QQuick3DModel *pointer = m_selectionPointers.value(key: series);
296
297 if (pointer) {
298 // Retrieve the already created material
299 QQmlListReference materialRef(pointer, "materials");
300 pointerMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(object: materialRef.at(0));
301 // Delete old model
302 delete pointer;
303 } else {
304 // No pointer yet; create material for it
305 pointerMaterial = new QQuick3DPrincipledMaterial();
306 pointerMaterial->setParent(this);
307 pointerMaterial->setBaseColor(theme()->singleHighlightColor());
308 }
309 // Create new pointer
310 pointer = new QQuick3DModel();
311 pointer->setParent(parent);
312 pointer->setParentItem(parent);
313 pointer->setSource(QUrl(filename));
314 pointer->setScale(QVector3D(0.05f, 0.05f, 0.05f));
315 // Insert to the map
316 m_selectionPointers.insert(key: series, value: pointer);
317 // Set material
318 QQmlListReference materialRef(pointer, "materials");
319 materialRef.append(pointerMaterial);
320 }
321}
322
323void QQuickGraphsSurface::changeSlicePointerForSeries(const QString &filename,
324 QSurface3DSeries *series)
325{
326 if (!filename.isEmpty()) {
327 // Slice selection pointer
328 QQuick3DNode *sliceParent = sliceView()->scene();
329
330 QQuick3DPrincipledMaterial *slicePointerMaterial = nullptr;
331 QQuick3DModel *slicePointer = m_sliceSelectionPointers.value(key: series);
332
333 if (slicePointer) {
334 // Retrieve the already created material
335 QQmlListReference sliceMaterialRef(slicePointer, "materials");
336 slicePointerMaterial = qobject_cast<QQuick3DPrincipledMaterial *>(
337 object: sliceMaterialRef.at(0));
338 // Delete old model
339 delete slicePointer;
340 } else {
341 // No pointer yet; create material for it
342 slicePointerMaterial = new QQuick3DPrincipledMaterial();
343 slicePointerMaterial->setParent(sliceParent);
344 slicePointerMaterial->setBaseColor(theme()->singleHighlightColor());
345 }
346 // Create new pointer
347 slicePointer = new QQuick3DModel();
348 slicePointer->setParent(sliceParent);
349 slicePointer->setParentItem(sliceParent);
350 slicePointer->setSource(QUrl(filename));
351 slicePointer->setScale(QVector3D(0.05f, 0.05f, 0.05f));
352 // Insert to the map
353 m_sliceSelectionPointers.insert(key: series, value: slicePointer);
354 // Set material
355 QQmlListReference sliceMaterialRef(slicePointer, "materials");
356 sliceMaterialRef.append(slicePointerMaterial);
357 }
358}
359
360void QQuickGraphsSurface::handleFlipHorizontalGridChanged(bool flip)
361{
362 float factor = -1.0f;
363 if (isGridUpdated())
364 factor = flip ? -1.0f : 1.0f;
365
366 for (int i = 0; i < repeaterX()->count(); i++) {
367 QQuick3DNode *obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
368 QVector3D pos = obj->position();
369 pos.setY(pos.y() * factor);
370 obj->setPosition(pos);
371 }
372
373 for (int i = 0; i < repeaterZ()->count(); i++) {
374 QQuick3DNode *obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
375 QVector3D pos = obj->position();
376 pos.setY(pos.y() * factor);
377 obj->setPosition(pos);
378 }
379
380 QVector3D pos = titleLabelX()->position();
381 pos.setY(pos.y() * factor);
382 titleLabelX()->setPosition(pos);
383
384 pos = titleLabelZ()->position();
385 pos.setY(pos.y() * factor);
386 titleLabelZ()->setPosition(pos);
387
388 setGridUpdated(false);
389 emit flipHorizontalGridChanged(flip);
390 setFlipHorizontalGridChanged(false);
391}
392
393void QQuickGraphsSurface::adjustAxisRanges()
394{
395 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
396 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
397 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
398 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
399 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
400 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
401 bool first = true;
402
403 if (adjustX || adjustY || adjustZ) {
404 float minValueX = 0.0f;
405 float maxValueX = 0.0f;
406 float minValueY = 0.0f;
407 float maxValueY = 0.0f;
408 float minValueZ = 0.0f;
409 float maxValueZ = 0.0f;
410 qsizetype seriesCount = m_seriesList.size();
411 for (int series = 0; series < seriesCount; series++) {
412 const QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(
413 m_seriesList.at(i: series));
414 const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
415 if (surfaceSeries->isVisible() && proxy) {
416 QVector3D minLimits;
417 QVector3D maxLimits;
418 proxy->d_func()->limitValues(minValues&: minLimits,
419 maxValues&: maxLimits,
420 axisX: valueAxisX,
421 axisY: valueAxisY,
422 axisZ: valueAxisZ);
423 if (adjustX) {
424 if (first) {
425 // First series initializes the values
426 minValueX = minLimits.x();
427 maxValueX = maxLimits.x();
428 } else {
429 minValueX = qMin(a: minValueX, b: minLimits.x());
430 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
431 }
432 }
433 if (adjustY) {
434 if (first) {
435 // First series initializes the values
436 minValueY = minLimits.y();
437 maxValueY = maxLimits.y();
438 } else {
439 minValueY = qMin(a: minValueY, b: minLimits.y());
440 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
441 }
442 }
443 if (adjustZ) {
444 if (first) {
445 // First series initializes the values
446 minValueZ = minLimits.z();
447 maxValueZ = maxLimits.z();
448 } else {
449 minValueZ = qMin(a: minValueZ, b: minLimits.z());
450 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
451 }
452 }
453 first = false;
454 }
455 }
456
457 static const float adjustmentRatio = 20.0f;
458 static const float defaultAdjustment = 1.0f;
459
460 if (adjustX) {
461 // If all points at same coordinate, need to default to some valid range
462 float adjustment = 0.0f;
463 if (minValueX == maxValueX) {
464 if (adjustZ) {
465 // X and Z are linked to have similar unit size, so choose the valid
466 // range based on it
467 if (minValueZ == maxValueZ)
468 adjustment = defaultAdjustment;
469 else
470 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
471 } else {
472 if (valueAxisZ)
473 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
474 else
475 adjustment = defaultAdjustment;
476 }
477 }
478 valueAxisX->d_func()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
479 }
480 if (adjustY) {
481 // If all points at same coordinate, need to default to some valid range
482 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
483 float adjustment = 0.0f;
484 if (minValueY == maxValueY)
485 adjustment = defaultAdjustment;
486 valueAxisY->d_func()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
487 }
488 if (adjustZ) {
489 // If all points at same coordinate, need to default to some valid range
490 float adjustment = 0.0f;
491 if (minValueZ == maxValueZ) {
492 if (adjustX) {
493 // X and Z are linked to have similar unit size, so choose the valid
494 // range based on it
495 if (minValueX == maxValueX)
496 adjustment = defaultAdjustment;
497 else
498 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
499 } else {
500 if (valueAxisX)
501 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
502 else
503 adjustment = defaultAdjustment;
504 }
505 }
506 valueAxisZ->d_func()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
507 }
508 }
509}
510
511void QQuickGraphsSurface::handleArrayReset()
512{
513 QSurface3DSeries *series;
514 if (qobject_cast<QSurfaceDataProxy *>(object: sender()))
515 series = static_cast<QSurfaceDataProxy *>(sender())->series();
516 else
517 series = static_cast<QSurface3DSeries *>(sender());
518
519 if (series->isVisible()) {
520 adjustAxisRanges();
521 setDataDirty(true);
522 }
523 if (!m_changedSeriesList.contains(t: series))
524 m_changedSeriesList.append(t: series);
525
526 // Clear selection unless still valid
527 if (m_selectedPoint != invalidSelectionPosition())
528 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
529 series->d_func()->markItemLabelDirty();
530 emitNeedRender();
531}
532
533void QQuickGraphsSurface::handleFlatShadingSupportedChange(bool supported)
534{
535 // Handle renderer flat surface support indicator signal. This happens exactly
536 // once per renderer.
537 if (m_flatShadingSupported != supported) {
538 m_flatShadingSupported = supported;
539 // Emit the change for all added surfaces
540 for (QAbstract3DSeries *series : m_seriesList) {
541 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
542 emit surfaceSeries->flatShadingSupportedChanged(enabled: m_flatShadingSupported);
543 }
544 }
545}
546
547void QQuickGraphsSurface::handleRowsChanged(qsizetype startIndex, qsizetype count)
548{
549 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
550 qsizetype oldChangeCount = m_changedRows.size();
551 if (!oldChangeCount)
552 m_changedRows.reserve(asize: count);
553
554 int selectedRow = m_selectedPoint.x();
555 for (qsizetype i = 0; i < count; i++) {
556 bool newItem = true;
557 qsizetype candidate = startIndex + i;
558 for (qsizetype j = 0; j < oldChangeCount; j++) {
559 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
560 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
561 newItem = false;
562 break;
563 }
564 }
565 if (newItem) {
566 ChangeRow newChangeItem = {.series: series, .row: candidate};
567 m_changedRows.append(t: newChangeItem);
568 if (series == m_selectedSeries && selectedRow == candidate)
569 series->d_func()->markItemLabelDirty();
570 }
571 }
572 if (count) {
573 m_changeTracker.rowsChanged = true;
574 setDataDirty(true);
575
576 if (series->isVisible())
577 adjustAxisRanges();
578 emitNeedRender();
579 }
580}
581
582void QQuickGraphsSurface::handleItemChanged(qsizetype rowIndex, qsizetype columnIndex)
583{
584 QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
585 QSurface3DSeries *series = sender->series();
586
587 bool newItem = true;
588 QPoint candidate((int(rowIndex)), (int(columnIndex)));
589 for (ChangeItem item : m_changedItems) {
590 if (item.point == candidate && item.series == series) {
591 newItem = false;
592 break;
593 }
594 }
595 if (newItem) {
596 ChangeItem newItem = {.series: series, .point: candidate};
597 m_changedItems.append(t: newItem);
598 m_changeTracker.itemChanged = true;
599 setDataDirty(true);
600
601 if (series == m_selectedSeries && m_selectedPoint == candidate)
602 series->d_func()->markItemLabelDirty();
603
604 if (series->isVisible())
605 adjustAxisRanges();
606 emitNeedRender();
607 }
608}
609
610void QQuickGraphsSurface::handleRowsAdded(qsizetype startIndex, qsizetype count)
611{
612 Q_UNUSED(startIndex);
613 Q_UNUSED(count);
614 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
615 if (series->isVisible()) {
616 adjustAxisRanges();
617 setDataDirty(true);
618 }
619 if (!m_changedSeriesList.contains(t: series))
620 m_changedSeriesList.append(t: series);
621 emitNeedRender();
622}
623
624void QQuickGraphsSurface::handleRowsInserted(qsizetype startIndex, qsizetype count)
625{
626 Q_UNUSED(startIndex);
627 Q_UNUSED(count);
628 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
629 if (series == m_selectedSeries) {
630 // If rows inserted to selected series before the selection, adjust the selection
631 int selectedRow = m_selectedPoint.x();
632 if (startIndex <= selectedRow) {
633 selectedRow += count;
634 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
635 }
636 }
637
638 if (series->isVisible()) {
639 adjustAxisRanges();
640 setDataDirty(true);
641 }
642 if (!m_changedSeriesList.contains(t: series))
643 m_changedSeriesList.append(t: series);
644
645 emitNeedRender();
646}
647
648void QQuickGraphsSurface::handleRowsRemoved(qsizetype startIndex, qsizetype count)
649{
650 Q_UNUSED(startIndex);
651 Q_UNUSED(count);
652 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
653 if (series == m_selectedSeries) {
654 // If rows removed from selected series before the selection, adjust the selection
655 int selectedRow = m_selectedPoint.x();
656 if (startIndex <= selectedRow) {
657 if ((startIndex + count) > selectedRow)
658 selectedRow = -1; // Selected row removed
659 else
660 selectedRow -= count; // Move selected row down by amount of rows removed
661
662 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
663 }
664 }
665
666 if (series->isVisible()) {
667 adjustAxisRanges();
668 setDataDirty(true);
669 }
670 if (!m_changedSeriesList.contains(t: series))
671 m_changedSeriesList.append(t: series);
672
673 emitNeedRender();
674}
675
676QPoint QQuickGraphsSurface::invalidSelectionPosition()
677{
678 static QPoint invalidSelectionPoint(-1, -1);
679 return invalidSelectionPoint;
680}
681
682void QQuickGraphsSurface::setSelectedPoint(const QPoint position,
683 QSurface3DSeries *series,
684 bool enterSlice)
685{
686 // If the selection targets non-existent point, clear selection instead.
687 QPoint pos = position;
688
689 // Series may already have been removed, so check it before setting the selection.
690 if (!m_seriesList.contains(t: series))
691 series = 0;
692
693 const QSurfaceDataProxy *proxy = 0;
694 if (series)
695 proxy = series->dataProxy();
696
697 if (!proxy)
698 pos = invalidSelectionPosition();
699
700 if (pos != invalidSelectionPosition()) {
701 qsizetype maxRow = proxy->rowCount() - 1;
702 qsizetype maxCol = proxy->columnCount() - 1;
703
704 if (pos.y() < 0 || pos.y() > maxRow || pos.x() < 0 || pos.x() > maxCol)
705 pos = invalidSelectionPosition();
706 }
707
708 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
709 if (pos == invalidSelectionPosition() || !series->isVisible()) {
710 scene()->setSlicingActive(false);
711 } else {
712 // If the selected point is outside data window, or there is no selected
713 // point, disable slicing
714 float axisMinX = m_axisX->min();
715 float axisMaxX = m_axisX->max();
716 float axisMinZ = m_axisZ->min();
717 float axisMaxZ = m_axisZ->max();
718
719 QSurfaceDataItem item = series->dataArray().at(i: pos.y()).at(i: pos.x());
720 if (item.x() < axisMinX || item.x() > axisMaxX || item.z() < axisMinZ
721 || item.z() > axisMaxZ) {
722 scene()->setSlicingActive(false);
723 } else if (enterSlice) {
724 scene()->setSlicingActive(true);
725 }
726 }
727 emitNeedRender();
728 }
729
730 if (pos != m_selectedPoint || series != m_selectedSeries) {
731 bool seriesChanged = (series != m_selectedSeries);
732 m_selectedPoint = pos;
733 m_selectedSeries = series;
734 m_changeTracker.selectedPointChanged = true;
735
736 // Clear selection from other series and finally set new selection to the
737 // specified series
738 for (QAbstract3DSeries *otherSeries : m_seriesList) {
739 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
740 if (surfaceSeries != m_selectedSeries)
741 surfaceSeries->d_func()->setSelectedPoint(invalidSelectionPosition());
742 }
743 if (m_selectedSeries)
744 m_selectedSeries->d_func()->setSelectedPoint(m_selectedPoint);
745
746 if (seriesChanged)
747 emit selectedSeriesChanged(series: m_selectedSeries);
748
749 emitNeedRender();
750 }
751}
752
753void QQuickGraphsSurface::setSelectionMode(QtGraphs3D::SelectionFlags mode)
754{
755 // Currently surface only supports row and column modes when also slicing
756 if ((mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
757 || mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))
758 && !mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
759 qWarning(msg: "Unsupported selection mode.");
760 return;
761 } else if (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
762 && (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
763 == mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
764 qWarning(msg: "Must specify one of either row or column selection mode"
765 "in conjunction with slicing mode.");
766 } else {
767 QtGraphs3D::SelectionFlags oldMode = selectionMode();
768
769 QQuickGraphsItem::setSelectionMode(mode);
770
771 if (mode != oldMode) {
772 // Refresh selection upon mode change to ensure slicing is correctly
773 // updated according to series the visibility.
774 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: true);
775
776 // Special case: Always deactivate slicing when changing away from slice
777 // automanagement, as this can't be handled in setSelectedBar.
778 if (!mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
779 && oldMode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
780 scene()->setSlicingActive(false);
781 }
782 }
783 }
784}
785
786void QQuickGraphsSurface::handleAxisAutoAdjustRangeChangedInOrientation(
787 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
788{
789 Q_UNUSED(orientation);
790 Q_UNUSED(autoAdjust);
791
792 adjustAxisRanges();
793}
794
795void QQuickGraphsSurface::handleAxisRangeChangedBySender(QObject *sender)
796{
797 QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
798
799 // Update selected point - may be moved offscreen
800 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
801}
802
803void QQuickGraphsSurface::handleSeriesVisibilityChangedBySender(QObject *sender)
804{
805 QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
806
807 setSeriesVisibilityDirty(true);
808 // Visibility changes may require disabling slicing,
809 // so just reset selection to ensure everything is still valid.
810 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
811}
812
813void QQuickGraphsSurface::setFlipHorizontalGrid(bool flip)
814{
815 if (m_flipHorizontalGrid != flip) {
816 m_flipHorizontalGrid = flip;
817 m_changeTracker.flipHorizontalGridChanged = true;
818 emit flipHorizontalGridChanged(flip);
819 emitNeedRender();
820 }
821}
822
823bool QQuickGraphsSurface::flipHorizontalGrid() const
824{
825 return m_flipHorizontalGrid;
826}
827
828bool QQuickGraphsSurface::isFlatShadingSupported()
829{
830 return m_flatShadingSupported;
831}
832
833QList<QSurface3DSeries *> QQuickGraphsSurface::surfaceSeriesList()
834{
835 QList<QSurface3DSeries *> surfaceSeriesList;
836 for (QAbstract3DSeries *abstractSeries : m_seriesList) {
837 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
838 if (surfaceSeries)
839 surfaceSeriesList.append(t: surfaceSeries);
840 }
841
842 return surfaceSeriesList;
843}
844
845void QQuickGraphsSurface::updateSurfaceTexture(QSurface3DSeries *series)
846{
847 m_changeTracker.surfaceTextureChanged = true;
848
849 if (!m_changedTextures.contains(t: series))
850 m_changedTextures.append(t: series);
851
852 emitNeedRender();
853}
854
855QQmlListProperty<QSurface3DSeries> QQuickGraphsSurface::seriesList()
856{
857 return QQmlListProperty<QSurface3DSeries>(this,
858 this,
859 &QQuickGraphsSurface::appendSeriesFunc,
860 &QQuickGraphsSurface::countSeriesFunc,
861 &QQuickGraphsSurface::atSeriesFunc,
862 &QQuickGraphsSurface::clearSeriesFunc);
863}
864
865void QQuickGraphsSurface::appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
866 QSurface3DSeries *series)
867{
868 reinterpret_cast<QQuickGraphsSurface *>(list->data)->addSeries(series);
869}
870
871qsizetype QQuickGraphsSurface::countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
872{
873 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().size();
874}
875
876QSurface3DSeries *QQuickGraphsSurface::atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
877 qsizetype index)
878{
879 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().at(i: index);
880}
881
882void QQuickGraphsSurface::clearSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
883{
884 QQuickGraphsSurface *declSurface = reinterpret_cast<QQuickGraphsSurface *>(list->data);
885 QList<QSurface3DSeries *> realList = declSurface->surfaceSeriesList();
886 qsizetype count = realList.size();
887 for (qsizetype i = 0; i < count; i++)
888 declSurface->removeSeries(series: realList.at(i));
889}
890
891void QQuickGraphsSurface::addSeries(QSurface3DSeries *series)
892{
893 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Surface);
894
895 QQuickGraphsItem::addSeriesInternal(series);
896
897 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
898 if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
899 setSelectedPoint(position: surfaceSeries->selectedPoint(), series: surfaceSeries, enterSlice: false);
900
901 if (!surfaceSeries->texture().isNull())
902 updateSurfaceTexture(series: surfaceSeries);
903
904 if (isReady())
905 addModel(series);
906}
907
908void QQuickGraphsSurface::removeSeries(QSurface3DSeries *series)
909{
910 bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
911
912 QQuickGraphsItem::removeSeriesInternal(series);
913
914 if (m_selectedSeries == series)
915 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
916
917 if (wasVisible)
918 adjustAxisRanges();
919
920 series->setParent(this); // Reparent as removing will leave series parentless
921 for (int i = 0; i < m_model.size();) {
922 if (m_model[i]->series == series) {
923 m_model[i]->model->deleteLater();
924 m_model[i]->gridModel->deleteLater();
925 if (const auto &proxy = m_model[i]->proxyModel)
926 proxy->deleteLater();
927 if (sliceView()) {
928 m_model[i]->sliceModel->deleteLater();
929 m_model[i]->sliceGridModel->deleteLater();
930 }
931 delete m_model[i];
932 m_model.removeAt(i);
933 } else {
934 ++i;
935 }
936 }
937}
938
939void QQuickGraphsSurface::clearSelection()
940{
941 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
942 for (auto model : m_model)
943 model->picked = false;
944}
945
946void QQuickGraphsSurface::handleAxisXChanged(QAbstract3DAxis *axis)
947{
948 emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis));
949}
950
951void QQuickGraphsSurface::handleAxisYChanged(QAbstract3DAxis *axis)
952{
953 emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis));
954}
955
956void QQuickGraphsSurface::handleAxisZChanged(QAbstract3DAxis *axis)
957{
958 emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis));
959}
960
961void QQuickGraphsSurface::componentComplete()
962{
963 QQuickGraphsItem::componentComplete();
964
965 for (auto series : surfaceSeriesList()) {
966 addModel(series);
967 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
968 }
969
970 graphsInputHandler()->setGraphsItem(this);
971}
972
973void QQuickGraphsSurface::synchData()
974{
975 if (isFlipHorizontalGridChanged())
976 setHorizontalFlipFactor(flipHorizontalGrid() ? -1 : 1);
977
978 QQuickGraphsItem::synchData();
979
980 if (isSelectedPointChanged()) {
981 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
982 updateSelectedPoint();
983 setSelectedPointChanged(false);
984 }
985
986 if (isGridUpdated() || isFlipHorizontalGridChanged())
987 handleFlipHorizontalGridChanged(flip: flipHorizontalGrid());
988
989 if (isSurfaceTextureChanged()) {
990 if (!isChangedTexturesEmpty()) {
991 for (auto model : m_model) {
992 if (hasSeriesToChangeTexture(series: model->series))
993 updateMaterial(model);
994 }
995 }
996 setSurfaceTextureChanged(false);
997 }
998
999 if (gridLineType() == QtGraphs3D::GridLineType::Shader) {
1000 if (!m_topGrid) {
1001 //add horizontal top grid
1002 QUrl topGridUrl = QUrl(QStringLiteral(":/defaultMeshes/barMeshFull"));
1003 m_topGrid = new QQuick3DModel();
1004 m_topGridScale = new QQuick3DNode();
1005 m_topGridRotation = new QQuick3DNode();
1006
1007 m_topGridScale->setParent(rootNode());
1008 m_topGridScale->setParentItem(rootNode());
1009
1010 m_topGridRotation->setParent(m_topGridScale);
1011 m_topGridRotation->setParentItem(m_topGridScale);
1012
1013 m_topGrid->setObjectName("Top Grid");
1014 m_topGrid->setParent(m_topGridRotation);
1015 m_topGrid->setParentItem(m_topGridRotation);
1016
1017 m_topGrid->setSource(topGridUrl);
1018 m_topGrid->setPickable(false);
1019 }
1020 auto min = qMin(a: scaleWithBackground().x() + backgroundScaleMargin().x(),
1021 b: scaleWithBackground().z() + backgroundScaleMargin().z());
1022 m_topGridScale->setScale(QVector3D(scaleWithBackground().x() + backgroundScaleMargin().x(),
1023 min * gridOffset(),
1024 scaleWithBackground().z() + backgroundScaleMargin().z()));
1025 m_topGridScale->setPosition(
1026 QVector3D(0.0f, scaleWithBackground().y() + backgroundScaleMargin().y(), 0.0f));
1027
1028 m_topGrid->setVisible(m_flipHorizontalGrid);
1029 QQmlListReference materialsRefF(m_topGrid, "materials");
1030 QQmlListReference bbRef(background(), "materials");
1031 QQuick3DCustomMaterial *bgMatFloor;
1032 if (!materialsRefF.size() && bbRef.size()) {
1033 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(bbRef.at(0));
1034 materialsRefF.append(bgMatFloor);
1035 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1036 } else if (materialsRefF.size()) {
1037 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(materialsRefF.at(0));
1038 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1039 }
1040 }
1041
1042 if (m_pickThisFrame) {
1043 doPicking(position: m_lastPick);
1044 m_pickThisFrame = false;
1045 }
1046}
1047
1048void QQuickGraphsSurface::updateGraph()
1049{
1050 for (auto model : m_model) {
1051 bool seriesVisible = model->series->isVisible();
1052 if (isSeriesVisibilityDirty()) {
1053 if (!seriesVisible) {
1054 model->model->setVisible(seriesVisible);
1055 model->gridModel->setVisible(seriesVisible);
1056 if (sliceView()) {
1057 model->sliceModel->setVisible(seriesVisible);
1058 model->sliceGridModel->setVisible(seriesVisible);
1059
1060 if (m_selectedSeries == model->series) {
1061 clearSelection();
1062 setSliceActivatedChanged(true);
1063 m_selectionDirty = !seriesVisible;
1064 }
1065 }
1066 continue;
1067 }
1068 }
1069
1070 if (model->model->visible() != seriesVisible)
1071 model->model->setVisible(seriesVisible);
1072
1073 model->gridModel->setVisible(
1074 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe) && seriesVisible);
1075 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1076 model->model->setLocalOpacity(1.f);
1077 else
1078 model->model->setLocalOpacity(.0f);
1079
1080 if (sliceView() && sliceView()->isVisible()) {
1081 model->sliceGridModel->setVisible(
1082 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1083 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1084 model->sliceModel->setLocalOpacity(1.f);
1085 else
1086 model->sliceModel->setLocalOpacity(.0f);
1087 }
1088 updateMaterial(model);
1089 }
1090
1091 setSeriesVisibilityDirty(false);
1092 if (isDataDirty() || isSeriesVisualsDirty()) {
1093 if (hasChangedSeriesList()) {
1094 handleChangedSeries();
1095 } else {
1096 for (auto model : m_model) {
1097 bool visible = model->series->isVisible();
1098 if (visible)
1099 updateModel(model);
1100 }
1101 }
1102
1103 if (isSliceEnabled()) {
1104 if (!sliceView())
1105 createSliceView();
1106
1107 if (sliceView()->isVisible()) {
1108 if (!m_selectedSeries) {
1109 m_selectionDirty = true;
1110 setSliceActivatedChanged(true);
1111 }
1112 toggleSliceGraph();
1113 }
1114 }
1115
1116 setDataDirty(false);
1117 setSeriesVisualsDirty(false);
1118 }
1119
1120 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
1121 updateSelectedPoint();
1122}
1123
1124void QQuickGraphsSurface::calculateSceneScalingFactors()
1125{
1126 float scaleX, scaleY, scaleZ;
1127 float marginH, marginV;
1128
1129 if (margin() < 0.0f) {
1130 marginH = .1f;
1131 marginV = .1f;
1132 } else {
1133 marginH = margin();
1134 marginV = margin();
1135 }
1136
1137 if (isPolar()) {
1138 float polarMargin = calculatePolarBackgroundMargin();
1139 marginH = qMax(a: marginH, b: polarMargin);
1140 }
1141 float hAspectRatio;
1142 if (isPolar())
1143 hAspectRatio = 1.0f;
1144 else
1145 hAspectRatio = horizontalAspectRatio();
1146
1147 QSizeF areaSize;
1148 if (qFuzzyIsNull(f: hAspectRatio)) {
1149 areaSize.setHeight(axisZ()->max() - axisZ()->min());
1150 areaSize.setWidth(axisX()->max() - axisX()->min());
1151 } else {
1152 areaSize.setHeight(1.0);
1153 areaSize.setWidth(hAspectRatio);
1154 }
1155
1156 float horizontalMaxDimension;
1157 if (aspectRatio() > 2.0f) {
1158 horizontalMaxDimension = 2.0f;
1159 scaleY = 2.0f / aspectRatio();
1160 } else {
1161 horizontalMaxDimension = aspectRatio();
1162 scaleY = 1.0f;
1163 }
1164
1165 if (isPolar())
1166 m_polarRadius = horizontalMaxDimension;
1167
1168 float scaleFactor = qMax(a: areaSize.width(), b: areaSize.height());
1169 scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
1170 scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
1171
1172 setScale(QVector3D(scaleX, scaleY, scaleZ));
1173 setScaleWithBackground(QVector3D(scaleX, scaleY, scaleZ));
1174 setBackgroundScaleMargin(QVector3D(marginH, marginV, marginH));
1175}
1176
1177void QQuickGraphsSurface::handleChangedSeries()
1178{
1179 auto changedSeries = changedSeriesList();
1180 for (auto series : changedSeries) {
1181 for (auto model : m_model) {
1182 if (model->series == series) {
1183 updateModel(model);
1184 }
1185 }
1186 }
1187}
1188
1189inline static float getDataValue(const QSurfaceDataArray &array, bool searchRow, qsizetype index)
1190{
1191 if (searchRow)
1192 return array.at(i: 0).at(i: index).x();
1193 else
1194 return array.at(i: index).at(i: 0).z();
1195}
1196
1197inline static int binarySearchArray(const QSurfaceDataArray &array,
1198 qsizetype maxIndex,
1199 float limitValue,
1200 bool searchRow,
1201 bool lowBound,
1202 bool ascending)
1203{
1204 qsizetype min = 0;
1205 qsizetype max = maxIndex;
1206 qsizetype mid = 0;
1207 qsizetype retVal;
1208
1209 while (max >= min) {
1210 mid = (min + max) / 2;
1211 float arrayValue = getDataValue(array, searchRow, index: mid);
1212 if (arrayValue == limitValue)
1213 return int(mid);
1214 if (ascending) {
1215 if (arrayValue < limitValue)
1216 min = mid + 1;
1217 else
1218 max = mid - 1;
1219 } else {
1220 if (arrayValue > limitValue)
1221 min = mid + 1;
1222 else
1223 max = mid - 1;
1224 }
1225 }
1226
1227 if (lowBound == ascending) {
1228 if (mid > max)
1229 retVal = mid;
1230 else
1231 retVal = min;
1232 } else {
1233 if (mid > max)
1234 retVal = max;
1235 else
1236 retVal = mid;
1237 }
1238
1239 if (retVal < 0 || retVal > maxIndex) {
1240 retVal = -1;
1241 } else if (lowBound) {
1242 if (getDataValue(array, searchRow, index: retVal) < limitValue)
1243 retVal = -1;
1244 } else {
1245 if (getDataValue(array, searchRow, index: retVal) > limitValue)
1246 retVal = -1;
1247 }
1248 return int(retVal);
1249}
1250
1251QRect QQuickGraphsSurface::calculateSampleSpace(SurfaceModel *model)
1252{
1253 QRect sampleSpace;
1254 const QSurfaceDataArray &array = model->series->dataArray();
1255 if (array.size() > 0) {
1256 if (array.size() >= 2 && array.at(i: 0).size() >= 2) {
1257 const qsizetype maxRow = array.size() - 1;
1258 const qsizetype maxColumn = array.at(i: 0).size() - 1;
1259
1260 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
1261 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1262
1263 if (model->ascendingX != ascendingX) {
1264 setIndexDirty(true);
1265 model->ascendingX = ascendingX;
1266 }
1267 if (model->ascendingZ != ascendingZ) {
1268 setIndexDirty(true);
1269 model->ascendingZ = ascendingZ;
1270 }
1271
1272 int idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->min(), searchRow: true, lowBound: true, ascending: ascendingX);
1273 if (idx != -1) {
1274 if (ascendingX)
1275 sampleSpace.setLeft(idx);
1276 else
1277 sampleSpace.setRight(idx);
1278 } else {
1279 sampleSpace.setWidth(-1);
1280 return sampleSpace;
1281 }
1282
1283 idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->max(), searchRow: true, lowBound: false, ascending: ascendingX);
1284 if (idx != -1) {
1285 if (ascendingX)
1286 sampleSpace.setRight(idx);
1287 else
1288 sampleSpace.setLeft(idx);
1289 } else {
1290 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1291 return sampleSpace;
1292 }
1293
1294 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->min(), searchRow: false, lowBound: true, ascending: ascendingZ);
1295 if (idx != -1) {
1296 if (ascendingZ)
1297 sampleSpace.setTop(idx);
1298 else
1299 sampleSpace.setBottom(idx);
1300 } else {
1301 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1302 return sampleSpace;
1303 }
1304
1305 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->max(), searchRow: false, lowBound: false, ascending: ascendingZ);
1306 if (idx != -1) {
1307 if (ascendingZ)
1308 sampleSpace.setBottom(idx);
1309 else
1310 sampleSpace.setTop(idx);
1311 } else {
1312 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1313 return sampleSpace;
1314 }
1315 }
1316 }
1317 return sampleSpace;
1318}
1319
1320void QQuickGraphsSurface::updateModel(SurfaceModel *model)
1321{
1322 const QSurfaceDataArray &array = model->series->dataArray();
1323
1324 if (!array.isEmpty()) {
1325 qsizetype rowCount = array.size();
1326 qsizetype columnCount = array.at(i: 0).size();
1327
1328 const qsizetype maxSize = 4096; // maximum texture size
1329 columnCount = qMin(a: maxSize, b: columnCount);
1330 rowCount = qMin(a: maxSize, b: rowCount);
1331
1332 if (model->rowCount != rowCount) {
1333 model->rowCount = rowCount;
1334 setIndexDirty(true);
1335 }
1336 if (model->columnCount != columnCount) {
1337 model->columnCount = columnCount;
1338 setIndexDirty(true);
1339 }
1340
1341 bool dimensionsChanged = false;
1342 QRect sampleSpace = calculateSampleSpace(model);
1343 if (sampleSpace != model->sampleSpace) {
1344 dimensionsChanged = true;
1345 model->sampleSpace = sampleSpace;
1346 }
1347 int rowStart = sampleSpace.top();
1348 int columnStart = sampleSpace.left();
1349 int rowLimit = sampleSpace.bottom() + 1;
1350 int columnLimit = sampleSpace.right() + 1;
1351
1352 QPoint selC = model->selectedVertex.coord;
1353 selC.setX(qMin(a: selC.x(), b: int(columnCount) - 1));
1354 selC.setY(qMin(a: selC.y(), b: int(rowCount) - 1));
1355 QVector3D selP = array.at(i: selC.y()).at(i: selC.x()).position();
1356
1357 bool pickOutOfRange = false;
1358 if (selP.x() < axisX()->min() || selP.x() > axisX()->max() || selP.z() < axisZ()->min()
1359 || selP.z() > axisZ()->max()) {
1360 pickOutOfRange = true;
1361 }
1362
1363 if (m_isIndexDirty || pickOutOfRange) {
1364 model->selectedVertex = SurfaceVertex();
1365 if (sliceView() && sliceView()->isVisible() && model->series == m_selectedSeries) {
1366 setSlicingActive(false);
1367 setSliceActivatedChanged(true);
1368 m_selectionDirty = true;
1369 }
1370 }
1371 qsizetype totalSize = rowCount * columnCount * 2;
1372 float uvX = 1.0f / float(columnCount - 1);
1373 float uvY = 1.0f / float(rowCount - 1);
1374
1375 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1376
1377 QVector3D boundsMin = model->boundsMin;
1378 QVector3D boundsMax = model->boundsMax;
1379
1380 QVector<QVector4D> heights;
1381 heights.reserve(asize: totalSize);
1382
1383 QQmlListReference materialRef(model->model, "materials");
1384 auto material = materialRef.at(0);
1385 QVariant heightInputAsVariant = material->property(name: "height");
1386 QQuick3DShaderUtilsTextureInput *heightInput
1387 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1388 QQuick3DTexture *heightMap = heightInput->texture();
1389 QQuick3DTextureData *heightMapData = nullptr;
1390 if (!heightMap) {
1391 heightMap = new QQuick3DTexture();
1392 heightMap->setParent(this);
1393 heightMap->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1394 heightMap->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1395 heightMap->setMinFilter(QQuick3DTexture::Nearest);
1396 heightMap->setMagFilter(QQuick3DTexture::Nearest);
1397 heightMapData = new QQuick3DTextureData();
1398 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1399 heightMapData->setFormat(QQuick3DTextureData::RGBA32F);
1400 heightMapData->setParent(heightMap);
1401 heightMapData->setParentItem(heightMap);
1402 } else {
1403 heightMapData = heightMap->textureData();
1404 if (dimensionsChanged)
1405 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1406 }
1407 if (heightMapData->size().width() < 1 || heightMapData->size().height() < 1) {
1408 heightMapData->setTextureData(QByteArray());
1409 heightMap->setTextureData(heightMapData);
1410 heightInput->setTexture(heightMap);
1411 model->heightTexture = heightMap;
1412 return;
1413 }
1414
1415 material->setProperty(name: "xDiff", value: 1.0f / float(sampleSpace.width() - 1));
1416 material->setProperty(name: "yDiff", value: 1.0f / float(sampleSpace.height() - 1));
1417 material->setProperty(name: "flatShading", value: flatShading);
1418 material->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1419 material->setProperty(name: "uvOffset", value: QVector2D(columnStart, rowStart));
1420 material->setProperty(name: "size", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1421 material->setProperty(name: "vertCount", value: QVector2D(columnCount, rowCount));
1422 material->setProperty(name: "flipU", value: !model->ascendingX);
1423 material->setProperty(name: "flipV", value: !model->ascendingZ);
1424
1425 model->vertices.clear();
1426 model->vertices.reserve(asize: totalSize);
1427
1428 for (int i = rowStart; i < rowLimit; i++) {
1429 const QSurfaceDataRow &row = array.at(i);
1430 for (int j = columnStart; j < columnLimit; j++) {
1431 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1432 heights.push_back(t: QVector4D(pos, .0f));
1433 SurfaceVertex vertex;
1434 vertex.position = pos;
1435 vertex.uv = QVector2D(j * uvX, i * uvY);
1436 vertex.coord = QPoint(j, i);
1437 model->vertices.push_back(t: vertex);
1438 if (!qIsNaN(f: pos.y()) && !qIsInf(f: pos.y())) {
1439 if (boundsMin.isNull()) {
1440 boundsMin = pos;
1441 } else {
1442 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: pos.x()),
1443 qMin(a: boundsMin.y(), b: pos.y()),
1444 qMin(a: boundsMin.z(), b: pos.z()));
1445 }
1446 }
1447 if (boundsMax.isNull()) {
1448 boundsMax = pos;
1449 } else {
1450 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: pos.x()),
1451 qMax(a: boundsMax.y(), b: pos.y()),
1452 qMax(a: boundsMax.z(), b: pos.z()));
1453 }
1454 }
1455 }
1456 model->boundsMin = boundsMin;
1457 model->boundsMax = boundsMax;
1458
1459 QByteArray heightData = QByteArray(reinterpret_cast<char *>(heights.data()),
1460 heights.size() * sizeof(QVector4D));
1461 heightMapData->setTextureData(heightData);
1462 heightMap->setTextureData(heightMapData);
1463 heightInput->setTexture(heightMap);
1464 model->heightTexture = heightMap;
1465
1466 if (m_isIndexDirty) {
1467 QVector<SurfaceVertex> vertices;
1468 for (int i = 0; i < rowCount; i++) {
1469 QSurfaceDataRow row = array.at(i);
1470 for (int j = 0; j < columnCount; j++) {
1471 SurfaceVertex vertex;
1472 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1473 vertex.position = pos;
1474 float uStep = model->ascendingX ? j * uvX : 1 - (j * uvX);
1475 float vStep = model->ascendingZ ? i * uvY : 1 - (i * uvY);
1476
1477 vertex.uv = QVector2D(uStep, vStep);
1478 vertex.coord = QPoint(j, i);
1479 vertices.push_back(t: vertex);
1480 }
1481 }
1482 createIndices(model, columnCount, rowCount);
1483 auto geometry = model->model->geometry();
1484 geometry->vertexData().clear();
1485 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()),
1486 vertices.size() * sizeof(SurfaceVertex));
1487 geometry->setVertexData(vertexBuffer);
1488 QByteArray indexBuffer(reinterpret_cast<char *>(model->indices.data()),
1489 model->indices.size() * sizeof(quint32));
1490 geometry->setIndexData(indexBuffer);
1491 geometry->setBounds(min: boundsMin, max: boundsMax);
1492 geometry->update();
1493
1494 createGridlineIndices(model, x: 0, y: 0, endX: columnCount, endY: rowCount);
1495 auto gridGeometry = model->gridModel->geometry();
1496 gridGeometry->vertexData().clear();
1497 gridGeometry->setVertexData(vertexBuffer);
1498 QByteArray gridIndexBuffer(reinterpret_cast<char *>(model->gridIndices.data()),
1499 model->gridIndices.size() * sizeof(quint32));
1500 gridGeometry->setIndexData(gridIndexBuffer);
1501 gridGeometry->setBounds(min: boundsMin, max: boundsMax);
1502 gridGeometry->update();
1503 m_isIndexDirty = false;
1504 }
1505 QQmlListReference gridMaterialRef(model->gridModel, "materials");
1506 auto gridMaterial = gridMaterialRef.at(0);
1507 QVariant gridHeightInputAsVariant = gridMaterial->property(name: "height");
1508 QQuick3DShaderUtilsTextureInput *gridHeightInput
1509 = gridHeightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1510 gridHeightInput->setTexture(heightMap);
1511 QColor gridColor = model->series->wireframeColor();
1512 gridMaterial->setProperty(name: "gridColor", value: gridColor);
1513 gridMaterial->setProperty(name: "range", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1514 gridMaterial->setProperty(name: "vertices", value: QVector2D(columnCount, rowCount));
1515 gridMaterial->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1516
1517 m_proxyDirty = true;
1518 }
1519 updateMaterial(model);
1520 updateSelectedPoint();
1521}
1522
1523void QQuickGraphsSurface::updateProxyModel(SurfaceModel *model)
1524{
1525 if (!model->proxyModel)
1526 createProxyModel(parentModel: model);
1527
1528 const QSurfaceDataArray &array = model->series->dataArray();
1529 if (array.isEmpty())
1530 return;
1531
1532 QRect sampleSpace = model->sampleSpace;
1533 int rowCount = sampleSpace.height();
1534 int columnCount = sampleSpace.width();
1535 int rowStart = sampleSpace.top();
1536 int columnStart = sampleSpace.left();
1537 int rowLimit = sampleSpace.bottom() + 1;
1538 int columnLimit = sampleSpace.right() + 1;
1539 if (rowCount == 0 || columnCount == 0)
1540 return;
1541
1542 // calculate decimate factor based on the order of magnitude of total vertices
1543
1544 int minBeforeDecimate = 1000;
1545 float totalSize = rowCount * columnCount;
1546 int decimateFactor = qMax(a: qFloor(v: std::log10(x: qMax(a: 1.0, b: totalSize - minBeforeDecimate))), b: 1);
1547
1548 int proxyColumnCount = 0;
1549 int proxyRowCount = 0;
1550 QVector<SurfaceVertex> proxyVerts;
1551
1552 float uvY = 1.0f / float(rowCount - 1);
1553 float uvX = 1.0f / float(columnCount - 1);
1554
1555 QVector3D boundsMin = model->boundsMin;
1556 QVector3D boundsMax = model->boundsMax;
1557
1558 int i = rowStart;
1559 while (i < rowLimit) {
1560 const QSurfaceDataRow &row = array.at(i);
1561 proxyRowCount++;
1562 int j = columnStart;
1563 while (j < columnLimit) {
1564 // getNormalizedVertex
1565 if (i == rowStart)
1566 proxyColumnCount++;
1567 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1568 SurfaceVertex vertex;
1569 vertex.position = pos;
1570 vertex.uv = QVector2D(j * uvX, i * uvY);
1571 vertex.coord = QPoint(i, j);
1572 proxyVerts.push_back(t: vertex);
1573 if (!qIsNaN(f: pos.y()) && !qIsInf(f: pos.y())) {
1574 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: pos.x()),
1575 qMin(a: boundsMin.y(), b: pos.y()),
1576 qMin(a: boundsMin.z(), b: pos.z()));
1577 }
1578 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: pos.x()),
1579 qMax(a: boundsMax.y(), b: pos.y()),
1580 qMax(a: boundsMax.z(), b: pos.z()));
1581
1582 if (j == columnLimit - 1)
1583 break;
1584
1585 j += decimateFactor;
1586 if (j >= columnLimit)
1587 j = columnLimit - 1;
1588 }
1589 if (i == rowLimit - 1)
1590 break;
1591
1592 i += decimateFactor;
1593 if (i >= rowLimit)
1594 i = rowLimit - 1;
1595 }
1596
1597 model->boundsMin = boundsMin;
1598 model->boundsMax = boundsMax;
1599 int endX = proxyColumnCount - 1;
1600 int endY = proxyRowCount - 1;
1601 int indexCount = 6 * endX * endY;
1602
1603 QVector<quint32> proxyIndices;
1604 proxyIndices.resize(size: indexCount);
1605
1606 int rowEnd = endY * proxyColumnCount;
1607 for (int row = 0; row < rowEnd; row += proxyColumnCount) {
1608 for (int j = 0; j < endX; j++) {
1609 if (model->ascendingX == model->ascendingZ) {
1610 proxyIndices.push_back(t: row + j + 1);
1611 proxyIndices.push_back(t: row + proxyColumnCount + j);
1612 proxyIndices.push_back(t: row + j);
1613
1614 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1615 proxyIndices.push_back(t: row + proxyColumnCount + j);
1616 proxyIndices.push_back(t: row + j + 1);
1617 } else if (!model->ascendingX) {
1618 proxyIndices.push_back(t: row + proxyColumnCount + j);
1619 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1620 proxyIndices.push_back(t: row + j);
1621
1622 proxyIndices.push_back(t: row + j);
1623 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1624 proxyIndices.push_back(t: row + j + 1);
1625 } else {
1626 proxyIndices.push_back(t: row + proxyColumnCount + j);
1627 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1628 proxyIndices.push_back(t: row + j + 1);
1629
1630 proxyIndices.push_back(t: row + j);
1631 proxyIndices.push_back(t: row + proxyColumnCount + j);
1632 proxyIndices.push_back(t: row + j + 1);
1633 }
1634 }
1635 }
1636
1637 auto geometry = model->proxyModel->geometry();
1638 geometry->vertexData().clear();
1639 QByteArray vertexBuffer(reinterpret_cast<char *>(proxyVerts.data()),
1640 proxyVerts.size() * sizeof(SurfaceVertex));
1641 geometry->setVertexData(vertexBuffer);
1642 QByteArray indexBuffer(reinterpret_cast<char *>(proxyIndices.data()),
1643 proxyIndices.size() * sizeof(quint32));
1644 geometry->setIndexData(indexBuffer);
1645 geometry->setBounds(min: boundsMin, max: boundsMax);
1646 geometry->update();
1647 m_proxyDirty = false;
1648}
1649
1650void QQuickGraphsSurface::createProxyModel(SurfaceModel *model)
1651{
1652 auto proxyModel = new QQuick3DModel();
1653 proxyModel->setParent(graphNode());
1654 proxyModel->setParentItem(model->model);
1655 proxyModel->setObjectName(QStringLiteral("ProxyModel"));
1656 proxyModel->setVisible(true);
1657 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
1658 proxyModel->setPickable(false);
1659 else
1660 proxyModel->setPickable(true);
1661
1662 auto geometry = new QQuick3DGeometry();
1663 geometry->setParent(proxyModel);
1664 geometry->setStride(sizeof(SurfaceVertex));
1665 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
1666 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
1667 offset: 0,
1668 componentType: QQuick3DGeometry::Attribute::F32Type);
1669 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
1670 offset: sizeof(QVector3D),
1671 componentType: QQuick3DGeometry::Attribute::F32Type);
1672 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
1673 offset: 0,
1674 componentType: QQuick3DGeometry::Attribute::U32Type);
1675 proxyModel->setGeometry(geometry);
1676
1677 QQmlListReference materialRef(proxyModel, "materials");
1678 QQuick3DPrincipledMaterial *material = new QQuick3DPrincipledMaterial();
1679 material->setParent(proxyModel);
1680 material->setBaseColor(Qt::white);
1681 material->setOpacity(0);
1682 material->setCullMode(QQuick3DMaterial::NoCulling);
1683 materialRef.append(material);
1684
1685 model->proxyModel = proxyModel;
1686}
1687
1688void QQuickGraphsSurface::updateMaterial(SurfaceModel *model)
1689{
1690 QQmlListReference materialRef(model->model, "materials");
1691
1692 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
1693
1694 if (!material) {
1695 material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceMaterial"));
1696 model->customMaterial = material;
1697 }
1698
1699 bool textured = !(model->series->texture().isNull() && model->series->textureFile().isEmpty());
1700
1701 if (isSeriesVisualsDirty() || !textured) {
1702 float minY = model->boundsMin.y();
1703 float maxY = model->boundsMax.y();
1704 float range = maxY - minY;
1705
1706 switch (model->series->colorStyle()) {
1707 case (QGraphsTheme::ColorStyle::ObjectGradient):
1708 material->setProperty(name: "colorStyle", value: 0);
1709 material->setProperty(name: "gradientMin", value: -(minY / range));
1710 material->setProperty(name: "gradientHeight", value: 1.0f / range);
1711 break;
1712 case (QGraphsTheme::ColorStyle::RangeGradient):
1713 material->setProperty(name: "colorStyle", value: 1);
1714 break;
1715 case (QGraphsTheme::ColorStyle::Uniform):
1716 material->setProperty(name: "colorStyle", value: 2);
1717 material->setProperty(name: "uniformColor", value: model->series->baseColor());
1718 break;
1719 }
1720
1721 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1722
1723 QVariant textureInputAsVariant = material->property(name: "custex");
1724 QQuick3DShaderUtilsTextureInput *textureInput
1725 = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1726 auto textureData = static_cast<QQuickGraphsTextureData *>(model->texture->textureData());
1727 textureData->createGradient(gradient: model->series->baseGradient());
1728 textureInput->setTexture(model->texture);
1729
1730 QVariant heightInputAsVariant = material->property(name: "height");
1731 QQuick3DShaderUtilsTextureInput *heightInput
1732 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1733 heightInput->setTexture(model->heightTexture);
1734 material->setParent(model->model);
1735 material->setParentItem(model->model);
1736 material->setCullMode(QQuick3DMaterial::NoCulling);
1737 material->setProperty(name: "flatShading", value: flatShading);
1738 }
1739
1740 if (textured) {
1741 material->setProperty(name: "colorStyle", value: 3);
1742 QQuick3DShaderUtilsTextureInput *texInput = material->property(name: "baseColor")
1743 .value<QQuick3DShaderUtilsTextureInput *>();
1744 if (!texInput->texture()) {
1745 QQuick3DTexture *texture = new QQuick3DTexture();
1746 texture->setParent(material);
1747 texture->setParentItem(material);
1748 texInput->setTexture(texture);
1749 }
1750 if (!model->series->textureFile().isEmpty()) {
1751 texInput->texture()->setSource(QUrl::fromLocalFile(localfile: model->series->textureFile()));
1752 } else if (!model->series->texture().isNull()) {
1753 QImage image = model->series->texture();
1754 image.convertTo(f: QImage::Format_RGBA32FPx4);
1755 auto textureData = static_cast<QQuickGraphsTextureData *>(model->texture->textureData());
1756 textureData->setFormat(QQuick3DTextureData::RGBA32F);
1757 textureData->setSize(image.size());
1758 textureData->setTextureData(
1759 QByteArray(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes()));
1760 texInput->texture()->setTextureData(textureData);
1761 texInput->texture()->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1762 texInput->texture()->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1763 } else {
1764 texInput->texture()->setSource(QUrl());
1765 }
1766 }
1767 material->update();
1768}
1769
1770QVector3D QQuickGraphsSurface::getNormalizedVertex(const QSurfaceDataItem &data,
1771 bool polar,
1772 bool flipXZ)
1773{
1774 Q_UNUSED(flipXZ);
1775
1776 QValue3DAxis *axisXValue = static_cast<QValue3DAxis *>(axisX());
1777 QValue3DAxis *axisYValue = static_cast<QValue3DAxis *>(axisY());
1778 QValue3DAxis *axisZValue = static_cast<QValue3DAxis *>(axisZ());
1779
1780 float normalizedX = axisXValue->positionAt(x: data.x());
1781 float normalizedY;
1782 float normalizedZ = axisZValue->positionAt(x: data.z());
1783 // TODO : Need to handle, flipXZ
1784
1785 float scale, translate;
1786 if (polar) {
1787 float angle = normalizedX * M_PI * 2.0f;
1788 float radius = normalizedZ * this->scaleWithBackground().z();
1789 normalizedX = radius * qSin(v: angle) * 1.0f;
1790 normalizedZ = -(radius * qCos(v: angle)) * 1.0f;
1791 } else {
1792 scale = translate = this->scaleWithBackground().x();
1793 normalizedX = normalizedX * scale * 2.0f - translate;
1794 scale = translate = this->scaleWithBackground().z();
1795 normalizedZ = normalizedZ * -scale * 2.0f + translate;
1796 }
1797 scale = translate = this->scale().y();
1798 normalizedY = axisYValue->positionAt(x: data.y()) * scale * 2.0f - translate;
1799 return QVector3D(normalizedX, normalizedY, normalizedZ);
1800}
1801
1802void QQuickGraphsSurface::toggleSliceGraph()
1803{
1804 if (m_selectionDirty)
1805 QQuickGraphsItem::toggleSliceGraph();
1806
1807 setSelectedPointChanged(true);
1808
1809 if (!sliceView()->isVisible())
1810 return;
1811
1812 QPointF worldCoord;
1813 for (auto model : m_model) {
1814 if (model->picked) {
1815 QPoint coords = model->selectedVertex.coord;
1816 worldCoord = mapCoordsToWorldSpace(model, coords);
1817 }
1818 }
1819
1820 for (auto model : m_model) {
1821 bool visible = model->series->isVisible();
1822
1823 model->sliceModel->setVisible(visible);
1824 model->sliceGridModel->setVisible(visible);
1825
1826 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked) {
1827 model->sliceModel->setVisible(false);
1828 model->sliceGridModel->setVisible(false);
1829 continue;
1830 } else {
1831 model->sliceGridModel->setVisible(
1832 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1833 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1834 model->sliceModel->setLocalOpacity(1.f);
1835 else
1836 model->sliceModel->setLocalOpacity(.0f);
1837 }
1838
1839 QVector<SurfaceVertex> selectedSeries;
1840
1841 QRect sampleSpace = model->sampleSpace;
1842 int rowStart = sampleSpace.top();
1843 int columnStart = sampleSpace.left();
1844 int rowEnd = sampleSpace.bottom() + 1;
1845 int columnEnd = sampleSpace.right() + 1;
1846 int rowCount = sampleSpace.height();
1847 int columnCount = sampleSpace.width();
1848
1849 QPoint coord;
1850 if (model->picked)
1851 coord = model->selectedVertex.coord;
1852 else
1853 coord = mapCoordsToSampleSpace(model, coords: worldCoord);
1854
1855 int indexCount = 0;
1856 const QSurfaceDataArray &array = model->series->dataArray();
1857 const qsizetype maxRow = array.size() - 1;
1858 const qsizetype maxColumn = array.at(i: 0).size() - 1;
1859 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
1860 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1861 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row) && coord.y() != -1) {
1862 selectedSeries.reserve(asize: columnCount * 2);
1863 QVector<SurfaceVertex> list;
1864 QSurfaceDataRow row = array.at(i: coord.y());
1865 for (int i = columnStart; i < columnEnd; i++) {
1866 int index = ascendingX ? i : columnEnd - i + columnStart - 1;
1867 QVector3D pos = getNormalizedVertex(data: row.at(i: index), polar: false, flipXZ: false);
1868 SurfaceVertex vertex;
1869 vertex.position = pos;
1870 vertex.position.setY(vertex.position.y() - .025f);
1871 vertex.position.setZ(.0f);
1872 selectedSeries.append(t: vertex);
1873 vertex.position.setY(vertex.position.y() + .05f);
1874 list.append(t: vertex);
1875 }
1876 selectedSeries.append(l: list);
1877 indexCount = columnCount - 1;
1878 }
1879
1880 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column) && coord.x() != -1) {
1881 selectedSeries.reserve(asize: rowCount * 2);
1882 QVector<SurfaceVertex> list;
1883 for (int i = rowStart; i < rowEnd; i++) {
1884 int index = ascendingZ ? i : rowEnd - i + rowStart - 1;
1885 QVector3D pos = getNormalizedVertex(data: array.at(i: index).at(i: coord.x()), polar: false, flipXZ: false);
1886 SurfaceVertex vertex;
1887 vertex.position = pos;
1888 vertex.position.setX(-vertex.position.z());
1889 vertex.position.setY(vertex.position.y() - .025f);
1890 vertex.position.setZ(0);
1891 selectedSeries.append(t: vertex);
1892 vertex.position.setY(vertex.position.y() + .05f);
1893 list.append(t: vertex);
1894 }
1895 selectedSeries.append(l: list);
1896 indexCount = rowCount - 1;
1897
1898 QQmlListReference materialRef(model->sliceModel, "materials");
1899 auto material = materialRef.at(0);
1900 material->setProperty(name: "isColumn", value: true);
1901 }
1902
1903 QVector<quint32> indices;
1904 indices.reserve(asize: indexCount * 6);
1905 for (int i = 0; i < indexCount; i++) {
1906 indices.push_back(t: i + 1);
1907 indices.push_back(t: i + indexCount + 1);
1908 indices.push_back(t: i);
1909 indices.push_back(t: i + indexCount + 2);
1910 indices.push_back(t: i + indexCount + 1);
1911 indices.push_back(t: i + 1);
1912 }
1913
1914 auto geometry = model->sliceModel->geometry();
1915 geometry->vertexData().clear();
1916 geometry->indexData().clear();
1917 QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
1918 selectedSeries.size() * sizeof(SurfaceVertex));
1919 geometry->setVertexData(vertexBuffer);
1920 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
1921 indices.size() * sizeof(quint32));
1922 geometry->setIndexData(indexBuffer);
1923 geometry->update();
1924
1925 geometry = model->sliceGridModel->geometry();
1926 geometry->vertexData().clear();
1927 geometry->indexData().clear();
1928 geometry->setVertexData(vertexBuffer);
1929
1930 QVector<quint32> gridIndices;
1931 gridIndices.reserve(asize: indexCount * 4);
1932 for (int i = 0; i < indexCount; i++) {
1933 gridIndices.push_back(t: i);
1934 gridIndices.push_back(t: i + indexCount + 1);
1935
1936 gridIndices.push_back(t: i);
1937 gridIndices.push_back(t: i + 1);
1938 }
1939 geometry->indexData().clear();
1940 QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
1941 gridIndices.size() * sizeof(quint32));
1942 geometry->setIndexData(gridIndexBuffer);
1943 geometry->update();
1944
1945 QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
1946 auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
1947 QColor gridColor = model->series->wireframeColor();
1948 gridMaterial->setBaseColor(gridColor);
1949
1950 updateSelectedPoint();
1951 }
1952}
1953
1954QPointF QQuickGraphsSurface::mapCoordsToWorldSpace(SurfaceModel *model, QPointF coords)
1955{
1956 const QSurfaceDataArray &array = model->series->dataArray();
1957 QSurfaceDataItem item = array.at(i: coords.y()).at(i: coords.x());
1958 return QPointF(item.x(), item.z());
1959}
1960
1961QPoint QQuickGraphsSurface::mapCoordsToSampleSpace(SurfaceModel *model, QPointF coords)
1962{
1963 const QSurfaceDataArray &array = model->series->dataArray();
1964 qsizetype maxRow = array.size() - 1;
1965 qsizetype maxCol = array.at(i: 0).size() - 1;
1966 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxCol).x();
1967 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1968 qsizetype botX = ascendingX ? 0 : maxCol;
1969 qsizetype botZ = ascendingZ ? 0 : maxRow;
1970 qsizetype topX = ascendingX ? maxCol : 0;
1971 qsizetype topZ = ascendingZ ? maxRow : 0;
1972
1973 QPoint point(-1, -1);
1974
1975 QSurfaceDataItem bottomLeft = array.at(i: botZ).at(i: botX);
1976 QSurfaceDataItem topRight = array.at(i: topZ).at(i: topX);
1977
1978 QPointF pointBL(bottomLeft.x(), bottomLeft.z());
1979 QPointF pointTR(topRight.x(), topRight.z());
1980
1981 QPointF pointF = coords - pointBL;
1982 QPointF span = pointTR - pointBL;
1983 QPointF step = QPointF(span.x() / float(maxCol), span.y() / float(maxRow));
1984 QPoint sample = QPoint((pointF.x() + (step.x() / 2.0)) / step.x(),
1985 (pointF.y() + (step.y() / 2.0)) / step.y());
1986
1987 if (bottomLeft.x() <= coords.x() && topRight.x() >= coords.x())
1988 point.setX(ascendingX ? sample.x() : int(maxCol) - sample.x());
1989
1990 if (bottomLeft.z() <= coords.y() && topRight.z() >= coords.y())
1991 point.setY(ascendingZ ? sample.y() : int(maxRow) - sample.y());
1992 return point;
1993}
1994
1995void QQuickGraphsSurface::createIndices(SurfaceModel *model, qsizetype columnCount, qsizetype rowCount)
1996{
1997 qsizetype endX = columnCount - 1;
1998 qsizetype endY = rowCount - 1;
1999
2000 qsizetype indexCount = 6 * endX * endY;
2001 QVector<quint32> *indices = &model->indices;
2002
2003 indices->clear();
2004 indices->resize(size: indexCount);
2005
2006 qsizetype rowEnd = endY * columnCount;
2007 for (qsizetype row = 0; row < rowEnd; row += columnCount) {
2008 for (qsizetype j = 0; j < endX; j++) {
2009 indices->push_back(t: int(row + j + 1));
2010 indices->push_back(t: int(row + columnCount + j));
2011 indices->push_back(t: int(row + j));
2012
2013 indices->push_back(t: int(row + columnCount + j + 1));
2014 indices->push_back(t: int(row + columnCount + j));
2015 indices->push_back(t: int(row + j + 1));
2016 }
2017 }
2018}
2019void QQuickGraphsSurface::createGridlineIndices(SurfaceModel *model, qsizetype x, qsizetype y, qsizetype endX, qsizetype endY)
2020{
2021 qsizetype columnCount = model->columnCount;
2022 qsizetype rowCount = model->rowCount;
2023
2024 if (endX >= columnCount)
2025 endX = columnCount - 1;
2026 if (endY >= rowCount)
2027 endY = rowCount - 1;
2028 if (x > endX)
2029 x = endX - 1;
2030 if (y > endY)
2031 y = endY - 1;
2032
2033 qsizetype nColumns = endX - x + 1;
2034 qsizetype nRows = endY - y + 1;
2035
2036 qsizetype gridIndexCount = 2 * nColumns * (nRows - 1) + 2 * nRows * (nColumns - 1);
2037 model->gridIndices.clear();
2038 model->gridIndices.resize(size: gridIndexCount);
2039
2040 for (qsizetype i = y, row = columnCount * y; i <= endY; i++, row += columnCount) {
2041 for (qsizetype j = x; j < endX; j++) {
2042 model->gridIndices.push_back(t: int(row + j));
2043 model->gridIndices.push_back(t: int(row + j + 1));
2044 }
2045 }
2046 for (qsizetype i = y, row = columnCount * y; i < endY; i++, row += columnCount) {
2047 for (qsizetype j = x; j <= endX; j++) {
2048 model->gridIndices.push_back(t: int(row + j));
2049 model->gridIndices.push_back(t: int(row + j + columnCount));
2050 }
2051 }
2052}
2053
2054bool QQuickGraphsSurface::doPicking(QPointF position)
2055{
2056 if (!m_pickThisFrame && m_proxyDirty) {
2057 m_pickThisFrame = true;
2058 m_lastPick = position;
2059 for (auto model : m_model)
2060 updateProxyModel(model);
2061 return false;
2062 }
2063 if (!QQuickGraphsItem::doPicking(point: position))
2064 return false;
2065
2066 m_selectionDirty = true;
2067 auto pickResult = pickAll(x: position.x(), y: position.y());
2068 QVector3D pickedPos(0.0f, 0.0f, 0.0f);
2069 QQuick3DModel *pickedModel = nullptr;
2070
2071 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
2072 if (!sliceView() && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice))
2073 createSliceView();
2074
2075 if (!pickResult.isEmpty()) {
2076 for (auto picked : pickResult) {
2077 bool inBounds = qAbs(t: picked.position().y()) < scaleWithBackground().y();
2078 if (inBounds && picked.objectHit()) {
2079 pickedPos = picked.position();
2080 if (picked.objectHit()->objectName().contains(QStringLiteral("ProxyModel"))) {
2081 pickedModel = qobject_cast<QQuick3DModel *>(
2082 object: picked.objectHit()->parentItem());
2083 } else if (picked.objectHit()->objectName().contains(
2084 QStringLiteral("SurfaceModel"))) {
2085 pickedModel = qobject_cast<QQuick3DModel *>(object: picked.objectHit());
2086 } else {
2087 clearSelection();
2088 continue;
2089 }
2090 bool visible = false;
2091 for (auto model : m_model) {
2092 if (model->model == pickedModel)
2093 visible = model->series->isVisible();
2094 }
2095 if (!pickedPos.isNull() && visible)
2096 break;
2097 } else {
2098 clearSelection();
2099 }
2100 }
2101
2102 bool inRange = qAbs(t: pickedPos.x()) < scaleWithBackground().x()
2103 && qAbs(t: pickedPos.z()) < scaleWithBackground().z();
2104
2105 if (!pickedPos.isNull() && inRange) {
2106 float min = -1.0f;
2107
2108 for (auto model : m_model) {
2109 if (!model->series->isVisible()) {
2110 model->picked = false;
2111 continue;
2112 }
2113
2114 model->picked = (model->model == pickedModel);
2115
2116 SurfaceVertex selectedVertex;
2117 for (auto vertex : model->vertices) {
2118 QVector3D pos = vertex.position;
2119 float dist = pickedPos.distanceToPoint(point: pos);
2120 if (selectedVertex.position.isNull() || dist < min) {
2121 min = dist;
2122 selectedVertex = vertex;
2123 }
2124 }
2125 model->selectedVertex = selectedVertex;
2126 if (!selectedVertex.position.isNull() && model->picked) {
2127 model->series->setSelectedPoint(selectedVertex.coord);
2128 setSlicingActive(false);
2129 if (isSliceEnabled())
2130 setSliceActivatedChanged(true);
2131 }
2132 }
2133 }
2134 } else {
2135 clearSelection();
2136 for (auto model : m_model)
2137 model->picked = false;
2138 }
2139 }
2140 return true;
2141}
2142
2143void QQuickGraphsSurface::updateSelectedPoint()
2144{
2145 bool labelVisible = false;
2146
2147 auto list = surfaceSeriesList();
2148 for (auto series : list) {
2149 // If the pointer and its instancing do not exist yet (as will happen in widget case),
2150 // we must create them
2151 if (!m_selectionPointers.value(key: series))
2152 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
2153 m_selectionPointers.value(key: series)->setVisible(false);
2154 if (sliceView() && sliceView()->isVisible())
2155 m_sliceSelectionPointers.value(key: series)->setVisible(false);
2156 }
2157
2158 QPointF worldCoord;
2159 for (auto model : m_model) {
2160 if (model->picked) {
2161 QPoint coords = model->selectedVertex.coord;
2162 worldCoord = mapCoordsToWorldSpace(model, coords);
2163 }
2164 }
2165 for (auto model : m_model) {
2166 if ((!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked)
2167 || model->selectedVertex.position.isNull()) {
2168 continue;
2169 }
2170 QPoint selectedCoord;
2171 if (model->picked)
2172 selectedCoord = model->selectedVertex.coord;
2173 else
2174 selectedCoord = mapCoordsToSampleSpace(model, coords: worldCoord);
2175 if (selectedCoord.x() == -1 || selectedCoord.y() == -1)
2176 continue;
2177
2178 const QSurfaceDataItem &dataPos
2179 = model->series->dataArray().at(i: selectedCoord.y()).at(i: selectedCoord.x());
2180 QVector3D pos = getNormalizedVertex(data: dataPos, polar: isPolar(), flipXZ: false);
2181
2182 SurfaceVertex selectedVertex;
2183 selectedVertex.position = pos;
2184 selectedVertex.coord = model->selectedVertex.coord;
2185 if (model->series->isVisible() && !selectedVertex.position.isNull()
2186 && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item)) {
2187 m_selectionPointers.value(key: model->series)->setPosition(selectedVertex.position);
2188 m_selectionPointers.value(key: model->series)->setVisible(true);
2189 QVector3D slicePosition = getNormalizedVertex(data: dataPos, polar: false, flipXZ: false);
2190 if (sliceView() && sliceView()->isVisible()) {
2191 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2192 slicePosition.setX(-slicePosition.z());
2193 slicePosition.setZ(.0f);
2194 m_sliceSelectionPointers.value(key: model->series)->setPosition(slicePosition);
2195 m_sliceSelectionPointers.value(key: model->series)->setVisible(true);
2196 }
2197 if (model->picked) {
2198 QVector3D labelPosition = selectedVertex.position;
2199 QString label = model->series->itemLabel();
2200 setSelectedPoint(position: selectedVertex.coord, series: model->series, enterSlice: false);
2201
2202 updateItemLabel(position: labelPosition);
2203 itemLabel()->setProperty(name: "labelText", value: label);
2204 if (!label.compare(s: hiddenLabelTag))
2205 itemLabel()->setVisible(false);
2206 labelVisible = model->series->isItemLabelVisible();
2207 if (sliceView() && sliceView()->isVisible())
2208 updateSliceItemLabel(label, position: slicePosition);
2209 }
2210 }
2211 }
2212 setItemSelected(m_selectedSeries != nullptr);
2213 itemLabel()->setVisible(labelVisible);
2214 if (sliceView() && sliceView()->isVisible())
2215 sliceItemLabel()->setVisible(labelVisible);
2216}
2217
2218void QQuickGraphsSurface::addModel(QSurface3DSeries *series)
2219{
2220 auto parent = graphNode();
2221 bool visible = series->isVisible();
2222
2223 auto model = new QQuick3DModel();
2224 model->setParent(parent);
2225 model->setParentItem(parent);
2226 model->setObjectName(QStringLiteral("SurfaceModel"));
2227 model->setVisible(visible);
2228 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
2229 model->setPickable(false);
2230 else
2231 model->setPickable(true);
2232
2233 auto geometry = new QQuick3DGeometry();
2234 geometry->setParent(model);
2235 geometry->setStride(sizeof(SurfaceVertex));
2236 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2237 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2238 offset: 0,
2239 componentType: QQuick3DGeometry::Attribute::F32Type);
2240 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2241 offset: sizeof(QVector3D),
2242 componentType: QQuick3DGeometry::Attribute::F32Type);
2243 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2244 offset: 0,
2245 componentType: QQuick3DGeometry::Attribute::U32Type);
2246 model->setGeometry(geometry);
2247
2248 model->setCastsShadows(false); //Disable shadows as they render incorrectly
2249
2250 QQuick3DTexture *texture = new QQuick3DTexture();
2251 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
2252 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
2253 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
2254 textureData->setParent(texture);
2255 textureData->setParentItem(texture);
2256 texture->setTextureData(textureData);
2257
2258 QQmlListReference materialRef(model, "materials");
2259
2260 QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
2261 QStringLiteral(":/materials/SurfaceMaterial"));
2262
2263 customMaterial->setParent(model);
2264 customMaterial->setParentItem(model);
2265 customMaterial->setCullMode(QQuick3DMaterial::NoCulling);
2266 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
2267 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
2268 .value<QQuick3DShaderUtilsTextureInput *>();
2269 textureInput->setTexture(texture);
2270
2271 texture->setParent(customMaterial);
2272
2273 materialRef.append(customMaterial);
2274
2275 auto gridModel = new QQuick3DModel();
2276 gridModel->setParent(parent);
2277 gridModel->setParentItem(parent);
2278 gridModel->setObjectName(QStringLiteral("SurfaceModel"));
2279 gridModel->setVisible(visible);
2280 gridModel->setDepthBias(1.0f);
2281 auto gridGeometry = new QQuick3DGeometry();
2282 gridGeometry->setParent(this);
2283 gridGeometry->setStride(sizeof(SurfaceVertex));
2284 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2285 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2286 offset: 0,
2287 componentType: QQuick3DGeometry::Attribute::F32Type);
2288 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2289 offset: sizeof(QVector3D),
2290 componentType: QQuick3DGeometry::Attribute::F32Type);
2291 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2292 offset: 0,
2293 componentType: QQuick3DGeometry::Attribute::U32Type);
2294 gridModel->setGeometry(gridGeometry);
2295 gridModel->setCastsShadows(false);
2296 QQmlListReference gridMaterialRef(gridModel, "materials");
2297 auto gridMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/GridSurfaceMaterial"));
2298 gridMaterial->setParent(gridModel);
2299 gridMaterial->setParentItem(gridModel);
2300 gridMaterialRef.append(gridMaterial);
2301
2302 SurfaceModel *surfaceModel = new SurfaceModel();
2303 surfaceModel->model = model;
2304 surfaceModel->gridModel = gridModel;
2305 surfaceModel->series = series;
2306 surfaceModel->texture = texture;
2307 surfaceModel->customMaterial = customMaterial;
2308
2309 m_model.push_back(t: surfaceModel);
2310
2311 connect(sender: series,
2312 signal: &QSurface3DSeries::shadingChanged,
2313 context: this,
2314 slot: &QQuickGraphsSurface::handleShadingChanged);
2315 connect(sender: series,
2316 signal: &QSurface3DSeries::wireframeColorChanged,
2317 context: this,
2318 slot: &QQuickGraphsSurface::handleWireframeColorChanged);
2319 connect(sender: series,
2320 signal: &QSurface3DSeries::userDefinedMeshChanged,
2321 context: this,
2322 slot: &QQuickGraphsSurface::handlePointerChanged);
2323 connect(sender: series,
2324 signal: &QSurface3DSeries::meshChanged,
2325 context: this,
2326 slot: &QQuickGraphsSurface::handleMeshTypeChanged);
2327 if (sliceView())
2328 addSliceModel(model: surfaceModel);
2329}
2330
2331void QQuickGraphsSurface::createSliceView()
2332{
2333 setSliceOrthoProjection(true);
2334 QQuickGraphsItem::createSliceView();
2335
2336 for (auto surfaceModel : m_model) {
2337 addSliceModel(model: surfaceModel);
2338 changeSlicePointerMeshTypeForSeries(mesh: surfaceModel->series->mesh(), series: surfaceModel->series);
2339 }
2340}
2341
2342void QQuickGraphsSurface::updateSliceItemLabel(const QString &label, QVector3D position)
2343{
2344 QQuickGraphsItem::updateSliceItemLabel(label, position);
2345
2346 QFontMetrics fm(theme()->labelFont());
2347 float textPadding = 12.0f;
2348 float labelHeight = fm.height() + textPadding;
2349 float labelWidth = fm.horizontalAdvance(label) + textPadding;
2350 sliceItemLabel()->setProperty(name: "labelWidth", value: labelWidth);
2351 sliceItemLabel()->setProperty(name: "labelHeight", value: labelHeight);
2352 QVector3D labelPosition = position;
2353 labelPosition.setZ(.1f);
2354 labelPosition.setY(position.y() + .05f);
2355 sliceItemLabel()->setPosition(labelPosition);
2356 sliceItemLabel()->setProperty(name: "labelText", value: label);
2357 if (!label.compare(s: hiddenLabelTag))
2358 sliceItemLabel()->setVisible(false);
2359}
2360
2361void QQuickGraphsSurface::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
2362{
2363 checkSliceEnabled();
2364 bool validSlice = mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
2365 && m_selectedPoint != invalidSelectionPosition();
2366 if (sliceView() && sliceView()->isVisible()) {
2367 if (validSlice) {
2368 toggleSliceGraph();
2369 } else {
2370 m_selectionDirty = true;
2371 setSliceActivatedChanged(true);
2372 }
2373 } else if (validSlice) {
2374 m_selectionDirty = true;
2375 setSliceActivatedChanged(true);
2376 }
2377
2378 setSeriesVisualsDirty(true);
2379 itemLabel()->setVisible(false);
2380 if (sliceView() && sliceView()->isVisible())
2381 sliceItemLabel()->setVisible(false);
2382}
2383
2384void QQuickGraphsSurface::addSliceModel(SurfaceModel *model)
2385{
2386 QQuick3DViewport *sliceParent = sliceView();
2387
2388 auto surfaceModel = new QQuick3DModel();
2389 surfaceModel->setParent(sliceParent->scene());
2390 surfaceModel->setParentItem(sliceParent->scene());
2391 surfaceModel->setVisible(model->series->isVisible());
2392
2393 auto geometry = new QQuick3DGeometry();
2394 geometry->setParent(surfaceModel);
2395 geometry->setParentItem(surfaceModel);
2396 geometry->setStride(sizeof(SurfaceVertex));
2397 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2398 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2399 offset: 0,
2400 componentType: QQuick3DGeometry::Attribute::F32Type);
2401 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2402 offset: sizeof(QVector3D),
2403 componentType: QQuick3DGeometry::Attribute::F32Type);
2404 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2405 offset: 0,
2406 componentType: QQuick3DGeometry::Attribute::U32Type);
2407 surfaceModel->setGeometry(geometry);
2408
2409 QQmlListReference materialRef(surfaceModel, "materials");
2410 auto material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceSliceMaterial"));
2411 material->setCullMode(QQuick3DMaterial::NoCulling);
2412 QVariant textureInputAsVariant = material->property(name: "custex");
2413 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
2414 .value<QQuick3DShaderUtilsTextureInput *>();
2415 QQuick3DTexture *texture = model->texture;
2416 textureInput->setTexture(texture);
2417 materialRef.append(material);
2418
2419 model->sliceModel = surfaceModel;
2420
2421 QQuick3DModel *gridModel = new QQuick3DModel();
2422 gridModel->setParent(sliceParent->scene());
2423 gridModel->setParentItem(sliceParent->scene());
2424 gridModel->setVisible(model->series->isVisible());
2425 gridModel->setDepthBias(1.0f);
2426 QQuick3DGeometry *gridGeometry = new QQuick3DGeometry();
2427 gridGeometry->setParent(gridModel);
2428 gridGeometry->setStride(sizeof(SurfaceVertex));
2429 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2430 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2431 offset: 0,
2432 componentType: QQuick3DGeometry::Attribute::F32Type);
2433 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2434 offset: 0,
2435 componentType: QQuick3DGeometry::Attribute::U32Type);
2436 gridModel->setGeometry(gridGeometry);
2437 QQmlListReference gridMaterialRef(gridModel, "materials");
2438 QQuick3DPrincipledMaterial *gridMaterial = new QQuick3DPrincipledMaterial();
2439 gridMaterial->setParent(gridModel);
2440 gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
2441 gridMaterial->setParent(gridModel);
2442 gridMaterialRef.append(gridMaterial);
2443
2444 model->sliceGridModel = gridModel;
2445}
2446
2447void QQuickGraphsSurface::updateSingleHighlightColor()
2448{
2449 auto list = surfaceSeriesList();
2450 for (auto series : list) {
2451 QQmlListReference pMaterialRef(m_selectionPointers.value(key: series), "materials");
2452 auto pmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: pMaterialRef.at(0));
2453 if (pmat)
2454 pmat->setBaseColor(theme()->singleHighlightColor());
2455 if (sliceView()) {
2456 QQmlListReference spMaterialRef(m_sliceSelectionPointers.value(key: series), "materials");
2457 auto spmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: spMaterialRef.at(0));
2458 spmat->setBaseColor(theme()->singleHighlightColor());
2459 }
2460 }
2461}
2462
2463void QQuickGraphsSurface::updateLightStrength()
2464{
2465 for (auto model : m_model) {
2466 QQmlListReference materialRef(model->model, "materials");
2467 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
2468 material->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
2469 }
2470}
2471
2472void QQuickGraphsSurface::handleThemeTypeChange()
2473{
2474 for (auto model : m_model)
2475 updateMaterial(model);
2476}
2477
2478QT_END_NAMESPACE
2479

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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