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 | |
21 | QT_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 | |
151 | QQuickGraphsSurface::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 | |
162 | QQuickGraphsSurface::~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 | |
170 | void QQuickGraphsSurface::setAxisX(QValue3DAxis *axis) |
171 | { |
172 | QQuickGraphsItem::setAxisX(axis); |
173 | } |
174 | |
175 | QValue3DAxis *QQuickGraphsSurface::axisX() const |
176 | { |
177 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX()); |
178 | } |
179 | |
180 | void QQuickGraphsSurface::setAxisY(QValue3DAxis *axis) |
181 | { |
182 | QQuickGraphsItem::setAxisY(axis); |
183 | } |
184 | |
185 | QValue3DAxis *QQuickGraphsSurface::axisY() const |
186 | { |
187 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY()); |
188 | } |
189 | |
190 | void QQuickGraphsSurface::setAxisZ(QValue3DAxis *axis) |
191 | { |
192 | QQuickGraphsItem::setAxisZ(axis); |
193 | } |
194 | |
195 | QValue3DAxis *QQuickGraphsSurface::axisZ() const |
196 | { |
197 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ()); |
198 | } |
199 | |
200 | void 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 | |
211 | void 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 | |
227 | void 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 | |
235 | void QQuickGraphsSurface::changePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh, |
236 | QSurface3DSeries *series) |
237 | { |
238 | changePointerForSeries(filename: getMeshFileName(mesh, series), series); |
239 | } |
240 | |
241 | void QQuickGraphsSurface::changeSlicePointerMeshTypeForSeries(QAbstract3DSeries::Mesh mesh, |
242 | QSurface3DSeries *series) |
243 | { |
244 | changeSlicePointerForSeries(filename: getMeshFileName(mesh, series), series); |
245 | } |
246 | |
247 | QString 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 | |
281 | void 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 | |
288 | void 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 | |
323 | void 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 | |
360 | void 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 | |
393 | void 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 | |
511 | void 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 | |
532 | void 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 | |
546 | void 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 | |
581 | void 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 | |
609 | void 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 | |
623 | void 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 | |
647 | void 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 | |
675 | QPoint QQuickGraphsSurface::invalidSelectionPosition() |
676 | { |
677 | static QPoint invalidSelectionPoint(-1, -1); |
678 | return invalidSelectionPoint; |
679 | } |
680 | |
681 | void 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 | |
752 | void 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 | |
785 | void QQuickGraphsSurface::handleAxisAutoAdjustRangeChangedInOrientation( |
786 | QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) |
787 | { |
788 | Q_UNUSED(orientation); |
789 | Q_UNUSED(autoAdjust); |
790 | |
791 | adjustAxisRanges(); |
792 | } |
793 | |
794 | void 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 | |
802 | void 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 | |
812 | void 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 | |
822 | bool QQuickGraphsSurface::flipHorizontalGrid() const |
823 | { |
824 | return m_flipHorizontalGrid; |
825 | } |
826 | |
827 | bool QQuickGraphsSurface::isFlatShadingSupported() |
828 | { |
829 | return m_flatShadingSupported; |
830 | } |
831 | |
832 | QList<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 | |
844 | void 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 | |
854 | QQmlListProperty<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 | |
864 | void QQuickGraphsSurface::appendSeriesFunc(QQmlListProperty<QSurface3DSeries> *list, |
865 | QSurface3DSeries *series) |
866 | { |
867 | reinterpret_cast<QQuickGraphsSurface *>(list->data)->addSeries(series); |
868 | } |
869 | |
870 | qsizetype QQuickGraphsSurface::countSeriesFunc(QQmlListProperty<QSurface3DSeries> *list) |
871 | { |
872 | return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().size(); |
873 | } |
874 | |
875 | QSurface3DSeries *QQuickGraphsSurface::atSeriesFunc(QQmlListProperty<QSurface3DSeries> *list, |
876 | qsizetype index) |
877 | { |
878 | return reinterpret_cast<QQuickGraphsSurface *>(list->data)->surfaceSeriesList().at(i: index); |
879 | } |
880 | |
881 | void 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 | |
890 | void 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 | |
907 | void 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 | |
938 | void QQuickGraphsSurface::clearSelection() |
939 | { |
940 | setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false); |
941 | } |
942 | |
943 | void QQuickGraphsSurface::handleAxisXChanged(QAbstract3DAxis *axis) |
944 | { |
945 | emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis)); |
946 | } |
947 | |
948 | void QQuickGraphsSurface::handleAxisYChanged(QAbstract3DAxis *axis) |
949 | { |
950 | emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis)); |
951 | } |
952 | |
953 | void QQuickGraphsSurface::handleAxisZChanged(QAbstract3DAxis *axis) |
954 | { |
955 | emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis)); |
956 | } |
957 | |
958 | void 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 | |
970 | void 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 | |
1045 | void 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 | |
1121 | void 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 | |
1174 | void 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 | |
1186 | inline 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 | |
1194 | inline 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 | |
1248 | QRect 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 | |
1317 | void 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 | |
1516 | void 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 | |
1642 | void 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 | |
1680 | void 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 | |
1762 | QVector3D 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 | |
1794 | void 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 | |
1946 | QPointF 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 | |
1953 | QPoint 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 | |
1987 | void 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 | } |
2011 | void 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 | |
2046 | bool 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 | |
2129 | void 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 | |
2202 | void 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 | |
2314 | void 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 | |
2325 | void 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 | |
2342 | void 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 | |
2365 | void 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 | |
2428 | void 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 | |
2444 | void 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 | |
2453 | void QQuickGraphsSurface::handleThemeTypeChange() |
2454 | { |
2455 | for (auto model : m_model) |
2456 | updateMaterial(model); |
2457 | } |
2458 | |
2459 | QT_END_NAMESPACE |
2460 |
Definitions
- QQuickGraphsSurface
- ~QQuickGraphsSurface
- setAxisX
- axisX
- setAxisY
- axisY
- setAxisZ
- axisZ
- handleShadingChanged
- handleWireframeColorChanged
- handleMeshTypeChanged
- changePointerMeshTypeForSeries
- changeSlicePointerMeshTypeForSeries
- getMeshFileName
- handlePointerChanged
- changePointerForSeries
- changeSlicePointerForSeries
- handleFlipHorizontalGridChanged
- adjustAxisRanges
- handleArrayReset
- handleFlatShadingSupportedChange
- handleRowsChanged
- handleItemChanged
- handleRowsAdded
- handleRowsInserted
- handleRowsRemoved
- invalidSelectionPosition
- setSelectedPoint
- setSelectionMode
- handleAxisAutoAdjustRangeChangedInOrientation
- handleAxisRangeChangedBySender
- handleSeriesVisibilityChangedBySender
- setFlipHorizontalGrid
- flipHorizontalGrid
- isFlatShadingSupported
- surfaceSeriesList
- updateSurfaceTexture
- seriesList
- appendSeriesFunc
- countSeriesFunc
- atSeriesFunc
- clearSeriesFunc
- addSeries
- removeSeries
- clearSelection
- handleAxisXChanged
- handleAxisYChanged
- handleAxisZChanged
- componentComplete
- synchData
- updateGraph
- calculateSceneScalingFactors
- handleChangedSeries
- getDataValue
- binarySearchArray
- calculateSampleSpace
- updateModel
- updateProxyModel
- createProxyModel
- updateMaterial
- getNormalizedVertex
- toggleSliceGraph
- mapCoordsToWorldSpace
- mapCoordsToSampleSpace
- createIndices
- createGridlineIndices
- doPicking
- updateSelectedPoint
- addModel
- createSliceView
- updateSliceItemLabel
- updateSelectionMode
- addSliceModel
- updateSingleHighlightColor
- updateLightStrength
Learn to use CMake with our Intro Training
Find out more