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

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