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

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