1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
5
6#include "surface3dcontroller_p.h"
7#include "surface3drenderer_p.h"
8#include "qvalue3daxis_p.h"
9#include "qsurfacedataproxy_p.h"
10#include "qsurface3dseries_p.h"
11#include <QtCore/QMutexLocker>
12
13QT_BEGIN_NAMESPACE
14
15Surface3DController::Surface3DController(QRect rect, Q3DScene *scene)
16 : Abstract3DController(rect, scene),
17 m_renderer(0),
18 m_selectedPoint(invalidSelectionPosition()),
19 m_selectedSeries(0),
20 m_flatShadingSupported(true),
21 m_flipHorizontalGrid(false)
22{
23 // Setting a null axis creates a new default axis according to orientation and graph type.
24 // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
25 // functions implemented by subclasses.
26 setAxisX(0);
27 setAxisY(0);
28 setAxisZ(0);
29}
30
31Surface3DController::~Surface3DController()
32{
33}
34
35void Surface3DController::initializeOpenGL()
36{
37 QMutexLocker mutexLocker(&m_renderMutex);
38
39 // Initialization is called multiple times when Qt Quick components are used
40 if (isInitialized())
41 return;
42
43 m_renderer = new Surface3DRenderer(this);
44 setRenderer(m_renderer);
45
46 emitNeedRender();
47}
48
49void Surface3DController::synchDataToRenderer()
50{
51 QMutexLocker mutexLocker(&m_renderMutex);
52
53 if (!isInitialized())
54 return;
55
56 Abstract3DController::synchDataToRenderer();
57
58 // Notify changes to renderer
59 if (m_changeTracker.rowsChanged) {
60 m_renderer->updateRows(rows: m_changedRows);
61 m_changeTracker.rowsChanged = false;
62 m_changedRows.clear();
63 }
64
65 if (m_changeTracker.itemChanged) {
66 m_renderer->updateItems(points: m_changedItems);
67 m_changeTracker.itemChanged = false;
68 m_changedItems.clear();
69 }
70
71 if (m_changeTracker.selectedPointChanged) {
72 m_renderer->updateSelectedPoint(position: m_selectedPoint, series: m_selectedSeries);
73 m_changeTracker.selectedPointChanged = false;
74 }
75
76 if (m_changeTracker.flipHorizontalGridChanged) {
77 m_renderer->updateFlipHorizontalGrid(flip: m_flipHorizontalGrid);
78 m_changeTracker.flipHorizontalGridChanged = false;
79 }
80
81 if (m_changeTracker.surfaceTextureChanged) {
82 m_renderer->updateSurfaceTextures(seriesList: m_changedTextures);
83 m_changeTracker.surfaceTextureChanged = false;
84 m_changedTextures.clear();
85 }
86}
87
88void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation(
89 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
90{
91 Q_UNUSED(orientation);
92 Q_UNUSED(autoAdjust);
93
94 adjustAxisRanges();
95}
96
97void Surface3DController::handleAxisRangeChangedBySender(QObject *sender)
98{
99 Abstract3DController::handleAxisRangeChangedBySender(sender);
100
101 // Update selected point - may be moved offscreen
102 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
103}
104
105void Surface3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
106{
107 Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
108
109 // Visibility changes may require disabling slicing,
110 // so just reset selection to ensure everything is still valid.
111 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
112}
113
114void Surface3DController::handlePendingClick()
115{
116 // This function is called while doing the sync, so it is okay to query from renderer
117 QPoint position = m_renderer->clickedPosition();
118 QSurface3DSeries *series = static_cast<QSurface3DSeries *>(m_renderer->clickedSeries());
119
120 setSelectedPoint(position, series, enterSlice: true);
121
122 Abstract3DController::handlePendingClick();
123
124 m_renderer->resetClickedStatus();
125}
126
127QPoint Surface3DController::invalidSelectionPosition()
128{
129 static QPoint invalidSelectionPoint(-1, -1);
130 return invalidSelectionPoint;
131}
132
133bool Surface3DController::isFlatShadingSupported()
134{
135 return m_flatShadingSupported;
136}
137
138void Surface3DController::addSeries(QAbstract3DSeries *series)
139{
140 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeSurface);
141
142 Abstract3DController::addSeries(series);
143
144 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
145 if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
146 setSelectedPoint(position: surfaceSeries->selectedPoint(), series: surfaceSeries, enterSlice: false);
147
148 if (!surfaceSeries->texture().isNull())
149 updateSurfaceTexture(series: surfaceSeries);
150}
151
152void Surface3DController::removeSeries(QAbstract3DSeries *series)
153{
154 bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
155
156 Abstract3DController::removeSeries(series);
157
158 if (m_selectedSeries == series)
159 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
160
161 if (wasVisible)
162 adjustAxisRanges();
163}
164
165QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList()
166{
167 QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
168 QList<QSurface3DSeries *> surfaceSeriesList;
169 foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
170 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
171 if (surfaceSeries)
172 surfaceSeriesList.append(t: surfaceSeries);
173 }
174
175 return surfaceSeriesList;
176}
177
178void Surface3DController::setFlipHorizontalGrid(bool flip)
179{
180 if (m_flipHorizontalGrid != flip) {
181 m_flipHorizontalGrid = flip;
182 m_changeTracker.flipHorizontalGridChanged = true;
183 emit flipHorizontalGridChanged(flip);
184 emitNeedRender();
185 }
186}
187
188bool Surface3DController::flipHorizontalGrid() const
189{
190 return m_flipHorizontalGrid;
191}
192
193void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
194{
195 // Currently surface only supports row and column modes when also slicing
196 if ((mode.testFlag(flag: QAbstract3DGraph::SelectionRow)
197 || mode.testFlag(flag: QAbstract3DGraph::SelectionColumn))
198 && !mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
199 qWarning(msg: "Unsupported selection mode.");
200 return;
201 } else if (mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
202 && (mode.testFlag(flag: QAbstract3DGraph::SelectionRow)
203 == mode.testFlag(flag: QAbstract3DGraph::SelectionColumn))) {
204 qWarning(msg: "Must specify one of either row or column selection mode in conjunction with slicing mode.");
205 } else {
206 QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
207
208 Abstract3DController::setSelectionMode(mode);
209
210 if (mode != oldMode) {
211 // Refresh selection upon mode change to ensure slicing is correctly updated
212 // according to series the visibility.
213 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: true);
214
215 // Special case: Always deactivate slicing when changing away from slice
216 // automanagement, as this can't be handled in setSelectedBar.
217 if (!mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
218 && oldMode.testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
219 scene()->setSlicingActive(false);
220 }
221 }
222 }
223}
224
225void Surface3DController::setSelectedPoint(const QPoint &position, QSurface3DSeries *series,
226 bool enterSlice)
227{
228 // If the selection targets non-existent point, clear selection instead.
229 QPoint pos = position;
230
231 // Series may already have been removed, so check it before setting the selection.
232 if (!m_seriesList.contains(t: series))
233 series = 0;
234
235 const QSurfaceDataProxy *proxy = 0;
236 if (series)
237 proxy = series->dataProxy();
238
239 if (!proxy)
240 pos = invalidSelectionPosition();
241
242 if (pos != invalidSelectionPosition()) {
243 int maxRow = proxy->rowCount() - 1;
244 int maxCol = proxy->columnCount() - 1;
245
246 if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
247 pos = invalidSelectionPosition();
248 }
249
250 if (selectionMode().testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
251 if (pos == invalidSelectionPosition() || !series->isVisible()) {
252 scene()->setSlicingActive(false);
253 } else {
254 // If the selected point is outside data window, or there is no selected point, disable slicing
255 float axisMinX = m_axisX->min();
256 float axisMaxX = m_axisX->max();
257 float axisMinZ = m_axisZ->min();
258 float axisMaxZ = m_axisZ->max();
259
260 QSurfaceDataItem item = proxy->array()->at(i: pos.x())->at(i: pos.y());
261 if (item.x() < axisMinX || item.x() > axisMaxX
262 || item.z() < axisMinZ || item.z() > axisMaxZ) {
263 scene()->setSlicingActive(false);
264 } else if (enterSlice) {
265 scene()->setSlicingActive(true);
266 }
267 }
268 emitNeedRender();
269 }
270
271 if (pos != m_selectedPoint || series != m_selectedSeries) {
272 bool seriesChanged = (series != m_selectedSeries);
273 m_selectedPoint = pos;
274 m_selectedSeries = series;
275 m_changeTracker.selectedPointChanged = true;
276
277 // Clear selection from other series and finally set new selection to the specified series
278 foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
279 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
280 if (surfaceSeries != m_selectedSeries)
281 surfaceSeries->dptr()->setSelectedPoint(invalidSelectionPosition());
282 }
283 if (m_selectedSeries)
284 m_selectedSeries->dptr()->setSelectedPoint(m_selectedPoint);
285
286 if (seriesChanged)
287 emit selectedSeriesChanged(series: m_selectedSeries);
288
289 emitNeedRender();
290 }
291}
292
293void Surface3DController::clearSelection()
294{
295 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
296}
297
298void Surface3DController::handleArrayReset()
299{
300 QSurface3DSeries *series;
301 if (qobject_cast<QSurfaceDataProxy *>(object: sender()))
302 series = static_cast<QSurfaceDataProxy *>(sender())->series();
303 else
304 series = static_cast<QSurface3DSeries *>(sender());
305
306 if (series->isVisible()) {
307 adjustAxisRanges();
308 m_isDataDirty = true;
309 }
310 if (!m_changedSeriesList.contains(t: series))
311 m_changedSeriesList.append(t: series);
312
313 // Clear selection unless still valid
314 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
315 series->d_ptr->markItemLabelDirty();
316 emitNeedRender();
317}
318
319void Surface3DController::handleFlatShadingSupportedChange(bool supported)
320{
321 // Handle renderer flat surface support indicator signal. This happens exactly once per renderer.
322 if (m_flatShadingSupported != supported) {
323 m_flatShadingSupported = supported;
324 // Emit the change for all added surfaces
325 foreach (QAbstract3DSeries *series, m_seriesList) {
326 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
327 emit surfaceSeries->flatShadingSupportedChanged(enable: m_flatShadingSupported);
328 }
329 }
330}
331
332void Surface3DController::handleRowsChanged(int startIndex, int count)
333{
334 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
335 int oldChangeCount = m_changedRows.size();
336 if (!oldChangeCount)
337 m_changedRows.reserve(asize: count);
338
339 int selectedRow = m_selectedPoint.x();
340 for (int i = 0; i < count; i++) {
341 bool newItem = true;
342 int candidate = startIndex + i;
343 for (int j = 0; j < oldChangeCount; j++) {
344 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
345 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
346 newItem = false;
347 break;
348 }
349 }
350 if (newItem) {
351 ChangeRow newChangeItem = {.series: series, .row: candidate};
352 m_changedRows.append(t: newChangeItem);
353 if (series == m_selectedSeries && selectedRow == candidate)
354 series->d_ptr->markItemLabelDirty();
355 }
356 }
357 if (count) {
358 m_changeTracker.rowsChanged = true;
359
360 if (series->isVisible())
361 adjustAxisRanges();
362 emitNeedRender();
363 }
364}
365
366void Surface3DController::handleItemChanged(int rowIndex, int columnIndex)
367{
368 QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
369 QSurface3DSeries *series = sender->series();
370
371 bool newItem = true;
372 QPoint candidate(rowIndex, columnIndex);
373 foreach (ChangeItem item, m_changedItems) {
374 if (item.point == candidate && item.series == series) {
375 newItem = false;
376 break;
377 }
378 }
379 if (newItem) {
380 ChangeItem newItem = {.series: series, .point: candidate};
381 m_changedItems.append(t: newItem);
382 m_changeTracker.itemChanged = true;
383
384 if (series == m_selectedSeries && m_selectedPoint == candidate)
385 series->d_ptr->markItemLabelDirty();
386
387 if (series->isVisible())
388 adjustAxisRanges();
389 emitNeedRender();
390 }
391}
392
393void Surface3DController::handleRowsAdded(int startIndex, int count)
394{
395 Q_UNUSED(startIndex);
396 Q_UNUSED(count);
397 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
398 if (series->isVisible()) {
399 adjustAxisRanges();
400 m_isDataDirty = true;
401 }
402 if (!m_changedSeriesList.contains(t: series))
403 m_changedSeriesList.append(t: series);
404 emitNeedRender();
405}
406
407void Surface3DController::handleRowsInserted(int startIndex, int count)
408{
409 Q_UNUSED(startIndex);
410 Q_UNUSED(count);
411 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
412 if (series == m_selectedSeries) {
413 // If rows inserted to selected series before the selection, adjust the selection
414 int selectedRow = m_selectedPoint.x();
415 if (startIndex <= selectedRow) {
416 selectedRow += count;
417 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
418 }
419 }
420
421 if (series->isVisible()) {
422 adjustAxisRanges();
423 m_isDataDirty = true;
424 }
425 if (!m_changedSeriesList.contains(t: series))
426 m_changedSeriesList.append(t: series);
427
428 emitNeedRender();
429}
430
431void Surface3DController::handleRowsRemoved(int startIndex, int count)
432{
433 Q_UNUSED(startIndex);
434 Q_UNUSED(count);
435 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
436 if (series == m_selectedSeries) {
437 // If rows removed from selected series before the selection, adjust the selection
438 int selectedRow = m_selectedPoint.x();
439 if (startIndex <= selectedRow) {
440 if ((startIndex + count) > selectedRow)
441 selectedRow = -1; // Selected row removed
442 else
443 selectedRow -= count; // Move selected row down by amount of rows removed
444
445 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
446 }
447 }
448
449 if (series->isVisible()) {
450 adjustAxisRanges();
451 m_isDataDirty = true;
452 }
453 if (!m_changedSeriesList.contains(t: series))
454 m_changedSeriesList.append(t: series);
455
456 emitNeedRender();
457}
458
459void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series)
460{
461 m_changeTracker.surfaceTextureChanged = true;
462
463 if (!m_changedTextures.contains(t: series))
464 m_changedTextures.append(t: series);
465
466 emitNeedRender();
467}
468
469void Surface3DController::adjustAxisRanges()
470{
471 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
472 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
473 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
474 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
475 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
476 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
477 bool first = true;
478
479 if (adjustX || adjustY || adjustZ) {
480 float minValueX = 0.0f;
481 float maxValueX = 0.0f;
482 float minValueY = 0.0f;
483 float maxValueY = 0.0f;
484 float minValueZ = 0.0f;
485 float maxValueZ = 0.0f;
486 int seriesCount = m_seriesList.size();
487 for (int series = 0; series < seriesCount; series++) {
488 const QSurface3DSeries *surfaceSeries =
489 static_cast<QSurface3DSeries *>(m_seriesList.at(i: series));
490 const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
491 if (surfaceSeries->isVisible() && proxy) {
492 QVector3D minLimits;
493 QVector3D maxLimits;
494 proxy->dptrc()->limitValues(minValues&: minLimits, maxValues&: maxLimits, axisX: valueAxisX, axisY: valueAxisY, axisZ: valueAxisZ);
495 if (adjustX) {
496 if (first) {
497 // First series initializes the values
498 minValueX = minLimits.x();
499 maxValueX = maxLimits.x();
500 } else {
501 minValueX = qMin(a: minValueX, b: minLimits.x());
502 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
503 }
504 }
505 if (adjustY) {
506 if (first) {
507 // First series initializes the values
508 minValueY = minLimits.y();
509 maxValueY = maxLimits.y();
510 } else {
511 minValueY = qMin(a: minValueY, b: minLimits.y());
512 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
513 }
514 }
515 if (adjustZ) {
516 if (first) {
517 // First series initializes the values
518 minValueZ = minLimits.z();
519 maxValueZ = maxLimits.z();
520 } else {
521 minValueZ = qMin(a: minValueZ, b: minLimits.z());
522 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
523 }
524 }
525 first = false;
526 }
527 }
528
529 static const float adjustmentRatio = 20.0f;
530 static const float defaultAdjustment = 1.0f;
531
532 if (adjustX) {
533 // If all points at same coordinate, need to default to some valid range
534 float adjustment = 0.0f;
535 if (minValueX == maxValueX) {
536 if (adjustZ) {
537 // X and Z are linked to have similar unit size, so choose the valid range based on it
538 if (minValueZ == maxValueZ)
539 adjustment = defaultAdjustment;
540 else
541 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
542 } else {
543 if (valueAxisZ)
544 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
545 else
546 adjustment = defaultAdjustment;
547 }
548 }
549 valueAxisX->dptr()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
550 }
551 if (adjustY) {
552 // If all points at same coordinate, need to default to some valid range
553 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
554 float adjustment = 0.0f;
555 if (minValueY == maxValueY)
556 adjustment = defaultAdjustment;
557 valueAxisY->dptr()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
558 }
559 if (adjustZ) {
560 // If all points at same coordinate, need to default to some valid range
561 float adjustment = 0.0f;
562 if (minValueZ == maxValueZ) {
563 if (adjustX) {
564 // X and Z are linked to have similar unit size, so choose the valid range based on it
565 if (minValueX == maxValueX)
566 adjustment = defaultAdjustment;
567 else
568 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
569 } else {
570 if (valueAxisX)
571 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
572 else
573 adjustment = defaultAdjustment;
574 }
575 }
576 valueAxisZ->dptr()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
577 }
578 }
579}
580
581QT_END_NAMESPACE
582

source code of qtdatavis3d/src/datavisualization/engine/surface3dcontroller.cpp