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 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
528 series->d_func()->markItemLabelDirty();
529 emitNeedRender();
530}
531
532void QQuickGraphsSurface::handleFlatShadingSupportedChange(bool supported)
533{
534 // Handle renderer flat surface support indicator signal. This happens exactly
535 // once per renderer.
536 if (m_flatShadingSupported != supported) {
537 m_flatShadingSupported = supported;
538 // Emit the change for all added surfaces
539 for (QAbstract3DSeries *series : m_seriesList) {
540 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
541 emit surfaceSeries->flatShadingSupportedChanged(enabled: m_flatShadingSupported);
542 }
543 }
544}
545
546void QQuickGraphsSurface::handleRowsChanged(qsizetype startIndex, qsizetype count)
547{
548 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
549 qsizetype oldChangeCount = m_changedRows.size();
550 if (!oldChangeCount)
551 m_changedRows.reserve(asize: count);
552
553 int selectedRow = m_selectedPoint.x();
554 for (qsizetype i = 0; i < count; i++) {
555 bool newItem = true;
556 qsizetype candidate = startIndex + i;
557 for (qsizetype j = 0; j < oldChangeCount; j++) {
558 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
559 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
560 newItem = false;
561 break;
562 }
563 }
564 if (newItem) {
565 ChangeRow newChangeItem = {.series: series, .row: candidate};
566 m_changedRows.append(t: newChangeItem);
567 if (series == m_selectedSeries && selectedRow == candidate)
568 series->d_func()->markItemLabelDirty();
569 }
570 }
571 if (count) {
572 m_changeTracker.rowsChanged = true;
573 setDataDirty(true);
574
575 if (series->isVisible())
576 adjustAxisRanges();
577 emitNeedRender();
578 }
579}
580
581void QQuickGraphsSurface::handleItemChanged(qsizetype rowIndex, qsizetype columnIndex)
582{
583 QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
584 QSurface3DSeries *series = sender->series();
585
586 bool newItem = true;
587 QPoint candidate((int(rowIndex)), (int(columnIndex)));
588 for (ChangeItem item : m_changedItems) {
589 if (item.point == candidate && item.series == series) {
590 newItem = false;
591 break;
592 }
593 }
594 if (newItem) {
595 ChangeItem newItem = {.series: series, .point: candidate};
596 m_changedItems.append(t: newItem);
597 m_changeTracker.itemChanged = true;
598 setDataDirty(true);
599
600 if (series == m_selectedSeries && m_selectedPoint == candidate)
601 series->d_func()->markItemLabelDirty();
602
603 if (series->isVisible())
604 adjustAxisRanges();
605 emitNeedRender();
606 }
607}
608
609void QQuickGraphsSurface::handleRowsAdded(qsizetype startIndex, qsizetype count)
610{
611 Q_UNUSED(startIndex);
612 Q_UNUSED(count);
613 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
614 if (series->isVisible()) {
615 adjustAxisRanges();
616 setDataDirty(true);
617 }
618 if (!m_changedSeriesList.contains(t: series))
619 m_changedSeriesList.append(t: series);
620 emitNeedRender();
621}
622
623void QQuickGraphsSurface::handleRowsInserted(qsizetype startIndex, qsizetype count)
624{
625 Q_UNUSED(startIndex);
626 Q_UNUSED(count);
627 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
628 if (series == m_selectedSeries) {
629 // If rows inserted to selected series before the selection, adjust the selection
630 int selectedRow = m_selectedPoint.x();
631 if (startIndex <= selectedRow) {
632 selectedRow += count;
633 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
634 }
635 }
636
637 if (series->isVisible()) {
638 adjustAxisRanges();
639 setDataDirty(true);
640 }
641 if (!m_changedSeriesList.contains(t: series))
642 m_changedSeriesList.append(t: series);
643
644 emitNeedRender();
645}
646
647void QQuickGraphsSurface::handleRowsRemoved(qsizetype startIndex, qsizetype count)
648{
649 Q_UNUSED(startIndex);
650 Q_UNUSED(count);
651 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
652 if (series == m_selectedSeries) {
653 // If rows removed from selected series before the selection, adjust the selection
654 int selectedRow = m_selectedPoint.x();
655 if (startIndex <= selectedRow) {
656 if ((startIndex + count) > selectedRow)
657 selectedRow = -1; // Selected row removed
658 else
659 selectedRow -= count; // Move selected row down by amount of rows removed
660
661 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
662 }
663 }
664
665 if (series->isVisible()) {
666 adjustAxisRanges();
667 setDataDirty(true);
668 }
669 if (!m_changedSeriesList.contains(t: series))
670 m_changedSeriesList.append(t: series);
671
672 emitNeedRender();
673}
674
675QPoint QQuickGraphsSurface::invalidSelectionPosition()
676{
677 static QPoint invalidSelectionPoint(-1, -1);
678 return invalidSelectionPoint;
679}
680
681void QQuickGraphsSurface::setSelectedPoint(const QPoint position,
682 QSurface3DSeries *series,
683 bool enterSlice)
684{
685 // If the selection targets non-existent point, clear selection instead.
686 QPoint pos = position;
687
688 // Series may already have been removed, so check it before setting the selection.
689 if (!m_seriesList.contains(t: series))
690 series = 0;
691
692 const QSurfaceDataProxy *proxy = 0;
693 if (series)
694 proxy = series->dataProxy();
695
696 if (!proxy)
697 pos = invalidSelectionPosition();
698
699 if (pos != invalidSelectionPosition()) {
700 qsizetype maxRow = proxy->rowCount() - 1;
701 qsizetype maxCol = proxy->columnCount() - 1;
702
703 if (pos.y() < 0 || pos.y() > maxRow || pos.x() < 0 || pos.x() > maxCol)
704 pos = invalidSelectionPosition();
705 }
706
707 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
708 if (pos == invalidSelectionPosition() || !series->isVisible()) {
709 scene()->setSlicingActive(false);
710 } else {
711 // If the selected point is outside data window, or there is no selected
712 // point, disable slicing
713 float axisMinX = m_axisX->min();
714 float axisMaxX = m_axisX->max();
715 float axisMinZ = m_axisZ->min();
716 float axisMaxZ = m_axisZ->max();
717
718 QSurfaceDataItem item = series->dataArray().at(i: pos.y()).at(i: pos.x());
719 if (item.x() < axisMinX || item.x() > axisMaxX || item.z() < axisMinZ
720 || item.z() > axisMaxZ) {
721 scene()->setSlicingActive(false);
722 } else if (enterSlice) {
723 scene()->setSlicingActive(true);
724 }
725 }
726 emitNeedRender();
727 }
728
729 if (pos != m_selectedPoint || series != m_selectedSeries) {
730 bool seriesChanged = (series != m_selectedSeries);
731 m_selectedPoint = pos;
732 m_selectedSeries = series;
733 m_changeTracker.selectedPointChanged = true;
734
735 // Clear selection from other series and finally set new selection to the
736 // specified series
737 for (QAbstract3DSeries *otherSeries : m_seriesList) {
738 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
739 if (surfaceSeries != m_selectedSeries)
740 surfaceSeries->d_func()->setSelectedPoint(invalidSelectionPosition());
741 }
742 if (m_selectedSeries)
743 m_selectedSeries->d_func()->setSelectedPoint(m_selectedPoint);
744
745 if (seriesChanged)
746 emit selectedSeriesChanged(series: m_selectedSeries);
747
748 emitNeedRender();
749 }
750}
751
752void QQuickGraphsSurface::setSelectionMode(QtGraphs3D::SelectionFlags mode)
753{
754 // Currently surface only supports row and column modes when also slicing
755 if ((mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
756 || mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))
757 && !mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
758 qWarning(msg: "Unsupported selection mode.");
759 return;
760 } else if (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
761 && (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
762 == mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
763 qWarning(msg: "Must specify one of either row or column selection mode"
764 "in conjunction with slicing mode.");
765 } else {
766 QtGraphs3D::SelectionFlags oldMode = selectionMode();
767
768 QQuickGraphsItem::setSelectionMode(mode);
769
770 if (mode != oldMode) {
771 // Refresh selection upon mode change to ensure slicing is correctly
772 // updated according to series the visibility.
773 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: true);
774
775 // Special case: Always deactivate slicing when changing away from slice
776 // automanagement, as this can't be handled in setSelectedBar.
777 if (!mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
778 && oldMode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
779 scene()->setSlicingActive(false);
780 }
781 }
782 }
783}
784
785void QQuickGraphsSurface::handleAxisAutoAdjustRangeChangedInOrientation(
786 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
787{
788 Q_UNUSED(orientation);
789 Q_UNUSED(autoAdjust);
790
791 adjustAxisRanges();
792}
793
794void QQuickGraphsSurface::handleAxisRangeChangedBySender(QObject *sender)
795{
796 QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
797
798 // Update selected point - may be moved offscreen
799 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
800}
801
802void QQuickGraphsSurface::handleSeriesVisibilityChangedBySender(QObject *sender)
803{
804 QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
805
806 setSeriesVisibilityDirty(true);
807 // Visibility changes may require disabling slicing,
808 // so just reset selection to ensure everything is still valid.
809 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
810}
811
812void QQuickGraphsSurface::setFlipHorizontalGrid(bool flip)
813{
814 if (m_flipHorizontalGrid != flip) {
815 m_flipHorizontalGrid = flip;
816 m_changeTracker.flipHorizontalGridChanged = true;
817 emit flipHorizontalGridChanged(flip);
818 emitNeedRender();
819 }
820}
821
822bool QQuickGraphsSurface::flipHorizontalGrid() const
823{
824 return m_flipHorizontalGrid;
825}
826
827bool QQuickGraphsSurface::isFlatShadingSupported()
828{
829 return m_flatShadingSupported;
830}
831
832QList<QSurface3DSeries *> QQuickGraphsSurface::surfaceSeriesList()
833{
834 QList<QSurface3DSeries *> surfaceSeriesList;
835 for (QAbstract3DSeries *abstractSeries : m_seriesList) {
836 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
837 if (surfaceSeries)
838 surfaceSeriesList.append(t: surfaceSeries);
839 }
840
841 return surfaceSeriesList;
842}
843
844void QQuickGraphsSurface::updateSurfaceTexture(QSurface3DSeries *series)
845{
846 m_changeTracker.surfaceTextureChanged = true;
847
848 if (!m_changedTextures.contains(t: series))
849 m_changedTextures.append(t: series);
850
851 emitNeedRender();
852}
853
854QQmlListProperty<QSurface3DSeries> QQuickGraphsSurface::seriesList()
855{
856 return QQmlListProperty<QSurface3DSeries>(this,
857 this,
858 &QQuickGraphsSurface::appendSeriesFunc,
859 &QQuickGraphsSurface::countSeriesFunc,
860 &QQuickGraphsSurface::atSeriesFunc,
861 &QQuickGraphsSurface::clearSeriesFunc);
862}
863
864void QQuickGraphsSurface::appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
865 QSurface3DSeries *series)
866{
867 reinterpret_cast<QQuickGraphsSurface *>(list->data)->addSeries(series);
868}
869
870qsizetype QQuickGraphsSurface::countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
871{
872 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().size();
873}
874
875QSurface3DSeries *QQuickGraphsSurface::atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list,
876 qsizetype index)
877{
878 return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().at(i: index);
879}
880
881void QQuickGraphsSurface::clearSeriesFunc(QQmlListProperty<QSurface3DSeries> *list)
882{
883 QQuickGraphsSurface *declSurface = reinterpret_cast<QQuickGraphsSurface *>(list->data);
884 QList<QSurface3DSeries *> realList = declSurface->surfaceSeriesList();
885 qsizetype count = realList.size();
886 for (qsizetype i = 0; i < count; i++)
887 declSurface->removeSeries(series: realList.at(i));
888}
889
890void QQuickGraphsSurface::addSeries(QSurface3DSeries *series)
891{
892 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Surface);
893
894 QQuickGraphsItem::addSeriesInternal(series);
895
896 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
897 if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
898 setSelectedPoint(position: surfaceSeries->selectedPoint(), series: surfaceSeries, enterSlice: false);
899
900 if (!surfaceSeries->texture().isNull())
901 updateSurfaceTexture(series: surfaceSeries);
902
903 if (isReady())
904 addModel(series);
905}
906
907void QQuickGraphsSurface::removeSeries(QSurface3DSeries *series)
908{
909 bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
910
911 QQuickGraphsItem::removeSeriesInternal(series);
912
913 if (m_selectedSeries == series)
914 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
915
916 if (wasVisible)
917 adjustAxisRanges();
918
919 series->setParent(this); // Reparent as removing will leave series parentless
920 for (int i = 0; i < m_model.size();) {
921 if (m_model[i]->series == series) {
922 m_model[i]->model->deleteLater();
923 m_model[i]->gridModel->deleteLater();
924 if (const auto &proxy = m_model[i]->proxyModel)
925 proxy->deleteLater();
926 if (sliceView()) {
927 m_model[i]->sliceModel->deleteLater();
928 m_model[i]->sliceGridModel->deleteLater();
929 }
930 delete m_model[i];
931 m_model.removeAt(i);
932 } else {
933 ++i;
934 }
935 }
936}
937
938void QQuickGraphsSurface::clearSelection()
939{
940 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
941}
942
943void QQuickGraphsSurface::handleAxisXChanged(QAbstract3DAxis *axis)
944{
945 emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis));
946}
947
948void QQuickGraphsSurface::handleAxisYChanged(QAbstract3DAxis *axis)
949{
950 emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis));
951}
952
953void QQuickGraphsSurface::handleAxisZChanged(QAbstract3DAxis *axis)
954{
955 emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis));
956}
957
958void QQuickGraphsSurface::componentComplete()
959{
960 QQuickGraphsItem::componentComplete();
961
962 for (auto series : surfaceSeriesList()) {
963 addModel(series);
964 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
965 }
966
967 graphsInputHandler()->setGraphsItem(this);
968}
969
970void QQuickGraphsSurface::synchData()
971{
972 if (isFlipHorizontalGridChanged())
973 setHorizontalFlipFactor(flipHorizontalGrid() ? -1 : 1);
974
975 QQuickGraphsItem::synchData();
976
977 if (isSelectedPointChanged()) {
978 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
979 updateSelectedPoint();
980 setSelectedPointChanged(false);
981 }
982
983 if (isGridUpdated() || isFlipHorizontalGridChanged())
984 handleFlipHorizontalGridChanged(flip: flipHorizontalGrid());
985
986 if (isSurfaceTextureChanged()) {
987 if (!isChangedTexturesEmpty()) {
988 for (auto model : m_model) {
989 if (hasSeriesToChangeTexture(series: model->series))
990 updateMaterial(model);
991 }
992 }
993 setSurfaceTextureChanged(false);
994 }
995
996 if (gridLineType() == QtGraphs3D::GridLineType::Shader) {
997 if (!m_topGrid) {
998 //add horizontal top grid
999 QUrl topGridUrl = QUrl(QStringLiteral(":/defaultMeshes/barMeshFull"));
1000 m_topGrid = new QQuick3DModel();
1001 m_topGridScale = new QQuick3DNode();
1002 m_topGridRotation = new QQuick3DNode();
1003
1004 m_topGridScale->setParent(rootNode());
1005 m_topGridScale->setParentItem(rootNode());
1006
1007 m_topGridRotation->setParent(m_topGridScale);
1008 m_topGridRotation->setParentItem(m_topGridScale);
1009
1010 m_topGrid->setObjectName("Top Grid");
1011 m_topGrid->setParent(m_topGridRotation);
1012 m_topGrid->setParentItem(m_topGridRotation);
1013
1014 m_topGrid->setSource(topGridUrl);
1015 m_topGrid->setPickable(false);
1016 }
1017 auto min = qMin(a: scaleWithBackground().x() + backgroundScaleMargin().x(),
1018 b: scaleWithBackground().z() + backgroundScaleMargin().z());
1019 m_topGridScale->setScale(QVector3D(scaleWithBackground().x() + backgroundScaleMargin().x(),
1020 min * gridOffset(),
1021 scaleWithBackground().z() + backgroundScaleMargin().z()));
1022 m_topGridScale->setPosition(
1023 QVector3D(0.0f, scaleWithBackground().y() + backgroundScaleMargin().y(), 0.0f));
1024
1025 m_topGrid->setVisible(m_flipHorizontalGrid);
1026 QQmlListReference materialsRefF(m_topGrid, "materials");
1027 QQmlListReference bbRef(background(), "materials");
1028 QQuick3DCustomMaterial *bgMatFloor;
1029 if (!materialsRefF.size() && bbRef.size()) {
1030 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(bbRef.at(0));
1031 materialsRefF.append(bgMatFloor);
1032 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1033 } else if (materialsRefF.size()) {
1034 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(materialsRefF.at(0));
1035 bgMatFloor->setProperty(name: "gridOnTop", value: m_flipHorizontalGrid);
1036 }
1037 }
1038
1039 if (m_pickThisFrame) {
1040 doPicking(position: m_lastPick);
1041 m_pickThisFrame = false;
1042 }
1043}
1044
1045void QQuickGraphsSurface::updateGraph()
1046{
1047 for (auto model : m_model) {
1048 bool seriesVisible = model->series->isVisible();
1049 if (isSeriesVisibilityDirty()) {
1050 if (!seriesVisible) {
1051 model->model->setVisible(seriesVisible);
1052 model->gridModel->setVisible(seriesVisible);
1053 if (sliceView()) {
1054 model->sliceModel->setVisible(seriesVisible);
1055 model->sliceGridModel->setVisible(seriesVisible);
1056
1057 if (m_selectedSeries == model->series) {
1058 clearSelection();
1059 setSliceActivatedChanged(true);
1060 m_selectionDirty = !seriesVisible;
1061 }
1062 }
1063 continue;
1064 }
1065 }
1066
1067 if (model->model->visible() != seriesVisible)
1068 model->model->setVisible(seriesVisible);
1069
1070 model->gridModel->setVisible(
1071 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe) && seriesVisible);
1072 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1073 model->model->setLocalOpacity(1.f);
1074 else
1075 model->model->setLocalOpacity(.0f);
1076
1077 if (sliceView() && sliceView()->isVisible()) {
1078 model->sliceGridModel->setVisible(
1079 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1080 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1081 model->sliceModel->setLocalOpacity(1.f);
1082 else
1083 model->sliceModel->setLocalOpacity(.0f);
1084 }
1085 updateMaterial(model);
1086 }
1087
1088 setSeriesVisibilityDirty(false);
1089 if (isDataDirty() || isSeriesVisualsDirty()) {
1090 if (hasChangedSeriesList()) {
1091 handleChangedSeries();
1092 } else {
1093 for (auto model : m_model) {
1094 bool visible = model->series->isVisible();
1095 if (visible)
1096 updateModel(model);
1097 }
1098 }
1099
1100 if (isSliceEnabled()) {
1101 if (!sliceView())
1102 createSliceView();
1103
1104 if (sliceView()->isVisible()) {
1105 if (!m_selectedSeries) {
1106 m_selectionDirty = true;
1107 setSliceActivatedChanged(true);
1108 }
1109 toggleSliceGraph();
1110 }
1111 }
1112
1113 setDataDirty(false);
1114 setSeriesVisualsDirty(false);
1115 }
1116
1117 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))
1118 updateSelectedPoint();
1119}
1120
1121void QQuickGraphsSurface::calculateSceneScalingFactors()
1122{
1123 float scaleX, scaleY, scaleZ;
1124 float marginH, marginV;
1125
1126 if (margin() < 0.0f) {
1127 marginH = .1f;
1128 marginV = .1f;
1129 } else {
1130 marginH = margin();
1131 marginV = margin();
1132 }
1133
1134 if (isPolar()) {
1135 float polarMargin = calculatePolarBackgroundMargin();
1136 marginH = qMax(a: marginH, b: polarMargin);
1137 }
1138 float hAspectRatio;
1139 if (isPolar())
1140 hAspectRatio = 1.0f;
1141 else
1142 hAspectRatio = horizontalAspectRatio();
1143
1144 QSizeF areaSize;
1145 if (qFuzzyIsNull(f: hAspectRatio)) {
1146 areaSize.setHeight(axisZ()->max() - axisZ()->min());
1147 areaSize.setWidth(axisX()->max() - axisX()->min());
1148 } else {
1149 areaSize.setHeight(1.0);
1150 areaSize.setWidth(hAspectRatio);
1151 }
1152
1153 float horizontalMaxDimension;
1154 if (aspectRatio() > 2.0f) {
1155 horizontalMaxDimension = 2.0f;
1156 scaleY = 2.0f / aspectRatio();
1157 } else {
1158 horizontalMaxDimension = aspectRatio();
1159 scaleY = 1.0f;
1160 }
1161
1162 if (isPolar())
1163 m_polarRadius = horizontalMaxDimension;
1164
1165 float scaleFactor = qMax(a: areaSize.width(), b: areaSize.height());
1166 scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
1167 scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
1168
1169 setScale(QVector3D(scaleX, scaleY, scaleZ));
1170 setScaleWithBackground(QVector3D(scaleX, scaleY, scaleZ));
1171 setBackgroundScaleMargin(QVector3D(marginH, marginV, marginH));
1172}
1173
1174void QQuickGraphsSurface::handleChangedSeries()
1175{
1176 auto changedSeries = changedSeriesList();
1177 for (auto series : changedSeries) {
1178 for (auto model : m_model) {
1179 if (model->series == series) {
1180 updateModel(model);
1181 }
1182 }
1183 }
1184}
1185
1186inline static float getDataValue(const QSurfaceDataArray &array, bool searchRow, qsizetype index)
1187{
1188 if (searchRow)
1189 return array.at(i: 0).at(i: index).x();
1190 else
1191 return array.at(i: index).at(i: 0).z();
1192}
1193
1194inline static int binarySearchArray(const QSurfaceDataArray &array,
1195 qsizetype maxIndex,
1196 float limitValue,
1197 bool searchRow,
1198 bool lowBound,
1199 bool ascending)
1200{
1201 qsizetype min = 0;
1202 qsizetype max = maxIndex;
1203 qsizetype mid = 0;
1204 qsizetype retVal;
1205
1206 while (max >= min) {
1207 mid = (min + max) / 2;
1208 float arrayValue = getDataValue(array, searchRow, index: mid);
1209 if (arrayValue == limitValue)
1210 return int(mid);
1211 if (ascending) {
1212 if (arrayValue < limitValue)
1213 min = mid + 1;
1214 else
1215 max = mid - 1;
1216 } else {
1217 if (arrayValue > limitValue)
1218 min = mid + 1;
1219 else
1220 max = mid - 1;
1221 }
1222 }
1223
1224 if (lowBound == ascending) {
1225 if (mid > max)
1226 retVal = mid;
1227 else
1228 retVal = min;
1229 } else {
1230 if (mid > max)
1231 retVal = max;
1232 else
1233 retVal = mid;
1234 }
1235
1236 if (retVal < 0 || retVal > maxIndex) {
1237 retVal = -1;
1238 } else if (lowBound) {
1239 if (getDataValue(array, searchRow, index: retVal) < limitValue)
1240 retVal = -1;
1241 } else {
1242 if (getDataValue(array, searchRow, index: retVal) > limitValue)
1243 retVal = -1;
1244 }
1245 return int(retVal);
1246}
1247
1248QRect QQuickGraphsSurface::calculateSampleSpace(SurfaceModel *model)
1249{
1250 QRect sampleSpace;
1251 const QSurfaceDataArray &array = model->series->dataArray();
1252 if (array.size() > 0) {
1253 if (array.size() >= 2 && array.at(i: 0).size() >= 2) {
1254 const qsizetype maxRow = array.size() - 1;
1255 const qsizetype maxColumn = array.at(i: 0).size() - 1;
1256
1257 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
1258 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1259
1260 if (model->ascendingX != ascendingX) {
1261 setIndexDirty(true);
1262 model->ascendingX = ascendingX;
1263 }
1264 if (model->ascendingZ != ascendingZ) {
1265 setIndexDirty(true);
1266 model->ascendingZ = ascendingZ;
1267 }
1268
1269 int idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->min(), searchRow: true, lowBound: true, ascending: ascendingX);
1270 if (idx != -1) {
1271 if (ascendingX)
1272 sampleSpace.setLeft(idx);
1273 else
1274 sampleSpace.setRight(idx);
1275 } else {
1276 sampleSpace.setWidth(-1);
1277 return sampleSpace;
1278 }
1279
1280 idx = binarySearchArray(array, maxIndex: maxColumn, limitValue: axisX()->max(), searchRow: true, lowBound: false, ascending: ascendingX);
1281 if (idx != -1) {
1282 if (ascendingX)
1283 sampleSpace.setRight(idx);
1284 else
1285 sampleSpace.setLeft(idx);
1286 } else {
1287 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1288 return sampleSpace;
1289 }
1290
1291 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->min(), searchRow: false, lowBound: true, ascending: ascendingZ);
1292 if (idx != -1) {
1293 if (ascendingZ)
1294 sampleSpace.setTop(idx);
1295 else
1296 sampleSpace.setBottom(idx);
1297 } else {
1298 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1299 return sampleSpace;
1300 }
1301
1302 idx = binarySearchArray(array, maxIndex: maxRow, limitValue: axisZ()->max(), searchRow: false, lowBound: false, ascending: ascendingZ);
1303 if (idx != -1) {
1304 if (ascendingZ)
1305 sampleSpace.setBottom(idx);
1306 else
1307 sampleSpace.setTop(idx);
1308 } else {
1309 sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
1310 return sampleSpace;
1311 }
1312 }
1313 }
1314 return sampleSpace;
1315}
1316
1317void QQuickGraphsSurface::updateModel(SurfaceModel *model)
1318{
1319 const QSurfaceDataArray &array = model->series->dataArray();
1320
1321 if (!array.isEmpty()) {
1322 qsizetype rowCount = array.size();
1323 qsizetype columnCount = array.at(i: 0).size();
1324
1325 const qsizetype maxSize = 4096; // maximum texture size
1326 columnCount = qMin(a: maxSize, b: columnCount);
1327 rowCount = qMin(a: maxSize, b: rowCount);
1328
1329 if (model->rowCount != rowCount) {
1330 model->rowCount = rowCount;
1331 setIndexDirty(true);
1332 }
1333 if (model->columnCount != columnCount) {
1334 model->columnCount = columnCount;
1335 setIndexDirty(true);
1336 }
1337
1338 bool dimensionsChanged = false;
1339 QRect sampleSpace = calculateSampleSpace(model);
1340 if (sampleSpace != model->sampleSpace) {
1341 dimensionsChanged = true;
1342 model->sampleSpace = sampleSpace;
1343 }
1344 int rowStart = sampleSpace.top();
1345 int columnStart = sampleSpace.left();
1346 int rowLimit = sampleSpace.bottom() + 1;
1347 int columnLimit = sampleSpace.right() + 1;
1348
1349 QPoint selC = model->selectedVertex.coord;
1350 selC.setX(qMin(a: selC.x(), b: int(columnCount) - 1));
1351 selC.setY(qMin(a: selC.y(), b: int(rowCount) - 1));
1352 QVector3D selP = array.at(i: selC.y()).at(i: selC.x()).position();
1353
1354 bool pickOutOfRange = false;
1355 if (selP.x() < axisX()->min() || selP.x() > axisX()->max() || selP.z() < axisZ()->min()
1356 || selP.z() > axisZ()->max()) {
1357 pickOutOfRange = true;
1358 }
1359
1360 if (m_isIndexDirty || pickOutOfRange) {
1361 model->selectedVertex = SurfaceVertex();
1362 if (sliceView() && sliceView()->isVisible() && model->series == m_selectedSeries) {
1363 setSlicingActive(false);
1364 setSliceActivatedChanged(true);
1365 m_selectionDirty = true;
1366 }
1367 }
1368 qsizetype totalSize = rowCount * columnCount * 2;
1369 float uvX = 1.0f / float(columnCount - 1);
1370 float uvY = 1.0f / float(rowCount - 1);
1371
1372 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1373
1374 QVector3D boundsMin = model->boundsMin;
1375 QVector3D boundsMax = model->boundsMax;
1376
1377 QVector<QVector4D> heights;
1378 heights.reserve(asize: totalSize);
1379
1380 QQmlListReference materialRef(model->model, "materials");
1381 auto material = materialRef.at(0);
1382 QVariant heightInputAsVariant = material->property(name: "height");
1383 QQuick3DShaderUtilsTextureInput *heightInput
1384 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1385 QQuick3DTexture *heightMap = heightInput->texture();
1386 QQuick3DTextureData *heightMapData = nullptr;
1387 if (!heightMap) {
1388 heightMap = new QQuick3DTexture();
1389 heightMap->setParent(this);
1390 heightMap->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1391 heightMap->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1392 heightMap->setMinFilter(QQuick3DTexture::Nearest);
1393 heightMap->setMagFilter(QQuick3DTexture::Nearest);
1394 heightMapData = new QQuick3DTextureData();
1395 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1396 heightMapData->setFormat(QQuick3DTextureData::RGBA32F);
1397 heightMapData->setParent(heightMap);
1398 heightMapData->setParentItem(heightMap);
1399 } else {
1400 heightMapData = heightMap->textureData();
1401 if (dimensionsChanged)
1402 heightMapData->setSize(QSize(sampleSpace.width(), sampleSpace.height()));
1403 }
1404 if (heightMapData->size().width() < 1 || heightMapData->size().height() < 1) {
1405 heightMapData->setTextureData(QByteArray());
1406 heightMap->setTextureData(heightMapData);
1407 heightInput->setTexture(heightMap);
1408 model->heightTexture = heightMap;
1409 return;
1410 }
1411
1412 material->setProperty(name: "xDiff", value: 1.0f / float(sampleSpace.width() - 1));
1413 material->setProperty(name: "yDiff", value: 1.0f / float(sampleSpace.height() - 1));
1414 material->setProperty(name: "flatShading", value: flatShading);
1415 material->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1416 material->setProperty(name: "uvOffset", value: QVector2D(columnStart, rowStart));
1417 material->setProperty(name: "size", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1418 material->setProperty(name: "vertCount", value: QVector2D(columnCount, rowCount));
1419 material->setProperty(name: "flipU", value: !model->ascendingX);
1420 material->setProperty(name: "flipV", value: !model->ascendingZ);
1421
1422 model->vertices.clear();
1423 model->vertices.reserve(asize: totalSize);
1424
1425 for (int i = rowStart; i < rowLimit; i++) {
1426 const QSurfaceDataRow &row = array.at(i);
1427 for (int j = columnStart; j < columnLimit; j++) {
1428 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1429 heights.push_back(t: QVector4D(pos, .0f));
1430 SurfaceVertex vertex;
1431 vertex.position = pos;
1432 vertex.uv = QVector2D(j * uvX, i * uvY);
1433 vertex.coord = QPoint(j, i);
1434 model->vertices.push_back(t: vertex);
1435 if (boundsMin.isNull())
1436 boundsMin = pos;
1437 else
1438 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: pos.x()),
1439 qMin(a: boundsMin.y(), b: pos.y()),
1440 qMin(a: boundsMin.z(), b: pos.z()));
1441 if (boundsMax.isNull())
1442 boundsMax = pos;
1443 else
1444 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: pos.x()),
1445 qMax(a: boundsMax.y(), b: pos.y()),
1446 qMax(a: boundsMax.z(), b: pos.z()));
1447 }
1448 }
1449 model->boundsMin = boundsMin;
1450 model->boundsMax = boundsMax;
1451
1452 QByteArray heightData = QByteArray(reinterpret_cast<char *>(heights.data()),
1453 heights.size() * sizeof(QVector4D));
1454 heightMapData->setTextureData(heightData);
1455 heightMap->setTextureData(heightMapData);
1456 heightInput->setTexture(heightMap);
1457 model->heightTexture = heightMap;
1458
1459 if (m_isIndexDirty) {
1460 QVector<SurfaceVertex> vertices;
1461 for (int i = 0; i < rowCount; i++) {
1462 QSurfaceDataRow row = array.at(i);
1463 for (int j = 0; j < columnCount; j++) {
1464 SurfaceVertex vertex;
1465 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1466 vertex.position = pos;
1467 float uStep = model->ascendingX ? j * uvX : 1 - (j * uvX);
1468 float vStep = model->ascendingZ ? i * uvY : 1 - (i * uvY);
1469
1470 vertex.uv = QVector2D(uStep, vStep);
1471 vertex.coord = QPoint(j, i);
1472 vertices.push_back(t: vertex);
1473 }
1474 }
1475 createIndices(model, columnCount, rowCount);
1476 auto geometry = model->model->geometry();
1477 geometry->vertexData().clear();
1478 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()),
1479 vertices.size() * sizeof(SurfaceVertex));
1480 geometry->setVertexData(vertexBuffer);
1481 QByteArray indexBuffer(reinterpret_cast<char *>(model->indices.data()),
1482 model->indices.size() * sizeof(quint32));
1483 geometry->setIndexData(indexBuffer);
1484 geometry->setBounds(min: boundsMin, max: boundsMax);
1485 geometry->update();
1486
1487 createGridlineIndices(model, x: 0, y: 0, endX: columnCount, endY: rowCount);
1488 auto gridGeometry = model->gridModel->geometry();
1489 gridGeometry->vertexData().clear();
1490 gridGeometry->setVertexData(vertexBuffer);
1491 QByteArray gridIndexBuffer(reinterpret_cast<char *>(model->gridIndices.data()),
1492 model->gridIndices.size() * sizeof(quint32));
1493 gridGeometry->setIndexData(gridIndexBuffer);
1494 gridGeometry->setBounds(min: boundsMin, max: boundsMax);
1495 gridGeometry->update();
1496 m_isIndexDirty = false;
1497 }
1498 QQmlListReference gridMaterialRef(model->gridModel, "materials");
1499 auto gridMaterial = gridMaterialRef.at(0);
1500 QVariant gridHeightInputAsVariant = gridMaterial->property(name: "height");
1501 QQuick3DShaderUtilsTextureInput *gridHeightInput
1502 = gridHeightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1503 gridHeightInput->setTexture(heightMap);
1504 QColor gridColor = model->series->wireframeColor();
1505 gridMaterial->setProperty(name: "gridColor", value: gridColor);
1506 gridMaterial->setProperty(name: "range", value: QVector2D(sampleSpace.width(), sampleSpace.height()));
1507 gridMaterial->setProperty(name: "vertices", value: QVector2D(columnCount, rowCount));
1508 gridMaterial->setProperty(name: "graphHeight", value: scaleWithBackground().y());
1509
1510 m_proxyDirty = true;
1511 }
1512 updateMaterial(model);
1513 updateSelectedPoint();
1514}
1515
1516void QQuickGraphsSurface::updateProxyModel(SurfaceModel *model)
1517{
1518 if (!model->proxyModel)
1519 createProxyModel(parentModel: model);
1520
1521 const QSurfaceDataArray &array = model->series->dataArray();
1522 if (array.isEmpty())
1523 return;
1524
1525 QRect sampleSpace = model->sampleSpace;
1526 int rowCount = sampleSpace.height();
1527 int columnCount = sampleSpace.width();
1528 int rowStart = sampleSpace.top();
1529 int columnStart = sampleSpace.left();
1530 int rowLimit = sampleSpace.bottom() + 1;
1531 int columnLimit = sampleSpace.right() + 1;
1532 if (rowCount == 0 || columnCount == 0)
1533 return;
1534
1535 // calculate decimate factor based on the order of magnitude of total vertices
1536
1537 int minBeforeDecimate = 1000;
1538 float totalSize = rowCount * columnCount;
1539 int decimateFactor = qMax(a: qFloor(v: std::log10(x: qMax(a: 1.0, b: totalSize - minBeforeDecimate))), b: 1);
1540
1541 int proxyColumnCount = 0;
1542 int proxyRowCount = 0;
1543 QVector<SurfaceVertex> proxyVerts;
1544
1545 float uvY = 1.0f / float(rowCount - 1);
1546 float uvX = 1.0f / float(columnCount - 1);
1547
1548 QVector3D boundsMin = model->boundsMin;
1549 QVector3D boundsMax = model->boundsMax;
1550
1551 int i = rowStart;
1552 while (i < rowLimit) {
1553 const QSurfaceDataRow &row = array.at(i);
1554 proxyRowCount++;
1555 int j = columnStart;
1556 while (j < columnLimit) {
1557 // getNormalizedVertex
1558 if (i == rowStart)
1559 proxyColumnCount++;
1560 QVector3D pos = getNormalizedVertex(data: row.at(i: j), polar: isPolar(), flipXZ: false);
1561 SurfaceVertex vertex;
1562 vertex.position = pos;
1563 vertex.uv = QVector2D(j * uvX, i * uvY);
1564 vertex.coord = QPoint(i, j);
1565 proxyVerts.push_back(t: vertex);
1566
1567 boundsMin = QVector3D(qMin(a: boundsMin.x(), b: pos.x()),
1568 qMin(a: boundsMin.y(), b: pos.y()),
1569 qMin(a: boundsMin.z(), b: pos.z()));
1570 boundsMax = QVector3D(qMax(a: boundsMax.x(), b: pos.x()),
1571 qMax(a: boundsMax.y(), b: pos.y()),
1572 qMax(a: boundsMax.z(), b: pos.z()));
1573
1574 if (j == columnLimit - 1)
1575 break;
1576
1577 j += decimateFactor;
1578 if (j >= columnLimit)
1579 j = columnLimit - 1;
1580 }
1581 if (i == rowLimit - 1)
1582 break;
1583
1584 i += decimateFactor;
1585 if (i >= rowLimit)
1586 i = rowLimit - 1;
1587 }
1588
1589 model->boundsMin = boundsMin;
1590 model->boundsMax = boundsMax;
1591 int endX = proxyColumnCount - 1;
1592 int endY = proxyRowCount - 1;
1593 int indexCount = 6 * endX * endY;
1594
1595 QVector<quint32> proxyIndices;
1596 proxyIndices.resize(size: indexCount);
1597
1598 int rowEnd = endY * proxyColumnCount;
1599 for (int row = 0; row < rowEnd; row += proxyColumnCount) {
1600 for (int j = 0; j < endX; j++) {
1601 if (model->ascendingX == model->ascendingZ) {
1602 proxyIndices.push_back(t: row + j + 1);
1603 proxyIndices.push_back(t: row + proxyColumnCount + j);
1604 proxyIndices.push_back(t: row + j);
1605
1606 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1607 proxyIndices.push_back(t: row + proxyColumnCount + j);
1608 proxyIndices.push_back(t: row + j + 1);
1609 } else if (!model->ascendingX) {
1610 proxyIndices.push_back(t: row + proxyColumnCount + j);
1611 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1612 proxyIndices.push_back(t: row + j);
1613
1614 proxyIndices.push_back(t: row + j);
1615 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1616 proxyIndices.push_back(t: row + j + 1);
1617 } else {
1618 proxyIndices.push_back(t: row + proxyColumnCount + j);
1619 proxyIndices.push_back(t: row + proxyColumnCount + j + 1);
1620 proxyIndices.push_back(t: row + j + 1);
1621
1622 proxyIndices.push_back(t: row + j);
1623 proxyIndices.push_back(t: row + proxyColumnCount + j);
1624 proxyIndices.push_back(t: row + j + 1);
1625 }
1626 }
1627 }
1628
1629 auto geometry = model->proxyModel->geometry();
1630 geometry->vertexData().clear();
1631 QByteArray vertexBuffer(reinterpret_cast<char *>(proxyVerts.data()),
1632 proxyVerts.size() * sizeof(SurfaceVertex));
1633 geometry->setVertexData(vertexBuffer);
1634 QByteArray indexBuffer(reinterpret_cast<char *>(proxyIndices.data()),
1635 proxyIndices.size() * sizeof(quint32));
1636 geometry->setIndexData(indexBuffer);
1637 geometry->setBounds(min: boundsMin, max: boundsMax);
1638 geometry->update();
1639 m_proxyDirty = false;
1640}
1641
1642void QQuickGraphsSurface::createProxyModel(SurfaceModel *model)
1643{
1644 auto proxyModel = new QQuick3DModel();
1645 proxyModel->setParent(graphNode());
1646 proxyModel->setParentItem(model->model);
1647 proxyModel->setObjectName(QStringLiteral("ProxyModel"));
1648 proxyModel->setVisible(true);
1649 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
1650 proxyModel->setPickable(false);
1651 else
1652 proxyModel->setPickable(true);
1653
1654 auto geometry = new QQuick3DGeometry();
1655 geometry->setParent(proxyModel);
1656 geometry->setStride(sizeof(SurfaceVertex));
1657 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
1658 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
1659 offset: 0,
1660 componentType: QQuick3DGeometry::Attribute::F32Type);
1661 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
1662 offset: sizeof(QVector3D),
1663 componentType: QQuick3DGeometry::Attribute::F32Type);
1664 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
1665 offset: 0,
1666 componentType: QQuick3DGeometry::Attribute::U32Type);
1667 proxyModel->setGeometry(geometry);
1668
1669 QQmlListReference materialRef(proxyModel, "materials");
1670 QQuick3DPrincipledMaterial *material = new QQuick3DPrincipledMaterial();
1671 material->setParent(proxyModel);
1672 material->setBaseColor(Qt::white);
1673 material->setOpacity(0);
1674 material->setCullMode(QQuick3DMaterial::NoCulling);
1675 materialRef.append(material);
1676
1677 model->proxyModel = proxyModel;
1678}
1679
1680void QQuickGraphsSurface::updateMaterial(SurfaceModel *model)
1681{
1682 QQmlListReference materialRef(model->model, "materials");
1683
1684 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
1685
1686 if (!material) {
1687 material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceMaterial"));
1688 model->customMaterial = material;
1689 }
1690
1691 bool textured = !(model->series->texture().isNull() && model->series->textureFile().isEmpty());
1692
1693 if (isSeriesVisualsDirty() || !textured) {
1694 float minY = model->boundsMin.y();
1695 float maxY = model->boundsMax.y();
1696 float range = maxY - minY;
1697
1698 switch (model->series->colorStyle()) {
1699 case (QGraphsTheme::ColorStyle::ObjectGradient):
1700 material->setProperty(name: "colorStyle", value: 0);
1701 material->setProperty(name: "gradientMin", value: -(minY / range));
1702 material->setProperty(name: "gradientHeight", value: 1.0f / range);
1703 break;
1704 case (QGraphsTheme::ColorStyle::RangeGradient):
1705 material->setProperty(name: "colorStyle", value: 1);
1706 break;
1707 case (QGraphsTheme::ColorStyle::Uniform):
1708 material->setProperty(name: "colorStyle", value: 2);
1709 material->setProperty(name: "uniformColor", value: model->series->baseColor());
1710 break;
1711 }
1712
1713 bool flatShading = model->series->shading() == QSurface3DSeries::Shading::Flat;
1714
1715 QVariant textureInputAsVariant = material->property(name: "custex");
1716 QQuick3DShaderUtilsTextureInput *textureInput
1717 = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1718 auto textureData = static_cast<QQuickGraphsTextureData *>(model->texture->textureData());
1719 textureData->createGradient(gradient: model->series->baseGradient());
1720 textureInput->setTexture(model->texture);
1721
1722 QVariant heightInputAsVariant = material->property(name: "height");
1723 QQuick3DShaderUtilsTextureInput *heightInput
1724 = heightInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1725 heightInput->setTexture(model->heightTexture);
1726 material->setParent(model->model);
1727 material->setParentItem(model->model);
1728 material->setCullMode(QQuick3DMaterial::NoCulling);
1729 material->setProperty(name: "flatShading", value: flatShading);
1730 }
1731
1732 if (textured) {
1733 material->setProperty(name: "colorStyle", value: 3);
1734 QQuick3DShaderUtilsTextureInput *texInput = material->property(name: "baseColor")
1735 .value<QQuick3DShaderUtilsTextureInput *>();
1736 if (!texInput->texture()) {
1737 QQuick3DTexture *texture = new QQuick3DTexture();
1738 texture->setParent(material);
1739 texture->setParentItem(material);
1740 texInput->setTexture(texture);
1741 }
1742 if (!model->series->textureFile().isEmpty()) {
1743 texInput->texture()->setSource(QUrl::fromLocalFile(localfile: model->series->textureFile()));
1744 } else if (!model->series->texture().isNull()) {
1745 QImage image = model->series->texture();
1746 image.convertTo(f: QImage::Format_RGBA32FPx4);
1747 auto textureData = static_cast<QQuickGraphsTextureData *>(model->texture->textureData());
1748 textureData->setFormat(QQuick3DTextureData::RGBA32F);
1749 textureData->setSize(image.size());
1750 textureData->setTextureData(
1751 QByteArray(reinterpret_cast<const char *>(image.bits()), image.sizeInBytes()));
1752 texInput->texture()->setTextureData(textureData);
1753 texInput->texture()->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1754 texInput->texture()->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1755 } else {
1756 texInput->texture()->setSource(QUrl());
1757 }
1758 }
1759 material->update();
1760}
1761
1762QVector3D QQuickGraphsSurface::getNormalizedVertex(const QSurfaceDataItem &data,
1763 bool polar,
1764 bool flipXZ)
1765{
1766 Q_UNUSED(flipXZ);
1767
1768 QValue3DAxis *axisXValue = static_cast<QValue3DAxis *>(axisX());
1769 QValue3DAxis *axisYValue = static_cast<QValue3DAxis *>(axisY());
1770 QValue3DAxis *axisZValue = static_cast<QValue3DAxis *>(axisZ());
1771
1772 float normalizedX = axisXValue->positionAt(x: data.x());
1773 float normalizedY;
1774 float normalizedZ = axisZValue->positionAt(x: data.z());
1775 // TODO : Need to handle, flipXZ
1776
1777 float scale, translate;
1778 if (polar) {
1779 float angle = normalizedX * M_PI * 2.0f;
1780 float radius = normalizedZ * this->scaleWithBackground().z();
1781 normalizedX = radius * qSin(v: angle) * 1.0f;
1782 normalizedZ = -(radius * qCos(v: angle)) * 1.0f;
1783 } else {
1784 scale = translate = this->scaleWithBackground().x();
1785 normalizedX = normalizedX * scale * 2.0f - translate;
1786 scale = translate = this->scaleWithBackground().z();
1787 normalizedZ = normalizedZ * -scale * 2.0f + translate;
1788 }
1789 scale = translate = this->scale().y();
1790 normalizedY = axisYValue->positionAt(x: data.y()) * scale * 2.0f - translate;
1791 return QVector3D(normalizedX, normalizedY, normalizedZ);
1792}
1793
1794void QQuickGraphsSurface::toggleSliceGraph()
1795{
1796 if (m_selectionDirty)
1797 QQuickGraphsItem::toggleSliceGraph();
1798
1799 setSelectedPointChanged(true);
1800
1801 if (!sliceView()->isVisible())
1802 return;
1803
1804 QPointF worldCoord;
1805 for (auto model : m_model) {
1806 if (model->picked) {
1807 QPoint coords = model->selectedVertex.coord;
1808 worldCoord = mapCoordsToWorldSpace(model, coords);
1809 }
1810 }
1811
1812 for (auto model : m_model) {
1813 bool visible = model->series->isVisible();
1814
1815 model->sliceModel->setVisible(visible);
1816 model->sliceGridModel->setVisible(visible);
1817
1818 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked) {
1819 model->sliceModel->setVisible(false);
1820 model->sliceGridModel->setVisible(false);
1821 continue;
1822 } else {
1823 model->sliceGridModel->setVisible(
1824 model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawWireframe));
1825 if (model->series->drawMode().testFlag(flag: QSurface3DSeries::DrawSurface))
1826 model->sliceModel->setLocalOpacity(1.f);
1827 else
1828 model->sliceModel->setLocalOpacity(.0f);
1829 }
1830
1831 QVector<SurfaceVertex> selectedSeries;
1832
1833 QRect sampleSpace = model->sampleSpace;
1834 int rowStart = sampleSpace.top();
1835 int columnStart = sampleSpace.left();
1836 int rowEnd = sampleSpace.bottom() + 1;
1837 int columnEnd = sampleSpace.right() + 1;
1838 int rowCount = sampleSpace.height();
1839 int columnCount = sampleSpace.width();
1840
1841 QPoint coord;
1842 if (model->picked)
1843 coord = model->selectedVertex.coord;
1844 else
1845 coord = mapCoordsToSampleSpace(model, coords: worldCoord);
1846
1847 int indexCount = 0;
1848 const QSurfaceDataArray &array = model->series->dataArray();
1849 const qsizetype maxRow = array.size() - 1;
1850 const qsizetype maxColumn = array.at(i: 0).size() - 1;
1851 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxColumn).x();
1852 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1853 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row) && coord.y() != -1) {
1854 selectedSeries.reserve(asize: columnCount * 2);
1855 QVector<SurfaceVertex> list;
1856 QSurfaceDataRow row = array.at(i: coord.y());
1857 for (int i = columnStart; i < columnEnd; i++) {
1858 int index = ascendingX ? i : columnEnd - i + columnStart - 1;
1859 QVector3D pos = getNormalizedVertex(data: row.at(i: index), polar: false, flipXZ: false);
1860 SurfaceVertex vertex;
1861 vertex.position = pos;
1862 vertex.position.setY(vertex.position.y() - .025f);
1863 vertex.position.setZ(.0f);
1864 selectedSeries.append(t: vertex);
1865 vertex.position.setY(vertex.position.y() + .05f);
1866 list.append(t: vertex);
1867 }
1868 selectedSeries.append(l: list);
1869 indexCount = columnCount - 1;
1870 }
1871
1872 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column) && coord.x() != -1) {
1873 selectedSeries.reserve(asize: rowCount * 2);
1874 QVector<SurfaceVertex> list;
1875 for (int i = rowStart; i < rowEnd; i++) {
1876 int index = ascendingZ ? i : rowEnd - i + rowStart - 1;
1877 QVector3D pos = getNormalizedVertex(data: array.at(i: index).at(i: coord.x()), polar: false, flipXZ: false);
1878 SurfaceVertex vertex;
1879 vertex.position = pos;
1880 vertex.position.setX(-vertex.position.z());
1881 vertex.position.setY(vertex.position.y() - .025f);
1882 vertex.position.setZ(0);
1883 selectedSeries.append(t: vertex);
1884 vertex.position.setY(vertex.position.y() + .05f);
1885 list.append(t: vertex);
1886 }
1887 selectedSeries.append(l: list);
1888 indexCount = rowCount - 1;
1889
1890 QQmlListReference materialRef(model->sliceModel, "materials");
1891 auto material = materialRef.at(0);
1892 material->setProperty(name: "isColumn", value: true);
1893 }
1894
1895 QVector<quint32> indices;
1896 indices.reserve(asize: indexCount * 6);
1897 for (int i = 0; i < indexCount; i++) {
1898 indices.push_back(t: i + 1);
1899 indices.push_back(t: i + indexCount + 1);
1900 indices.push_back(t: i);
1901 indices.push_back(t: i + indexCount + 2);
1902 indices.push_back(t: i + indexCount + 1);
1903 indices.push_back(t: i + 1);
1904 }
1905
1906 auto geometry = model->sliceModel->geometry();
1907 geometry->vertexData().clear();
1908 geometry->indexData().clear();
1909 QByteArray vertexBuffer(reinterpret_cast<char *>(selectedSeries.data()),
1910 selectedSeries.size() * sizeof(SurfaceVertex));
1911 geometry->setVertexData(vertexBuffer);
1912 QByteArray indexBuffer(reinterpret_cast<char *>(indices.data()),
1913 indices.size() * sizeof(quint32));
1914 geometry->setIndexData(indexBuffer);
1915 geometry->update();
1916
1917 geometry = model->sliceGridModel->geometry();
1918 geometry->vertexData().clear();
1919 geometry->indexData().clear();
1920 geometry->setVertexData(vertexBuffer);
1921
1922 QVector<quint32> gridIndices;
1923 gridIndices.reserve(asize: indexCount * 4);
1924 for (int i = 0; i < indexCount; i++) {
1925 gridIndices.push_back(t: i);
1926 gridIndices.push_back(t: i + indexCount + 1);
1927
1928 gridIndices.push_back(t: i);
1929 gridIndices.push_back(t: i + 1);
1930 }
1931 geometry->indexData().clear();
1932 QByteArray gridIndexBuffer(reinterpret_cast<char *>(gridIndices.data()),
1933 gridIndices.size() * sizeof(quint32));
1934 geometry->setIndexData(gridIndexBuffer);
1935 geometry->update();
1936
1937 QQmlListReference gridMaterialRef(model->sliceGridModel, "materials");
1938 auto gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(gridMaterialRef.at(0));
1939 QColor gridColor = model->series->wireframeColor();
1940 gridMaterial->setBaseColor(gridColor);
1941
1942 updateSelectedPoint();
1943 }
1944}
1945
1946QPointF QQuickGraphsSurface::mapCoordsToWorldSpace(SurfaceModel *model, QPointF coords)
1947{
1948 const QSurfaceDataArray &array = model->series->dataArray();
1949 QSurfaceDataItem item = array.at(i: coords.y()).at(i: coords.x());
1950 return QPointF(item.x(), item.z());
1951}
1952
1953QPoint QQuickGraphsSurface::mapCoordsToSampleSpace(SurfaceModel *model, QPointF coords)
1954{
1955 const QSurfaceDataArray &array = model->series->dataArray();
1956 qsizetype maxRow = array.size() - 1;
1957 qsizetype maxCol = array.at(i: 0).size() - 1;
1958 const bool ascendingX = array.at(i: 0).at(i: 0).x() < array.at(i: 0).at(i: maxCol).x();
1959 const bool ascendingZ = array.at(i: 0).at(i: 0).z() < array.at(i: maxRow).at(i: 0).z();
1960 qsizetype botX = ascendingX ? 0 : maxCol;
1961 qsizetype botZ = ascendingZ ? 0 : maxRow;
1962 qsizetype topX = ascendingX ? maxCol : 0;
1963 qsizetype topZ = ascendingZ ? maxRow : 0;
1964
1965 QPoint point(-1, -1);
1966
1967 QSurfaceDataItem bottomLeft = array.at(i: botZ).at(i: botX);
1968 QSurfaceDataItem topRight = array.at(i: topZ).at(i: topX);
1969
1970 QPointF pointBL(bottomLeft.x(), bottomLeft.z());
1971 QPointF pointTR(topRight.x(), topRight.z());
1972
1973 QPointF pointF = coords - pointBL;
1974 QPointF span = pointTR - pointBL;
1975 QPointF step = QPointF(span.x() / float(maxCol), span.y() / float(maxRow));
1976 QPoint sample = QPoint((pointF.x() + (step.x() / 2.0)) / step.x(),
1977 (pointF.y() + (step.y() / 2.0)) / step.y());
1978
1979 if (bottomLeft.x() <= coords.x() && topRight.x() >= coords.x())
1980 point.setX(ascendingX ? sample.x() : int(maxCol) - sample.x());
1981
1982 if (bottomLeft.z() <= coords.y() && topRight.z() >= coords.y())
1983 point.setY(ascendingZ ? sample.y() : int(maxRow) - sample.y());
1984 return point;
1985}
1986
1987void QQuickGraphsSurface::createIndices(SurfaceModel *model, qsizetype columnCount, qsizetype rowCount)
1988{
1989 qsizetype endX = columnCount - 1;
1990 qsizetype endY = rowCount - 1;
1991
1992 qsizetype indexCount = 6 * endX * endY;
1993 QVector<quint32> *indices = &model->indices;
1994
1995 indices->clear();
1996 indices->resize(size: indexCount);
1997
1998 qsizetype rowEnd = endY * columnCount;
1999 for (qsizetype row = 0; row < rowEnd; row += columnCount) {
2000 for (qsizetype j = 0; j < endX; j++) {
2001 indices->push_back(t: int(row + j + 1));
2002 indices->push_back(t: int(row + columnCount + j));
2003 indices->push_back(t: int(row + j));
2004
2005 indices->push_back(t: int(row + columnCount + j + 1));
2006 indices->push_back(t: int(row + columnCount + j));
2007 indices->push_back(t: int(row + j + 1));
2008 }
2009 }
2010}
2011void QQuickGraphsSurface::createGridlineIndices(SurfaceModel *model, qsizetype x, qsizetype y, qsizetype endX, qsizetype endY)
2012{
2013 qsizetype columnCount = model->columnCount;
2014 qsizetype rowCount = model->rowCount;
2015
2016 if (endX >= columnCount)
2017 endX = columnCount - 1;
2018 if (endY >= rowCount)
2019 endY = rowCount - 1;
2020 if (x > endX)
2021 x = endX - 1;
2022 if (y > endY)
2023 y = endY - 1;
2024
2025 qsizetype nColumns = endX - x + 1;
2026 qsizetype nRows = endY - y + 1;
2027
2028 qsizetype gridIndexCount = 2 * nColumns * (nRows - 1) + 2 * nRows * (nColumns - 1);
2029 model->gridIndices.clear();
2030 model->gridIndices.resize(size: gridIndexCount);
2031
2032 for (qsizetype i = y, row = columnCount * y; i <= endY; i++, row += columnCount) {
2033 for (qsizetype j = x; j < endX; j++) {
2034 model->gridIndices.push_back(t: int(row + j));
2035 model->gridIndices.push_back(t: int(row + j + 1));
2036 }
2037 }
2038 for (qsizetype i = y, row = columnCount * y; i < endY; i++, row += columnCount) {
2039 for (qsizetype j = x; j <= endX; j++) {
2040 model->gridIndices.push_back(t: int(row + j));
2041 model->gridIndices.push_back(t: int(row + j + columnCount));
2042 }
2043 }
2044}
2045
2046bool QQuickGraphsSurface::doPicking(QPointF position)
2047{
2048 if (!m_pickThisFrame && m_proxyDirty) {
2049 m_pickThisFrame = true;
2050 m_lastPick = position;
2051 for (auto model : m_model)
2052 updateProxyModel(model);
2053 return false;
2054 }
2055 if (!QQuickGraphsItem::doPicking(point: position))
2056 return false;
2057
2058 m_selectionDirty = true;
2059 auto pickResult = pickAll(x: position.x(), y: position.y());
2060 QVector3D pickedPos(0.0f, 0.0f, 0.0f);
2061 QQuick3DModel *pickedModel = nullptr;
2062
2063 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
2064 if (!sliceView() && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice))
2065 createSliceView();
2066
2067 if (!pickResult.isEmpty()) {
2068 for (auto picked : pickResult) {
2069 bool inBounds = qAbs(t: picked.position().y()) < scaleWithBackground().y();
2070 if (inBounds && picked.objectHit()
2071 && picked.objectHit()->objectName().contains(QStringLiteral("ProxyModel"))) {
2072 pickedPos = picked.position();
2073 pickedModel = qobject_cast<QQuick3DModel *>(object: picked.objectHit()->parentItem());
2074 bool visible = false;
2075 for (auto model : m_model) {
2076 if (model->model == pickedModel)
2077 visible = model->series->isVisible();
2078 }
2079 if (!pickedPos.isNull() && visible)
2080 break;
2081 } else {
2082 clearSelection();
2083 for (auto model : m_model)
2084 model->picked = false;
2085 }
2086 }
2087
2088 bool inRange = qAbs(t: pickedPos.x()) < scaleWithBackground().x()
2089 && qAbs(t: pickedPos.z()) < scaleWithBackground().z();
2090
2091 if (!pickedPos.isNull() && inRange) {
2092 float min = -1.0f;
2093
2094 for (auto model : m_model) {
2095 if (!model->series->isVisible()) {
2096 model->picked = false;
2097 continue;
2098 }
2099
2100 model->picked = (model->model == pickedModel);
2101
2102 SurfaceVertex selectedVertex;
2103 for (auto vertex : model->vertices) {
2104 QVector3D pos = vertex.position;
2105 float dist = pickedPos.distanceToPoint(point: pos);
2106 if (selectedVertex.position.isNull() || dist < min) {
2107 min = dist;
2108 selectedVertex = vertex;
2109 }
2110 }
2111 model->selectedVertex = selectedVertex;
2112 if (!selectedVertex.position.isNull() && model->picked) {
2113 model->series->setSelectedPoint(selectedVertex.coord);
2114 setSlicingActive(false);
2115 if (isSliceEnabled())
2116 setSliceActivatedChanged(true);
2117 }
2118 }
2119 }
2120 } else {
2121 clearSelection();
2122 for (auto model : m_model)
2123 model->picked = false;
2124 }
2125 }
2126 return true;
2127}
2128
2129void QQuickGraphsSurface::updateSelectedPoint()
2130{
2131 bool labelVisible = false;
2132
2133 auto list = surfaceSeriesList();
2134 for (auto series : list) {
2135 // If the pointer and its instancing do not exist yet (as will happen in widget case),
2136 // we must create them
2137 if (!m_selectionPointers.value(key: series))
2138 changePointerMeshTypeForSeries(mesh: series->mesh(), series);
2139 m_selectionPointers.value(key: series)->setVisible(false);
2140 if (sliceView() && sliceView()->isVisible())
2141 m_sliceSelectionPointers.value(key: series)->setVisible(false);
2142 }
2143
2144 QPointF worldCoord;
2145 for (auto model : m_model) {
2146 if (model->picked) {
2147 QPoint coords = model->selectedVertex.coord;
2148 worldCoord = mapCoordsToWorldSpace(model, coords);
2149 }
2150 }
2151 for (auto model : m_model) {
2152 if ((!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && !model->picked)
2153 || model->selectedVertex.position.isNull()) {
2154 continue;
2155 }
2156 QPoint selectedCoord;
2157 if (model->picked)
2158 selectedCoord = model->selectedVertex.coord;
2159 else
2160 selectedCoord = mapCoordsToSampleSpace(model, coords: worldCoord);
2161 if (selectedCoord.x() == -1 || selectedCoord.y() == -1)
2162 continue;
2163
2164 const QSurfaceDataItem &dataPos
2165 = model->series->dataArray().at(i: selectedCoord.y()).at(i: selectedCoord.x());
2166 QVector3D pos = getNormalizedVertex(data: dataPos, polar: isPolar(), flipXZ: false);
2167
2168 SurfaceVertex selectedVertex;
2169 selectedVertex.position = pos;
2170 selectedVertex.coord = model->selectedVertex.coord;
2171 if (model->series->isVisible() && !selectedVertex.position.isNull()
2172 && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item)) {
2173 m_selectionPointers.value(key: model->series)->setPosition(selectedVertex.position);
2174 m_selectionPointers.value(key: model->series)->setVisible(true);
2175 QVector3D slicePosition = getNormalizedVertex(data: dataPos, polar: false, flipXZ: false);
2176 if (sliceView() && sliceView()->isVisible()) {
2177 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2178 slicePosition.setX(-slicePosition.z());
2179 slicePosition.setZ(.0f);
2180 m_sliceSelectionPointers.value(key: model->series)->setPosition(slicePosition);
2181 m_sliceSelectionPointers.value(key: model->series)->setVisible(true);
2182 }
2183 if (model->picked) {
2184 QVector3D labelPosition = selectedVertex.position;
2185 QString label = model->series->itemLabel();
2186 setSelectedPoint(position: selectedVertex.coord, series: model->series, enterSlice: false);
2187
2188 updateItemLabel(position: labelPosition);
2189 itemLabel()->setProperty(name: "labelText", value: label);
2190 labelVisible = model->series->isItemLabelVisible();
2191 if (sliceView() && sliceView()->isVisible())
2192 updateSliceItemLabel(label, position: slicePosition);
2193 }
2194 }
2195 }
2196 setItemSelected(m_selectedSeries != nullptr);
2197 itemLabel()->setVisible(labelVisible);
2198 if (sliceView() && sliceView()->isVisible())
2199 sliceItemLabel()->setVisible(labelVisible);
2200}
2201
2202void QQuickGraphsSurface::addModel(QSurface3DSeries *series)
2203{
2204 auto parent = graphNode();
2205 bool visible = series->isVisible();
2206
2207 auto model = new QQuick3DModel();
2208 model->setParent(parent);
2209 model->setParentItem(parent);
2210 model->setObjectName(QStringLiteral("SurfaceModel"));
2211 model->setVisible(visible);
2212 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
2213 model->setPickable(false);
2214 else
2215 model->setPickable(true);
2216
2217 auto geometry = new QQuick3DGeometry();
2218 geometry->setParent(model);
2219 geometry->setStride(sizeof(SurfaceVertex));
2220 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2221 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2222 offset: 0,
2223 componentType: QQuick3DGeometry::Attribute::F32Type);
2224 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2225 offset: sizeof(QVector3D),
2226 componentType: QQuick3DGeometry::Attribute::F32Type);
2227 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2228 offset: 0,
2229 componentType: QQuick3DGeometry::Attribute::U32Type);
2230 model->setGeometry(geometry);
2231
2232 model->setCastsShadows(false); //Disable shadows as they render incorrectly
2233
2234 QQuick3DTexture *texture = new QQuick3DTexture();
2235 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
2236 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
2237 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
2238 textureData->setParent(texture);
2239 textureData->setParentItem(texture);
2240 texture->setTextureData(textureData);
2241
2242 QQmlListReference materialRef(model, "materials");
2243
2244 QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
2245 QStringLiteral(":/materials/SurfaceMaterial"));
2246
2247 customMaterial->setParent(model);
2248 customMaterial->setParentItem(model);
2249 customMaterial->setCullMode(QQuick3DMaterial::NoCulling);
2250 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
2251 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
2252 .value<QQuick3DShaderUtilsTextureInput *>();
2253 textureInput->setTexture(texture);
2254
2255 texture->setParent(customMaterial);
2256
2257 materialRef.append(customMaterial);
2258
2259 auto gridModel = new QQuick3DModel();
2260 gridModel->setParent(parent);
2261 gridModel->setParentItem(parent);
2262 gridModel->setObjectName(QStringLiteral("SurfaceModel"));
2263 gridModel->setVisible(visible);
2264 gridModel->setDepthBias(1.0f);
2265 auto gridGeometry = new QQuick3DGeometry();
2266 gridGeometry->setParent(this);
2267 gridGeometry->setStride(sizeof(SurfaceVertex));
2268 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2269 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2270 offset: 0,
2271 componentType: QQuick3DGeometry::Attribute::F32Type);
2272 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2273 offset: sizeof(QVector3D),
2274 componentType: QQuick3DGeometry::Attribute::F32Type);
2275 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2276 offset: 0,
2277 componentType: QQuick3DGeometry::Attribute::U32Type);
2278 gridModel->setGeometry(gridGeometry);
2279 QQmlListReference gridMaterialRef(gridModel, "materials");
2280 auto gridMaterial = createQmlCustomMaterial(QStringLiteral(":/materials/GridSurfaceMaterial"));
2281 gridMaterial->setParent(gridModel);
2282 gridMaterial->setParentItem(gridModel);
2283 gridMaterialRef.append(gridMaterial);
2284
2285 SurfaceModel *surfaceModel = new SurfaceModel();
2286 surfaceModel->model = model;
2287 surfaceModel->gridModel = gridModel;
2288 surfaceModel->series = series;
2289 surfaceModel->texture = texture;
2290 surfaceModel->customMaterial = customMaterial;
2291
2292 m_model.push_back(t: surfaceModel);
2293
2294 connect(sender: series,
2295 signal: &QSurface3DSeries::shadingChanged,
2296 context: this,
2297 slot: &QQuickGraphsSurface::handleShadingChanged);
2298 connect(sender: series,
2299 signal: &QSurface3DSeries::wireframeColorChanged,
2300 context: this,
2301 slot: &QQuickGraphsSurface::handleWireframeColorChanged);
2302 connect(sender: series,
2303 signal: &QSurface3DSeries::userDefinedMeshChanged,
2304 context: this,
2305 slot: &QQuickGraphsSurface::handlePointerChanged);
2306 connect(sender: series,
2307 signal: &QSurface3DSeries::meshChanged,
2308 context: this,
2309 slot: &QQuickGraphsSurface::handleMeshTypeChanged);
2310 if (sliceView())
2311 addSliceModel(model: surfaceModel);
2312}
2313
2314void QQuickGraphsSurface::createSliceView()
2315{
2316 setSliceOrthoProjection(true);
2317 QQuickGraphsItem::createSliceView();
2318
2319 for (auto surfaceModel : m_model) {
2320 addSliceModel(model: surfaceModel);
2321 changeSlicePointerMeshTypeForSeries(mesh: surfaceModel->series->mesh(), series: surfaceModel->series);
2322 }
2323}
2324
2325void QQuickGraphsSurface::updateSliceItemLabel(const QString &label, QVector3D position)
2326{
2327 QQuickGraphsItem::updateSliceItemLabel(label, position);
2328
2329 QFontMetrics fm(theme()->labelFont());
2330 float textPadding = 12.0f;
2331 float labelHeight = fm.height() + textPadding;
2332 float labelWidth = fm.horizontalAdvance(label) + textPadding;
2333 sliceItemLabel()->setProperty(name: "labelWidth", value: labelWidth);
2334 sliceItemLabel()->setProperty(name: "labelHeight", value: labelHeight);
2335 QVector3D labelPosition = position;
2336 labelPosition.setZ(.1f);
2337 labelPosition.setY(position.y() + .05f);
2338 sliceItemLabel()->setPosition(labelPosition);
2339 sliceItemLabel()->setProperty(name: "labelText", value: label);
2340}
2341
2342void QQuickGraphsSurface::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
2343{
2344 checkSliceEnabled();
2345 bool validSlice = mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
2346 && m_selectedPoint != invalidSelectionPosition();
2347 if (sliceView() && sliceView()->isVisible()) {
2348 if (validSlice) {
2349 toggleSliceGraph();
2350 } else {
2351 m_selectionDirty = true;
2352 setSliceActivatedChanged(true);
2353 }
2354 } else if (validSlice) {
2355 m_selectionDirty = true;
2356 setSliceActivatedChanged(true);
2357 }
2358
2359 setSeriesVisualsDirty(true);
2360 itemLabel()->setVisible(false);
2361 if (sliceView() && sliceView()->isVisible())
2362 sliceItemLabel()->setVisible(false);
2363}
2364
2365void QQuickGraphsSurface::addSliceModel(SurfaceModel *model)
2366{
2367 QQuick3DViewport *sliceParent = sliceView();
2368
2369 auto surfaceModel = new QQuick3DModel();
2370 surfaceModel->setParent(sliceParent->scene());
2371 surfaceModel->setParentItem(sliceParent->scene());
2372 surfaceModel->setVisible(model->series->isVisible());
2373
2374 auto geometry = new QQuick3DGeometry();
2375 geometry->setParent(surfaceModel);
2376 geometry->setParentItem(surfaceModel);
2377 geometry->setStride(sizeof(SurfaceVertex));
2378 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
2379 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2380 offset: 0,
2381 componentType: QQuick3DGeometry::Attribute::F32Type);
2382 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
2383 offset: sizeof(QVector3D),
2384 componentType: QQuick3DGeometry::Attribute::F32Type);
2385 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2386 offset: 0,
2387 componentType: QQuick3DGeometry::Attribute::U32Type);
2388 surfaceModel->setGeometry(geometry);
2389
2390 QQmlListReference materialRef(surfaceModel, "materials");
2391 auto material = createQmlCustomMaterial(QStringLiteral(":/materials/SurfaceSliceMaterial"));
2392 material->setCullMode(QQuick3DMaterial::NoCulling);
2393 QVariant textureInputAsVariant = material->property(name: "custex");
2394 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
2395 .value<QQuick3DShaderUtilsTextureInput *>();
2396 QQuick3DTexture *texture = model->texture;
2397 textureInput->setTexture(texture);
2398 materialRef.append(material);
2399
2400 model->sliceModel = surfaceModel;
2401
2402 QQuick3DModel *gridModel = new QQuick3DModel();
2403 gridModel->setParent(sliceParent->scene());
2404 gridModel->setParentItem(sliceParent->scene());
2405 gridModel->setVisible(model->series->isVisible());
2406 gridModel->setDepthBias(1.0f);
2407 QQuick3DGeometry *gridGeometry = new QQuick3DGeometry();
2408 gridGeometry->setParent(gridModel);
2409 gridGeometry->setStride(sizeof(SurfaceVertex));
2410 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
2411 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
2412 offset: 0,
2413 componentType: QQuick3DGeometry::Attribute::F32Type);
2414 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::IndexSemantic,
2415 offset: 0,
2416 componentType: QQuick3DGeometry::Attribute::U32Type);
2417 gridModel->setGeometry(gridGeometry);
2418 QQmlListReference gridMaterialRef(gridModel, "materials");
2419 QQuick3DPrincipledMaterial *gridMaterial = new QQuick3DPrincipledMaterial();
2420 gridMaterial->setParent(gridModel);
2421 gridMaterial->setLighting(QQuick3DPrincipledMaterial::NoLighting);
2422 gridMaterial->setParent(gridModel);
2423 gridMaterialRef.append(gridMaterial);
2424
2425 model->sliceGridModel = gridModel;
2426}
2427
2428void QQuickGraphsSurface::updateSingleHighlightColor()
2429{
2430 auto list = surfaceSeriesList();
2431 for (auto series : list) {
2432 QQmlListReference pMaterialRef(m_selectionPointers.value(key: series), "materials");
2433 auto pmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: pMaterialRef.at(0));
2434 if (pmat)
2435 pmat->setBaseColor(theme()->singleHighlightColor());
2436 if (sliceView()) {
2437 QQmlListReference spMaterialRef(m_sliceSelectionPointers.value(key: series), "materials");
2438 auto spmat = qobject_cast<QQuick3DPrincipledMaterial *>(object: spMaterialRef.at(0));
2439 spmat->setBaseColor(theme()->singleHighlightColor());
2440 }
2441 }
2442}
2443
2444void QQuickGraphsSurface::updateLightStrength()
2445{
2446 for (auto model : m_model) {
2447 QQmlListReference materialRef(model->model, "materials");
2448 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialRef.at(0));
2449 material->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
2450 }
2451}
2452
2453void QQuickGraphsSurface::handleThemeTypeChange()
2454{
2455 for (auto model : m_model)
2456 updateMaterial(model);
2457}
2458
2459QT_END_NAMESPACE
2460

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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