1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "q3dscene_p.h"
5
6QT_BEGIN_NAMESPACE
7
8/*!
9 * \class Q3DScene
10 * \inmodule QtGraphs
11 * \ingroup graphs_3D
12 * \brief Q3DScene class provides description of the 3D scene being visualized.
13 *
14 * The 3D scene contains a single active camera and a single active light
15 * source. Visualized data is assumed to be at a fixed location.
16 *
17 * The 3D scene also keeps track of the viewport in which graph rendering is
18 * done, the primary subviewport inside the viewport where the main 3D graphs
19 * view resides and the secondary subviewport where the 2D sliced view of the
20 * data resides. The subviewports are by default resized by the \a Q3DScene. To
21 * override the resize behavior you need to listen to both \l viewportChanged()
22 * and \l slicingActiveChanged() signals and recalculate the subviewports
23 * accordingly.
24 *
25 * Also the scene has flag for tracking if the secondary 2D slicing view is
26 * currently active or not. \note Not all graphs support the secondary 2D
27 * slicing view.
28 */
29
30/*!
31 * \class Q3DSceneChangeBitField
32 * \internal
33 */
34
35/*!
36 * \qmltype Scene3D
37 * \inqmlmodule QtGraphs
38 * \ingroup graphs_qml_3D
39 * \nativetype Q3DScene
40 * \brief Scene3D type provides description of the 3D scene being visualized.
41 *
42 * The 3D scene contains a single active camera and a single active light
43 * source. Visualized data is assumed to be at a fixed location.
44 *
45 * The 3D scene also keeps track of the viewport in which graph rendering is
46 * done, the primary subviewport inside the viewport where the main 3D graphs
47 * view resides and the secondary subviewport where the 2D sliced view of the
48 * data resides.
49 *
50 * Also the scene has flag for tracking if the secondary 2D slicing view is
51 * currently active or not. \note Not all graphs support the secondary 2D
52 * slicing view.
53 */
54
55/*!
56 * \qmlproperty rect Scene3D::primarySubViewport
57 *
58 * The current subviewport rectangle inside the viewport where the
59 * primary view of the graphs is targeted.
60 *
61 * If the primary sub viewport has not
62 * been explicitly set, it will be one fifth of the viewport.
63 * \note Setting primarySubViewport larger than or outside of viewport resizes
64 * viewport accordingly.
65 */
66
67/*!
68 * \qmlproperty rect Scene3D::secondarySubViewport
69 *
70 * The secondary viewport is used for drawing the 2D slice view in some
71 * graphs. If it has not been explicitly set, it will be equal to the size of the viewport.
72 * \note If the secondary sub viewport is larger than or outside of the
73 * viewport, the viewport is resized accordingly.
74 */
75
76/*!
77 * \qmlproperty point Scene3D::selectionQueryPosition
78 *
79 * The coordinates for the user input that should be processed
80 * by the scene as a selection. If this property is set to a value other than
81 * invalidSelectionPoint, the
82 * graph tries to select a data item at the given point within the primary
83 * viewport. After the rendering pass, the property is returned to its default
84 * state of invalidSelectionPoint.
85 */
86
87/*!
88 * \qmlproperty point Scene3D::graphPositionQuery
89 *
90 * The coordinates for the user input that should be processed by the scene as a
91 * graph position query. If this property is set to value other than
92 * invalidSelectionPoint, the graph tries to match a graph position to the given
93 * point within the primary viewport. After the rendering pass, this property is
94 * returned to its default state of invalidSelectionPoint. The queried graph
95 * position can be read from the GraphsItem3D::queriedGraphPosition property
96 * after the next render pass.
97 *
98 * There is no single correct 3D coordinate to match a particular screen
99 * position, so to be consistent, the queries are always done against the inner
100 * sides of an invisible box surrounding the graph.
101 *
102 * \note Bar graphs allow graph position queries only at the graph floor level.
103 *
104 * \sa GraphsItem3D::queriedGraphPosition
105 */
106
107/*!
108 * \qmlproperty bool Scene3D::slicingActive
109 *
110 * Defines whether the 2D slicing view is currently active. If \c true,
111 * Graphs3D::selectionMode must have either the
112 * \l{QtGraphs3D::SelectionFlag}{Graphs3D.SelectionRow} or
113 * \l{QtGraphs3D::SelectionFlag}{Graphs3D.SelectionColumn}
114 * set to a valid selection.
115 * \note Not all graphs support the 2D slicing view.
116 */
117
118/*!
119 * \qmlproperty bool Scene3D::secondarySubviewOnTop
120 *
121 * Defines whether the 2D slicing view or the 3D view is drawn on top.
122 */
123
124/*!
125 * \qmlproperty qreal Scene3D::devicePixelRatio
126 *
127 * The current device pixel ratio that is used when mapping input
128 * coordinates to pixel coordinates.
129 */
130
131/*!
132 * \qmlproperty point Scene3D::invalidSelectionPoint
133 * A constant property providing an invalid point for selection.
134 */
135
136/*!
137 \qmlsignal Scene3D::viewportChanged(rect viewport)
138
139 This signal is emitted when viewport changes to \a viewport.
140*/
141
142/*!
143 \qmlsignal Scene3D::primarySubViewportChanged(rect subViewport)
144
145 This signal is emitted when primarySubViewport changes to \a subViewport.
146*/
147
148/*!
149 \qmlsignal Scene3D::secondarySubViewportChanged(rect subViewport)
150
151 This signal is emitted when secondarySubViewport changes to \a subViewport.
152*/
153
154/*!
155 \qmlsignal Scene3D::secondarySubviewOnTopChanged(bool isSecondaryOnTop)
156
157 This signal is emitted when secondarySubviewOnTop changes to \a isSecondaryOnTop.
158*/
159
160/*!
161 \qmlsignal Scene3D::slicingActiveChanged(bool isSlicingActive)
162
163 This signal is emitted when slicingActive changes to \a isSlicingActive.
164*/
165
166/*!
167 \qmlsignal Scene3D::devicePixelRatioChanged(qreal pixelRatio)
168
169 This signal is emitted when devicePixelRatio changes to \a pixelRatio.
170*/
171
172/*!
173 \qmlsignal Scene3D::selectionQueryPositionChanged(point position)
174
175 This signal is emitted when selectionQueryPosition changes to \a position.
176*/
177
178/*!
179 \qmlsignal Scene3D::graphPositionQueryChanged(point position)
180
181 This signal is emitted when graphPositionQuery changes to \a position.
182*/
183
184/*!
185 * Constructs a basic scene with one light and one camera in it. An
186 * optional \a parent parameter can be given and is then passed to QObject
187 * constructor.
188 */
189Q3DScene::Q3DScene(QObject *parent)
190 : QObject(*(new Q3DScenePrivate()), parent)
191{}
192
193/*!
194 * Destroys the 3D scene and all the objects contained within it.
195 */
196Q3DScene::~Q3DScene() {}
197
198/*!
199 * \property Q3DScene::viewport
200 *
201 * \brief A read only property that contains the current viewport rectangle
202 * where all the 3D rendering is targeted.
203 */
204QRect Q3DScene::viewport() const
205{
206 Q_D(const Q3DScene);
207 return d->m_viewport;
208}
209
210/*!
211 * \property Q3DScene::primarySubViewport
212 *
213 * \brief The current subviewport rectangle inside the viewport where the
214 * primary view of the graphs is targeted.
215 *
216 * If the primary sub viewport has not been explicitly set,
217 * it will be one fifth of viewport().
218 *
219 * \note Setting primarySubViewport larger than or outside of the viewport
220 * resizes the viewport accordingly.
221 */
222QRect Q3DScene::primarySubViewport() const
223{
224 Q_D(const Q3DScene);
225 QRect primary = d->m_primarySubViewport;
226 if (primary.isNull()) {
227 primary = d->m_defaultSmallViewport;
228 }
229 return primary;
230}
231
232void Q3DScene::setPrimarySubViewport(QRect primarySubViewport)
233{
234 Q_D(Q3DScene);
235 if (d->m_primarySubViewport != primarySubViewport) {
236 if (!primarySubViewport.isValid() && !primarySubViewport.isNull()) {
237 qWarning(msg: "Viewport is invalid.");
238 return;
239 }
240
241 // If viewport is smaller than primarySubViewport, enlarge it
242 if ((d->m_viewport.width() < (primarySubViewport.width() + primarySubViewport.x()))
243 || (d->m_viewport.height() < (primarySubViewport.height() + primarySubViewport.y()))) {
244 d->m_viewport.setWidth(
245 qMax(a: d->m_viewport.width(), b: primarySubViewport.width() + primarySubViewport.x()));
246 d->m_viewport.setHeight(
247 qMax(a: d->m_viewport.height(), b: primarySubViewport.height() + primarySubViewport.y()));
248 d->updateDefaultViewports();
249 }
250
251 d->m_primarySubViewport = primarySubViewport;
252 d->m_changeTracker.primarySubViewportChanged = true;
253 d->m_sceneDirty = true;
254
255 emit primarySubViewportChanged(subViewport: primarySubViewport);
256 emit needRender();
257 }
258}
259
260/*!
261 * Returns whether the given \a point resides inside the primary subview or not.
262 * \return \c true if the point is inside the primary subview.
263 * \note If subviews are superimposed, and the given \a point resides inside
264 * both, result is \c true only when the primary subview is on top.
265 */
266bool Q3DScene::isPointInPrimarySubView(QPoint point)
267{
268 Q_D(Q3DScene);
269 int x = point.x();
270 int y = point.y();
271 bool isInSecondary = d->isInArea(area: secondarySubViewport(), x, y);
272 if (!isInSecondary || (isInSecondary && !d->m_isSecondarySubviewOnTop))
273 return d->isInArea(area: primarySubViewport(), x, y);
274 else
275 return false;
276}
277
278/*!
279 * Returns whether the given \a point resides inside the secondary subview or
280 * not. \return \c true if the point is inside the secondary subview. \note If
281 * subviews are superimposed, and the given \a point resides inside both, result
282 * is \c true only when the secondary subview is on top.
283 */
284bool Q3DScene::isPointInSecondarySubView(QPoint point)
285{
286 Q_D(Q3DScene);
287 int x = point.x();
288 int y = point.y();
289 bool isInPrimary = d->isInArea(area: primarySubViewport(), x, y);
290 if (!isInPrimary || (isInPrimary && d->m_isSecondarySubviewOnTop))
291 return d->isInArea(area: secondarySubViewport(), x, y);
292 else
293 return false;
294}
295
296/*!
297 * \property Q3DScene::secondarySubViewport
298 *
299 * \brief The secondary viewport rectangle inside the viewport.
300 *
301 * The secondary viewport is used for drawing the 2D slice view in some
302 * graphs. If it has not been explicitly set, it will be equal to the size of the viewport.
303 * \note If the secondary sub viewport is larger than or outside of the
304 * viewport, the viewport is resized accordingly.
305 */
306QRect Q3DScene::secondarySubViewport() const
307{
308 Q_D(const Q3DScene);
309 QRect secondary = d->m_secondarySubViewport;
310 if (secondary.isNull() && d->m_isSlicingActive)
311 secondary = d->m_defaultLargeViewport;
312 return secondary;
313}
314
315void Q3DScene::setSecondarySubViewport(QRect secondarySubViewport)
316{
317 Q_D(Q3DScene);
318 if (d->m_secondarySubViewport != secondarySubViewport) {
319 if (!secondarySubViewport.isValid() && !secondarySubViewport.isNull()) {
320 qWarning(msg: "Viewport is invalid.");
321 return;
322 }
323
324 // If viewport is smaller than secondarySubViewport, enlarge it
325 if ((d->m_viewport.width() < (secondarySubViewport.width() + secondarySubViewport.x()))
326 || (d->m_viewport.height()
327 < (secondarySubViewport.height() + secondarySubViewport.y()))) {
328 d->m_viewport.setWidth(qMax(a: d->m_viewport.width(),
329 b: secondarySubViewport.width() + secondarySubViewport.x()));
330 d->m_viewport.setHeight(qMax(a: d->m_viewport.height(),
331 b: secondarySubViewport.height() + secondarySubViewport.y()));
332 d->updateDefaultViewports();
333 }
334
335 d->m_secondarySubViewport = secondarySubViewport;
336 d->m_changeTracker.secondarySubViewportChanged = true;
337 d->m_sceneDirty = true;
338
339 emit secondarySubViewportChanged(subViewport: secondarySubViewport);
340 emit needRender();
341 }
342}
343
344/*!
345 * \property Q3DScene::selectionQueryPosition
346 *
347 * \brief The coordinates for the user input that should be processed
348 * by the scene as a selection.
349 *
350 * If this property is set to a value other than invalidSelectionPoint(), the
351 * graph tries to select a data item, axis label, or a custom item at the
352 * specified coordinates within the primary viewport.
353 * After the rendering pass, the property is returned to its default state of
354 * invalidSelectionPoint().
355 *
356 * \sa Q3DGraphsWidgetItem::selectedElement
357 */
358void Q3DScene::setSelectionQueryPosition(QPoint point)
359{
360 Q_D(Q3DScene);
361 if (point != d->m_selectionQueryPosition) {
362 d->m_selectionQueryPosition = point;
363 d->m_changeTracker.selectionQueryPositionChanged = true;
364 d->m_sceneDirty = true;
365
366 emit selectionQueryPositionChanged(position: point);
367 emit needRender();
368 }
369}
370
371QPoint Q3DScene::selectionQueryPosition() const
372{
373 Q_D(const Q3DScene);
374 return d->m_selectionQueryPosition;
375}
376
377/*!
378 * \return a QPoint signifying an invalid selection position.
379 */
380QPoint Q3DScene::invalidSelectionPoint() const
381{
382 static const QPoint invalidSelectionPos(-1, -1);
383 return invalidSelectionPos;
384}
385
386/*!
387 * \property Q3DScene::graphPositionQuery
388 *
389 * \brief The coordinates for the user input that should be processed
390 * by the scene as a graph position query.
391 *
392 * If this property is set to a value other than invalidSelectionPoint(), the
393 * graph tries to match a graph position to the specified coordinates
394 * within the primary viewport.
395 * After the rendering pass, this property is returned to its default state of
396 * invalidSelectionPoint(). The queried graph position can be read from the
397 * Q3DGraphsWidgetItem::queriedGraphPosition property after the next render pass.
398 *
399 * There is no single correct 3D coordinate to match a particular screen
400 * position, so to be consistent, the queries are always done against the inner
401 * sides of an invisible box surrounding the graph.
402 *
403 * \note Bar graphs allow graph position queries only at the graph floor level.
404 *
405 * \sa Q3DGraphsWidgetItem::queriedGraphPosition
406 */
407void Q3DScene::setGraphPositionQuery(QPoint point)
408{
409 Q_D(Q3DScene);
410 if (point != d->m_graphPositionQueryPosition) {
411 d->m_graphPositionQueryPosition = point;
412 d->m_changeTracker.graphPositionQueryPositionChanged = true;
413 d->m_sceneDirty = true;
414
415 emit graphPositionQueryChanged(position: point);
416 emit needRender();
417 }
418}
419
420QPoint Q3DScene::graphPositionQuery() const
421{
422 Q_D(const Q3DScene);
423 return d->m_graphPositionQueryPosition;
424}
425
426/*!
427 * \property Q3DScene::slicingActive
428 *
429 * \brief Whether the 2D slicing view is currently active.
430 *
431 * If \c true, Q3DGraphsWidgetItem::selectionMode must have either
432 * QtGraphs3D::SelectionFlag::Row or QtGraphs3D::SelectionFlag::Column set
433 * to a valid selection.
434 * \note Not all graphs support the 2D slicing view.
435 */
436bool Q3DScene::isSlicingActive() const
437{
438 Q_D(const Q3DScene);
439 return d->m_isSlicingActive;
440}
441
442void Q3DScene::setSlicingActive(bool isSlicing)
443{
444 Q_D(Q3DScene);
445 if (d->m_isSlicingActive != isSlicing) {
446 d->m_isSlicingActive = isSlicing;
447 d->m_changeTracker.slicingActivatedChanged = true;
448 d->m_sceneDirty = true;
449
450 emit slicingActiveChanged(isSlicingActive: isSlicing);
451 emit needRender();
452 }
453}
454
455/*!
456 * \property Q3DScene::secondarySubviewOnTop
457 *
458 * \brief Whether the 2D slicing view or the 3D view is drawn on top.
459 */
460bool Q3DScene::isSecondarySubviewOnTop() const
461{
462 Q_D(const Q3DScene);
463 return d->m_isSecondarySubviewOnTop;
464}
465
466void Q3DScene::setSecondarySubviewOnTop(bool isSecondaryOnTop)
467{
468 Q_D(Q3DScene);
469 if (d->m_isSecondarySubviewOnTop != isSecondaryOnTop) {
470 d->m_isSecondarySubviewOnTop = isSecondaryOnTop;
471 d->m_changeTracker.subViewportOrderChanged = true;
472 d->m_sceneDirty = true;
473
474 emit secondarySubviewOnTopChanged(isSecondaryOnTop);
475 emit needRender();
476 }
477}
478
479/*!
480 * \property Q3DScene::devicePixelRatio
481 *
482 * \brief The device pixel ratio that is used when mapping input
483 * coordinates to pixel coordinates.
484 */
485qreal Q3DScene::devicePixelRatio() const
486{
487 Q_D(const Q3DScene);
488 return d->m_devicePixelRatio;
489}
490
491void Q3DScene::setDevicePixelRatio(qreal pixelRatio)
492{
493 Q_D(Q3DScene);
494 if (d->m_devicePixelRatio != pixelRatio) {
495 d->m_devicePixelRatio = pixelRatio;
496 d->m_changeTracker.devicePixelRatioChanged = true;
497 d->m_sceneDirty = true;
498
499 emit devicePixelRatioChanged(pixelRatio);
500 emit needRender();
501 }
502}
503
504Q3DScenePrivate::Q3DScenePrivate()
505 : m_isSecondarySubviewOnTop(true)
506 , m_devicePixelRatio(1.0)
507 , m_isUnderSideCameraEnabled(false)
508 , m_isSlicingActive(false)
509 , m_windowSize(QSize(0, 0))
510 , m_sceneDirty(true)
511{
512 Q_Q(Q3DScene);
513 m_selectionQueryPosition = q->invalidSelectionPoint();
514 m_graphPositionQueryPosition = q->invalidSelectionPoint();
515}
516
517Q3DScenePrivate::~Q3DScenePrivate() {}
518
519// Copies changed values from this scene to the other scene. If the other scene
520// had same changes, those changes are discarded.
521void Q3DScenePrivate::sync(Q3DScenePrivate &other)
522{
523 Q_Q(Q3DScene);
524 if (m_changeTracker.windowSizeChanged) {
525 other.setWindowSize(windowSize());
526 m_changeTracker.windowSizeChanged = false;
527 other.m_changeTracker.windowSizeChanged = false;
528 }
529 if (m_changeTracker.viewportChanged) {
530 other.setViewport(m_viewport);
531 m_changeTracker.viewportChanged = false;
532 other.m_changeTracker.viewportChanged = false;
533 }
534 if (m_changeTracker.subViewportOrderChanged) {
535 other.q_func()->setSecondarySubviewOnTop(q->isSecondarySubviewOnTop());
536 m_changeTracker.subViewportOrderChanged = false;
537 other.m_changeTracker.subViewportOrderChanged = false;
538 }
539 if (m_changeTracker.primarySubViewportChanged) {
540 other.q_func()->setPrimarySubViewport(q->primarySubViewport());
541 m_changeTracker.primarySubViewportChanged = false;
542 other.m_changeTracker.primarySubViewportChanged = false;
543 }
544 if (m_changeTracker.secondarySubViewportChanged) {
545 other.q_func()->setSecondarySubViewport(q->secondarySubViewport());
546 m_changeTracker.secondarySubViewportChanged = false;
547 other.m_changeTracker.secondarySubViewportChanged = false;
548 }
549 if (m_changeTracker.selectionQueryPositionChanged) {
550 other.q_func()->setSelectionQueryPosition(q->selectionQueryPosition());
551 m_changeTracker.selectionQueryPositionChanged = false;
552 other.m_changeTracker.selectionQueryPositionChanged = false;
553 }
554 if (m_changeTracker.graphPositionQueryPositionChanged) {
555 other.q_func()->setGraphPositionQuery(q->graphPositionQuery());
556 m_changeTracker.graphPositionQueryPositionChanged = false;
557 other.m_changeTracker.graphPositionQueryPositionChanged = false;
558 }
559
560 if (m_changeTracker.slicingActivatedChanged) {
561 other.q_func()->setSlicingActive(q->isSlicingActive());
562 m_changeTracker.slicingActivatedChanged = false;
563 other.m_changeTracker.slicingActivatedChanged = false;
564 }
565
566 if (m_changeTracker.devicePixelRatioChanged) {
567 other.q_func()->setDevicePixelRatio(q->devicePixelRatio());
568 m_changeTracker.devicePixelRatioChanged = false;
569 other.m_changeTracker.devicePixelRatioChanged = false;
570 }
571
572 m_sceneDirty = false;
573 other.m_sceneDirty = false;
574}
575
576void Q3DScenePrivate::setViewport(const QRect viewport)
577{
578 Q_Q(Q3DScene);
579 if (m_viewport != viewport && viewport.isValid()) {
580 m_viewport = viewport;
581 updateDefaultViewports();
582 emit q->needRender();
583 }
584}
585
586void Q3DScenePrivate::setViewportSize(int width, int height)
587{
588 Q_Q(Q3DScene);
589 if (m_viewport.width() != width || m_viewport.height() != height) {
590 m_viewport.setWidth(width);
591 m_viewport.setHeight(height);
592 updateDefaultViewports();
593 emit q->needRender();
594 }
595}
596
597/*!
598 * \internal
599 * Sets the size of the window being rendered to. With widget based graphs, this
600 * is equal to the size of the QWindow and is same as the bounding rectangle.
601 * With declarative graphs this is equal to the size of the QQuickWindow and
602 * can be different from the bounding rectangle.
603 */
604void Q3DScenePrivate::setWindowSize(QSize size)
605{
606 Q_Q(Q3DScene);
607 if (m_windowSize != size) {
608 m_windowSize = size;
609 m_changeTracker.windowSizeChanged = true;
610 emit q->needRender();
611 }
612}
613
614QSize Q3DScenePrivate::windowSize() const
615{
616 return m_windowSize;
617}
618
619void Q3DScenePrivate::updateDefaultViewports()
620{
621 m_defaultLargeViewport = m_viewport;
622 m_defaultSmallViewport = QRect(0, 0, m_viewport.width() * 0.2, m_viewport.height() * 0.2);
623}
624
625void Q3DScenePrivate::markDirty()
626{
627 Q_Q(Q3DScene);
628 m_sceneDirty = true;
629 emit q->needRender();
630}
631
632bool Q3DScenePrivate::isInArea(const QRect area, int x, int y) const
633{
634 int areaMinX = area.x();
635 int areaMaxX = area.x() + area.width();
636 int areaMinY = area.y();
637 int areaMaxY = area.y() + area.height();
638 return (x >= areaMinX && x <= areaMaxX && y >= areaMinY && y <= areaMaxY);
639}
640
641QT_END_NAMESPACE
642

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtgraphs/src/graphs3d/engine/q3dscene.cpp