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 "q3dscene_p.h" |
31 | #include "q3dcamera_p.h" |
32 | #include "q3dlight_p.h" |
33 | |
34 | QT_BEGIN_NAMESPACE_DATAVISUALIZATION |
35 | |
36 | /*! |
37 | * \class Q3DScene |
38 | * \inmodule QtDataVisualization |
39 | * \brief Q3DScene class provides description of the 3D scene being visualized. |
40 | * \since QtDataVisualization 1.0 |
41 | * |
42 | * The 3D scene contains a single active camera and a single active light source. |
43 | * Visualized data is assumed to be at a fixed location. |
44 | * |
45 | * The 3D scene also keeps track of the viewport in which visualization rendering is done, |
46 | * the primary subviewport inside the viewport where the main 3D data visualization view resides |
47 | * and the secondary subviewport where the 2D sliced view of the data resides. The subviewports are |
48 | * by default resized by the \a Q3DScene. To override the resize behavior you need to listen to both |
49 | * \l viewportChanged() and \l slicingActiveChanged() signals and recalculate the subviewports accordingly. |
50 | * |
51 | * Also the scene has flag for tracking if the secondary 2D slicing view is currently active or not. |
52 | * \note Not all visualizations support the secondary 2D slicing view. |
53 | */ |
54 | |
55 | /*! |
56 | * \class Q3DSceneChangeBitField |
57 | * \internal |
58 | */ |
59 | |
60 | /*! |
61 | * \qmltype Scene3D |
62 | * \inqmlmodule QtDataVisualization |
63 | * \since QtDataVisualization 1.0 |
64 | * \ingroup datavisualization_qml |
65 | * \instantiates Q3DScene |
66 | * \brief Scene3D type provides description of the 3D scene being visualized. |
67 | * |
68 | * The 3D scene contains a single active camera and a single active light source. |
69 | * Visualized data is assumed to be at a fixed location. |
70 | * |
71 | * The 3D scene also keeps track of the viewport in which visualization rendering is done, |
72 | * the primary subviewport inside the viewport where the main 3D data visualization view resides |
73 | * and the secondary subviewport where the 2D sliced view of the data resides. |
74 | * |
75 | * Also the scene has flag for tracking if the secondary 2D slicing view is currently active or not. |
76 | * \note Not all visualizations support the secondary 2D slicing view. |
77 | */ |
78 | |
79 | /*! |
80 | * \qmlproperty rect Scene3D::viewport |
81 | * |
82 | * The current viewport rectangle where all 3D rendering is targeted. |
83 | */ |
84 | |
85 | /*! |
86 | * \qmlproperty rect Scene3D::primarySubViewport |
87 | * |
88 | * The current subviewport rectangle inside the viewport where the |
89 | * primary view of the data visualization is targeted. |
90 | * |
91 | * If slicingActive is \c false, the primary sub viewport will be equal to the |
92 | * viewport. If slicingActive is \c true and the primary sub viewport has not |
93 | * been explicitly set, it will be one fifth of the viewport. |
94 | * \note Setting primarySubViewport larger than or outside of viewport resizes viewport accordingly. |
95 | */ |
96 | |
97 | /*! |
98 | * \qmlproperty rect Scene3D::secondarySubViewport |
99 | * |
100 | * The secondary viewport rectangle inside the viewport. The secondary viewport |
101 | * is used for drawing the 2D slice view in some visualizations. If it has not |
102 | * been explicitly set, it will be null. If slicingActive is \c true, it will |
103 | * be equal to the viewport. |
104 | * \note If the secondary sub viewport is larger than or outside of the |
105 | * viewport, the viewport is resized accordingly. |
106 | */ |
107 | |
108 | /*! |
109 | * \qmlproperty point Scene3D::selectionQueryPosition |
110 | * |
111 | * The coordinates for the user input that should be processed |
112 | * by the scene as a selection. If this property is set to a value other than |
113 | * invalidSelectionPoint, the |
114 | * graph tries to select a data item at the given point within the primary viewport. |
115 | * After the rendering pass, the property is returned to its default state of |
116 | * invalidSelectionPoint. |
117 | */ |
118 | |
119 | /*! |
120 | * \qmlproperty point Scene3D::graphPositionQuery |
121 | * |
122 | * The coordinates for the user input that should be processed by the scene as a |
123 | * graph position query. If this property is set to value other than |
124 | * invalidSelectionPoint, the graph tries to match a graph position to the given point |
125 | * within the primary viewport. |
126 | * After the rendering pass, this property is returned to its default state of |
127 | * invalidSelectionPoint. The queried graph position can be read from the |
128 | * AbstractGraph3D::queriedGraphPosition property after the next render pass. |
129 | * |
130 | * There is no single correct 3D coordinate to match a particular screen position, so to be |
131 | * consistent, the queries are always done against the inner sides of an invisible box surrounding |
132 | * the graph. |
133 | * |
134 | * \note Bar graphs allow graph position queries only at the graph floor level. |
135 | * |
136 | * \sa AbstractGraph3D::queriedGraphPosition |
137 | */ |
138 | |
139 | /*! |
140 | * \qmlproperty bool Scene3D::slicingActive |
141 | * |
142 | * Defines whether the 2D slicing view is currently active. If \c true, |
143 | * AbstractGraph3D::selectionMode must have either the |
144 | * \l{QAbstract3DGraph::SelectionRow}{AbstractGraph3D.SelectionRow} or |
145 | * \l{QAbstract3DGraph::SelectionColumn}{AbstractGraph3D.SelectionColumn} |
146 | * set to a valid selection. |
147 | * \note Not all visualizations support the 2D slicing view. |
148 | */ |
149 | |
150 | /*! |
151 | * \qmlproperty bool Scene3D::secondarySubviewOnTop |
152 | * |
153 | * Defines whether the 2D slicing view or the 3D view is drawn on top. |
154 | */ |
155 | |
156 | /*! |
157 | * \qmlproperty Camera3D Scene3D::activeCamera |
158 | * |
159 | * The currently active camera in the 3D scene. |
160 | * When a Camera3D is set in the property, it is automatically added as child of |
161 | * the scene. |
162 | */ |
163 | |
164 | /*! |
165 | * \qmlproperty Light3D Scene3D::activeLight |
166 | * |
167 | * The currently active light in the 3D scene. |
168 | * When a Light3D is set in the property, it is automatically added as child of |
169 | * the scene. |
170 | */ |
171 | |
172 | /*! |
173 | * \qmlproperty float Scene3D::devicePixelRatio |
174 | * |
175 | * The current device pixel ratio that is used when mapping input |
176 | * coordinates to pixel coordinates. |
177 | */ |
178 | |
179 | /*! |
180 | * \qmlproperty point Scene3D::invalidSelectionPoint |
181 | * A constant property providing an invalid point for selection. |
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 constructor. |
187 | */ |
188 | Q3DScene::Q3DScene(QObject *parent) : |
189 | QObject(parent), |
190 | d_ptr(new Q3DScenePrivate(this)) |
191 | { |
192 | setActiveCamera(new Q3DCamera(0)); |
193 | setActiveLight(new Q3DLight(0)); |
194 | } |
195 | |
196 | /*! |
197 | * Destroys the 3D scene and all the objects contained within it. |
198 | */ |
199 | Q3DScene::~Q3DScene() |
200 | { |
201 | } |
202 | |
203 | /*! |
204 | * \property Q3DScene::viewport |
205 | * |
206 | * \brief A read only property that contains the current viewport rectangle |
207 | * where all the 3D rendering is targeted. |
208 | */ |
209 | QRect Q3DScene::viewport() const |
210 | { |
211 | return d_ptr->m_viewport; |
212 | } |
213 | |
214 | /*! |
215 | * \property Q3DScene::primarySubViewport |
216 | * |
217 | * \brief The current subviewport rectangle inside the viewport where the |
218 | * primary view of the data visualization is targeted. |
219 | * |
220 | * If isSlicingActive() is \c false, the primary sub viewport is equal to |
221 | * viewport(). If isSlicingActive() is \c true and the primary sub viewport has |
222 | * not been explicitly set, it will be one fifth of viewport(). |
223 | * |
224 | * \note Setting primarySubViewport larger than or outside of the viewport |
225 | * resizes the viewport accordingly. |
226 | */ |
227 | QRect Q3DScene::primarySubViewport() const |
228 | { |
229 | QRect primary = d_ptr->m_primarySubViewport; |
230 | if (primary.isNull()) { |
231 | if (d_ptr->m_isSlicingActive) |
232 | primary = d_ptr->m_defaultSmallViewport; |
233 | else |
234 | primary = d_ptr->m_defaultLargeViewport; |
235 | } |
236 | return primary; |
237 | } |
238 | |
239 | void Q3DScene::setPrimarySubViewport(const QRect &primarySubViewport) |
240 | { |
241 | if (d_ptr->m_primarySubViewport != primarySubViewport) { |
242 | if (!primarySubViewport.isValid() && !primarySubViewport.isNull()) { |
243 | qWarning(msg: "Viewport is invalid." ); |
244 | return; |
245 | } |
246 | |
247 | // If viewport is smaller than primarySubViewport, enlarge it |
248 | if ((d_ptr->m_viewport.width() < (primarySubViewport.width() |
249 | + primarySubViewport.x())) |
250 | || (d_ptr->m_viewport.height() < (primarySubViewport.height() |
251 | + primarySubViewport.y()))) { |
252 | d_ptr->m_viewport.setWidth(qMax(a: d_ptr->m_viewport.width(), |
253 | b: primarySubViewport.width() + primarySubViewport.x())); |
254 | d_ptr->m_viewport.setHeight(qMax(a: d_ptr->m_viewport.height(), |
255 | b: primarySubViewport.height() + primarySubViewport.y())); |
256 | d_ptr->calculateSubViewports(); |
257 | } |
258 | |
259 | d_ptr->m_primarySubViewport = primarySubViewport; |
260 | d_ptr->updateGLSubViewports(); |
261 | d_ptr->m_changeTracker.primarySubViewportChanged = true; |
262 | d_ptr->m_sceneDirty = true; |
263 | |
264 | emit primarySubViewportChanged(subViewport: primarySubViewport); |
265 | emit d_ptr->needRender(); |
266 | } |
267 | } |
268 | |
269 | /*! |
270 | * Returns whether the given \a point resides inside the primary subview or not. |
271 | * \return \c true if the point is inside the primary subview. |
272 | * \note If subviews are superimposed, and the given \a point resides inside both, result is |
273 | * \c true only when the primary subview is on top. |
274 | */ |
275 | bool Q3DScene::isPointInPrimarySubView(const QPoint &point) |
276 | { |
277 | int x = point.x(); |
278 | int y = point.y(); |
279 | bool isInSecondary = d_ptr->isInArea(area: secondarySubViewport(), x, y); |
280 | if (!isInSecondary || (isInSecondary && !d_ptr->m_isSecondarySubviewOnTop)) |
281 | return d_ptr->isInArea(area: primarySubViewport(), x, y); |
282 | else |
283 | return false; |
284 | } |
285 | |
286 | /*! |
287 | * Returns whether the given \a point resides inside the secondary subview or not. |
288 | * \return \c true if the point is inside the secondary subview. |
289 | * \note If subviews are superimposed, and the given \a point resides inside both, result is |
290 | * \c true only when the secondary subview is on top. |
291 | */ |
292 | bool Q3DScene::isPointInSecondarySubView(const QPoint &point) |
293 | { |
294 | int x = point.x(); |
295 | int y = point.y(); |
296 | bool isInPrimary = d_ptr->isInArea(area: primarySubViewport(), x, y); |
297 | if (!isInPrimary || (isInPrimary && d_ptr->m_isSecondarySubviewOnTop)) |
298 | return d_ptr->isInArea(area: secondarySubViewport(), x, y); |
299 | else |
300 | return false; |
301 | } |
302 | |
303 | /*! |
304 | * \property Q3DScene::secondarySubViewport |
305 | * |
306 | * \brief The secondary viewport rectangle inside the viewport. |
307 | * |
308 | * The secondary viewport is used for drawing the 2D slice view in some |
309 | * visualizations. If it has not been explicitly set, it will be equal to |
310 | * QRect. If isSlicingActive() is \c true, it will be equal to \l viewport. |
311 | * \note If the secondary sub viewport is larger than or outside of the |
312 | * viewport, the viewport is resized accordingly. |
313 | */ |
314 | QRect Q3DScene::secondarySubViewport() const |
315 | { |
316 | QRect secondary = d_ptr->m_secondarySubViewport; |
317 | if (secondary.isNull() && d_ptr->m_isSlicingActive) |
318 | secondary = d_ptr->m_defaultLargeViewport; |
319 | return secondary; |
320 | } |
321 | |
322 | void Q3DScene::setSecondarySubViewport(const QRect &secondarySubViewport) |
323 | { |
324 | if (d_ptr->m_secondarySubViewport != secondarySubViewport) { |
325 | if (!secondarySubViewport.isValid() && !secondarySubViewport.isNull()) { |
326 | qWarning(msg: "Viewport is invalid." ); |
327 | return; |
328 | } |
329 | |
330 | // If viewport is smaller than secondarySubViewport, enlarge it |
331 | if ((d_ptr->m_viewport.width() < (secondarySubViewport.width() |
332 | + secondarySubViewport.x())) |
333 | || (d_ptr->m_viewport.height() < (secondarySubViewport.height() |
334 | + secondarySubViewport.y()))) { |
335 | d_ptr->m_viewport.setWidth(qMax(a: d_ptr->m_viewport.width(), |
336 | b: secondarySubViewport.width() |
337 | + secondarySubViewport.x())); |
338 | d_ptr->m_viewport.setHeight(qMax(a: d_ptr->m_viewport.height(), |
339 | b: secondarySubViewport.height() |
340 | + secondarySubViewport.y())); |
341 | d_ptr->calculateSubViewports(); |
342 | } |
343 | |
344 | d_ptr->m_secondarySubViewport = secondarySubViewport; |
345 | d_ptr->updateGLSubViewports(); |
346 | d_ptr->m_changeTracker.secondarySubViewportChanged = true; |
347 | d_ptr->m_sceneDirty = true; |
348 | |
349 | emit secondarySubViewportChanged(subViewport: secondarySubViewport); |
350 | emit d_ptr->needRender(); |
351 | } |
352 | } |
353 | |
354 | /*! |
355 | * \property Q3DScene::selectionQueryPosition |
356 | * |
357 | * \brief The coordinates for the user input that should be processed |
358 | * by the scene as a selection. |
359 | * |
360 | * If this property is set to a value other than invalidSelectionPoint(), the |
361 | * graph tries to select a data item, axis label, or a custom item at the |
362 | * specified coordinates within the primary viewport. |
363 | * After the rendering pass, the property is returned to its default state of |
364 | * invalidSelectionPoint(). |
365 | * |
366 | * \sa QAbstract3DGraph::selectedElement |
367 | */ |
368 | void Q3DScene::setSelectionQueryPosition(const QPoint &point) |
369 | { |
370 | if (point != d_ptr->m_selectionQueryPosition) { |
371 | d_ptr->m_selectionQueryPosition = point; |
372 | d_ptr->m_changeTracker.selectionQueryPositionChanged = true; |
373 | d_ptr->m_sceneDirty = true; |
374 | |
375 | emit selectionQueryPositionChanged(position: point); |
376 | emit d_ptr->needRender(); |
377 | } |
378 | } |
379 | |
380 | QPoint Q3DScene::selectionQueryPosition() const |
381 | { |
382 | return d_ptr->m_selectionQueryPosition; |
383 | } |
384 | |
385 | /*! |
386 | * \return a QPoint signifying an invalid selection position. |
387 | */ |
388 | QPoint Q3DScene::invalidSelectionPoint() |
389 | { |
390 | static const QPoint invalidSelectionPos(-1, -1); |
391 | return invalidSelectionPos; |
392 | } |
393 | |
394 | /*! |
395 | * \property Q3DScene::graphPositionQuery |
396 | * |
397 | * \brief The coordinates for the user input that should be processed |
398 | * by the scene as a graph position query. |
399 | * |
400 | * If this property is set to a value other than invalidSelectionPoint(), the |
401 | * graph tries to match a graph position to the specified coordinates |
402 | * within the primary viewport. |
403 | * After the rendering pass, this property is returned to its default state of |
404 | * invalidSelectionPoint(). The queried graph position can be read from the |
405 | * QAbstract3DGraph::queriedGraphPosition property after the next render pass. |
406 | * |
407 | * There is no single correct 3D coordinate to match a particular screen position, so to be |
408 | * consistent, the queries are always done against the inner sides of an invisible box surrounding |
409 | * the graph. |
410 | * |
411 | * \note Bar graphs allow graph position queries only at the graph floor level. |
412 | * |
413 | * \sa QAbstract3DGraph::queriedGraphPosition |
414 | */ |
415 | void Q3DScene::setGraphPositionQuery(const QPoint &point) |
416 | { |
417 | if (point != d_ptr->m_graphPositionQueryPosition) { |
418 | d_ptr->m_graphPositionQueryPosition = point; |
419 | d_ptr->m_changeTracker.graphPositionQueryPositionChanged = true; |
420 | d_ptr->m_sceneDirty = true; |
421 | |
422 | emit graphPositionQueryChanged(position: point); |
423 | emit d_ptr->needRender(); |
424 | } |
425 | } |
426 | |
427 | QPoint Q3DScene::graphPositionQuery() const |
428 | { |
429 | return d_ptr->m_graphPositionQueryPosition; |
430 | } |
431 | |
432 | /*! |
433 | * \property Q3DScene::slicingActive |
434 | * |
435 | * \brief Whether the 2D slicing view is currently active. |
436 | * |
437 | * If \c true, QAbstract3DGraph::selectionMode must have either |
438 | * QAbstract3DGraph::SelectionRow or QAbstract3DGraph::SelectionColumn set |
439 | * to a valid selection. |
440 | * \note Not all visualizations support the 2D slicing view. |
441 | */ |
442 | bool Q3DScene::isSlicingActive() const |
443 | { |
444 | return d_ptr->m_isSlicingActive; |
445 | } |
446 | |
447 | void Q3DScene::setSlicingActive(bool isSlicing) |
448 | { |
449 | if (d_ptr->m_isSlicingActive != isSlicing) { |
450 | d_ptr->m_isSlicingActive = isSlicing; |
451 | d_ptr->m_changeTracker.slicingActivatedChanged = true; |
452 | d_ptr->m_sceneDirty = true; |
453 | |
454 | // Set secondary subview behind primary to achieve default functionality (= clicking on |
455 | // primary disables slice) |
456 | setSecondarySubviewOnTop(!isSlicing); |
457 | |
458 | d_ptr->calculateSubViewports(); |
459 | emit slicingActiveChanged(isSlicingActive: isSlicing); |
460 | emit d_ptr->needRender(); |
461 | } |
462 | } |
463 | |
464 | /*! |
465 | * \property Q3DScene::secondarySubviewOnTop |
466 | * |
467 | * \brief Whether the 2D slicing view or the 3D view is drawn on top. |
468 | */ |
469 | bool Q3DScene::isSecondarySubviewOnTop() const |
470 | { |
471 | return d_ptr->m_isSecondarySubviewOnTop; |
472 | } |
473 | |
474 | void Q3DScene::setSecondarySubviewOnTop(bool isSecondaryOnTop) |
475 | { |
476 | if (d_ptr->m_isSecondarySubviewOnTop != isSecondaryOnTop) { |
477 | d_ptr->m_isSecondarySubviewOnTop = isSecondaryOnTop; |
478 | d_ptr->m_changeTracker.subViewportOrderChanged = true; |
479 | d_ptr->m_sceneDirty = true; |
480 | |
481 | emit secondarySubviewOnTopChanged(isSecondaryOnTop); |
482 | emit d_ptr->needRender(); |
483 | } |
484 | } |
485 | |
486 | /*! |
487 | * \property Q3DScene::activeCamera |
488 | * |
489 | * \brief The currently active camera in the 3D scene. |
490 | * |
491 | * When a new Q3DCamera object is set, it is automatically added as child of |
492 | * the scene. |
493 | */ |
494 | Q3DCamera *Q3DScene::activeCamera() const |
495 | { |
496 | return d_ptr->m_camera; |
497 | } |
498 | |
499 | void Q3DScene::setActiveCamera(Q3DCamera *camera) |
500 | { |
501 | Q_ASSERT(camera); |
502 | |
503 | // Add new camera as child of the scene |
504 | if (camera->parent() != this) |
505 | camera->setParent(this); |
506 | |
507 | if (camera != d_ptr->m_camera) { |
508 | if (d_ptr->m_camera) { |
509 | disconnect(sender: d_ptr->m_camera, signal: &Q3DCamera::xRotationChanged, receiver: d_ptr.data(), |
510 | slot: &Q3DScenePrivate::needRender); |
511 | disconnect(sender: d_ptr->m_camera, signal: &Q3DCamera::yRotationChanged, receiver: d_ptr.data(), |
512 | slot: &Q3DScenePrivate::needRender); |
513 | disconnect(sender: d_ptr->m_camera, signal: &Q3DCamera::zoomLevelChanged, receiver: d_ptr.data(), |
514 | slot: &Q3DScenePrivate::needRender); |
515 | } |
516 | |
517 | d_ptr->m_camera = camera; |
518 | d_ptr->m_changeTracker.cameraChanged = true; |
519 | d_ptr->m_sceneDirty = true; |
520 | |
521 | |
522 | if (camera) { |
523 | connect(sender: camera, signal: &Q3DCamera::xRotationChanged, receiver: d_ptr.data(), |
524 | slot: &Q3DScenePrivate::needRender); |
525 | connect(sender: camera, signal: &Q3DCamera::yRotationChanged, receiver: d_ptr.data(), |
526 | slot: &Q3DScenePrivate::needRender); |
527 | connect(sender: camera, signal: &Q3DCamera::zoomLevelChanged, receiver: d_ptr.data(), |
528 | slot: &Q3DScenePrivate::needRender); |
529 | } |
530 | |
531 | emit activeCameraChanged(camera); |
532 | emit d_ptr->needRender(); |
533 | } |
534 | } |
535 | |
536 | /*! |
537 | * \property Q3DScene::activeLight |
538 | * |
539 | * \brief The currently active light in the 3D scene. |
540 | * |
541 | * When a new Q3DLight objects is set, it is automatically added as child of |
542 | * the scene. |
543 | */ |
544 | Q3DLight *Q3DScene::activeLight() const |
545 | { |
546 | return d_ptr->m_light; |
547 | } |
548 | |
549 | void Q3DScene::setActiveLight(Q3DLight *light) |
550 | { |
551 | Q_ASSERT(light); |
552 | |
553 | // Add new light as child of the scene |
554 | if (light->parent() != this) |
555 | light->setParent(this); |
556 | |
557 | if (light != d_ptr->m_light) { |
558 | d_ptr->m_light = light; |
559 | d_ptr->m_changeTracker.lightChanged = true; |
560 | d_ptr->m_sceneDirty = true; |
561 | |
562 | emit activeLightChanged(light); |
563 | emit d_ptr->needRender(); |
564 | } |
565 | } |
566 | |
567 | /*! |
568 | * \property Q3DScene::devicePixelRatio |
569 | * |
570 | * \brief The device pixel ratio that is used when mapping input |
571 | * coordinates to pixel coordinates. |
572 | */ |
573 | float Q3DScene::devicePixelRatio() const |
574 | { |
575 | return d_ptr->m_devicePixelRatio; |
576 | } |
577 | |
578 | void Q3DScene::setDevicePixelRatio(float pixelRatio) |
579 | { |
580 | if (d_ptr->m_devicePixelRatio != pixelRatio) { |
581 | d_ptr->m_devicePixelRatio = pixelRatio; |
582 | d_ptr->m_changeTracker.devicePixelRatioChanged = true; |
583 | d_ptr->m_sceneDirty = true; |
584 | |
585 | emit devicePixelRatioChanged(pixelRatio); |
586 | d_ptr->updateGLViewport(); |
587 | emit d_ptr->needRender(); |
588 | } |
589 | } |
590 | |
591 | Q3DScenePrivate::Q3DScenePrivate(Q3DScene *q) : |
592 | QObject(0), |
593 | q_ptr(q), |
594 | m_isSecondarySubviewOnTop(true), |
595 | m_devicePixelRatio(1.f), |
596 | m_camera(), |
597 | m_light(), |
598 | m_isUnderSideCameraEnabled(false), |
599 | m_isSlicingActive(false), |
600 | m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()), |
601 | m_graphPositionQueryPosition(Q3DScene::invalidSelectionPoint()), |
602 | m_windowSize(QSize(0, 0)), |
603 | m_sceneDirty(true) |
604 | { |
605 | } |
606 | |
607 | Q3DScenePrivate::~Q3DScenePrivate() |
608 | { |
609 | delete m_camera; |
610 | delete m_light; |
611 | } |
612 | |
613 | // Copies changed values from this scene to the other scene. If the other scene had same changes, |
614 | // those changes are discarded. |
615 | void Q3DScenePrivate::sync(Q3DScenePrivate &other) |
616 | { |
617 | if (m_changeTracker.windowSizeChanged) { |
618 | other.setWindowSize(windowSize()); |
619 | m_changeTracker.windowSizeChanged = false; |
620 | other.m_changeTracker.windowSizeChanged = false; |
621 | } |
622 | if (m_changeTracker.viewportChanged) { |
623 | other.setViewport(m_viewport); |
624 | m_changeTracker.viewportChanged = false; |
625 | other.m_changeTracker.viewportChanged = false; |
626 | } |
627 | if (m_changeTracker.subViewportOrderChanged) { |
628 | other.q_ptr->setSecondarySubviewOnTop(q_ptr->isSecondarySubviewOnTop()); |
629 | m_changeTracker.subViewportOrderChanged = false; |
630 | other.m_changeTracker.subViewportOrderChanged = false; |
631 | } |
632 | if (m_changeTracker.primarySubViewportChanged) { |
633 | other.q_ptr->setPrimarySubViewport(q_ptr->primarySubViewport()); |
634 | m_changeTracker.primarySubViewportChanged = false; |
635 | other.m_changeTracker.primarySubViewportChanged = false; |
636 | } |
637 | if (m_changeTracker.secondarySubViewportChanged) { |
638 | other.q_ptr->setSecondarySubViewport(q_ptr->secondarySubViewport()); |
639 | m_changeTracker.secondarySubViewportChanged = false; |
640 | other.m_changeTracker.secondarySubViewportChanged = false; |
641 | } |
642 | if (m_changeTracker.selectionQueryPositionChanged) { |
643 | other.q_ptr->setSelectionQueryPosition(q_ptr->selectionQueryPosition()); |
644 | m_changeTracker.selectionQueryPositionChanged = false; |
645 | other.m_changeTracker.selectionQueryPositionChanged = false; |
646 | } |
647 | if (m_changeTracker.graphPositionQueryPositionChanged) { |
648 | other.q_ptr->setGraphPositionQuery(q_ptr->graphPositionQuery()); |
649 | m_changeTracker.graphPositionQueryPositionChanged = false; |
650 | other.m_changeTracker.graphPositionQueryPositionChanged = false; |
651 | } |
652 | if (m_changeTracker.cameraChanged) { |
653 | m_camera->setDirty(true); |
654 | m_changeTracker.cameraChanged = false; |
655 | other.m_changeTracker.cameraChanged = false; |
656 | } |
657 | m_camera->d_ptr->sync(other&: *other.m_camera); |
658 | |
659 | if (m_changeTracker.lightChanged) { |
660 | m_light->setDirty(true); |
661 | m_changeTracker.lightChanged = false; |
662 | other.m_changeTracker.lightChanged = false; |
663 | } |
664 | m_light->d_ptr->sync(other&: *other.m_light); |
665 | |
666 | if (m_changeTracker.slicingActivatedChanged) { |
667 | other.q_ptr->setSlicingActive(q_ptr->isSlicingActive()); |
668 | m_changeTracker.slicingActivatedChanged = false; |
669 | other.m_changeTracker.slicingActivatedChanged = false; |
670 | } |
671 | |
672 | if (m_changeTracker.devicePixelRatioChanged) { |
673 | other.q_ptr->setDevicePixelRatio(q_ptr->devicePixelRatio()); |
674 | m_changeTracker.devicePixelRatioChanged = false; |
675 | other.m_changeTracker.devicePixelRatioChanged = false; |
676 | } |
677 | |
678 | m_sceneDirty = false; |
679 | other.m_sceneDirty = false; |
680 | } |
681 | |
682 | void Q3DScenePrivate::setViewport(const QRect &viewport) |
683 | { |
684 | if (m_viewport != viewport && viewport.isValid()) { |
685 | m_viewport = viewport; |
686 | calculateSubViewports(); |
687 | emit needRender(); |
688 | } |
689 | } |
690 | |
691 | void Q3DScenePrivate::setViewportSize(int width, int height) |
692 | { |
693 | if (m_viewport.width() != width || m_viewport.height() != height) { |
694 | m_viewport.setWidth(width); |
695 | m_viewport.setHeight(height); |
696 | calculateSubViewports(); |
697 | emit needRender(); |
698 | } |
699 | } |
700 | |
701 | /*! |
702 | * \internal |
703 | * Sets the size of the window being rendered to. With widget based graphs, this |
704 | * is equal to the size of the QWindow and is same as the bounding rectangle. |
705 | * With declarative graphs this is equal to the size of the QQuickWindow and |
706 | * can be different from the bounding rectangle. |
707 | */ |
708 | void Q3DScenePrivate::setWindowSize(const QSize &size) |
709 | { |
710 | if (m_windowSize != size) { |
711 | m_windowSize = size; |
712 | updateGLViewport(); |
713 | m_changeTracker.windowSizeChanged = true; |
714 | emit needRender(); |
715 | } |
716 | } |
717 | |
718 | QSize Q3DScenePrivate::windowSize() const |
719 | { |
720 | return m_windowSize; |
721 | } |
722 | |
723 | void Q3DScenePrivate::calculateSubViewports() |
724 | { |
725 | // Calculates the default subviewport layout, used when slicing |
726 | const float smallerViewPortRatio = 0.2f; |
727 | m_defaultSmallViewport = QRect(0, 0, |
728 | m_viewport.width() * smallerViewPortRatio, |
729 | m_viewport.height() * smallerViewPortRatio); |
730 | m_defaultLargeViewport = QRect(0, 0, |
731 | m_viewport.width(), |
732 | m_viewport.height()); |
733 | |
734 | updateGLViewport(); |
735 | } |
736 | |
737 | void Q3DScenePrivate::updateGLViewport() |
738 | { |
739 | // Update GL viewport |
740 | m_glViewport.setX(m_viewport.x() * m_devicePixelRatio); |
741 | m_glViewport.setY((m_windowSize.height() - (m_viewport.y() + m_viewport.height())) |
742 | * m_devicePixelRatio); |
743 | m_glViewport.setWidth(m_viewport.width() * m_devicePixelRatio); |
744 | m_glViewport.setHeight(m_viewport.height() * m_devicePixelRatio); |
745 | |
746 | m_changeTracker.viewportChanged = true; |
747 | m_sceneDirty = true; |
748 | |
749 | // Do default subviewport changes first, then allow signal listeners to override. |
750 | updateGLSubViewports(); |
751 | emit q_ptr->viewportChanged(viewport: m_viewport); |
752 | } |
753 | |
754 | void Q3DScenePrivate::updateGLSubViewports() |
755 | { |
756 | if (m_isSlicingActive) { |
757 | QRect primary = m_primarySubViewport; |
758 | QRect secondary = m_secondarySubViewport; |
759 | if (primary.isNull()) |
760 | primary = m_defaultSmallViewport; |
761 | if (secondary.isNull()) |
762 | secondary = m_defaultLargeViewport; |
763 | |
764 | m_glPrimarySubViewport.setX((primary.x() + m_viewport.x()) * m_devicePixelRatio); |
765 | m_glPrimarySubViewport.setY((m_windowSize.height() |
766 | - (primary.y() + primary.height() + m_viewport.y())) |
767 | * m_devicePixelRatio); |
768 | m_glPrimarySubViewport.setWidth(primary.width() * m_devicePixelRatio); |
769 | m_glPrimarySubViewport.setHeight(primary.height() * m_devicePixelRatio); |
770 | |
771 | m_glSecondarySubViewport.setX((secondary.x() + m_viewport.x()) * m_devicePixelRatio); |
772 | m_glSecondarySubViewport.setY((m_windowSize.height() |
773 | - (secondary.y() + secondary.height() + m_viewport.y())) |
774 | * m_devicePixelRatio); |
775 | m_glSecondarySubViewport.setWidth(secondary.width() * m_devicePixelRatio); |
776 | m_glSecondarySubViewport.setHeight(secondary.height() * m_devicePixelRatio); |
777 | } else { |
778 | m_glPrimarySubViewport.setX(m_viewport.x() * m_devicePixelRatio); |
779 | m_glPrimarySubViewport.setY((m_windowSize.height() - (m_viewport.y() + m_viewport.height())) |
780 | * m_devicePixelRatio); |
781 | m_glPrimarySubViewport.setWidth(m_viewport.width() * m_devicePixelRatio); |
782 | m_glPrimarySubViewport.setHeight(m_viewport.height() * m_devicePixelRatio); |
783 | |
784 | m_glSecondarySubViewport = QRect(); |
785 | } |
786 | } |
787 | |
788 | QRect Q3DScenePrivate::glViewport() |
789 | { |
790 | return m_glViewport; |
791 | } |
792 | |
793 | QRect Q3DScenePrivate::glPrimarySubViewport() |
794 | { |
795 | return m_glPrimarySubViewport; |
796 | } |
797 | |
798 | QRect Q3DScenePrivate::glSecondarySubViewport() |
799 | { |
800 | return m_glSecondarySubViewport; |
801 | } |
802 | |
803 | /*! |
804 | * \internal |
805 | * Calculates and sets the light position relative to the currently active camera using the given |
806 | * parameters. |
807 | * The relative 3D offset to the current camera position is defined in \a relativePosition. |
808 | * Optional \a fixedRotation fixes the light rotation around the data visualization area to the |
809 | * given value in degrees. |
810 | * Optional \a distanceModifier modifies the distance of the light from the data visualization. |
811 | */ |
812 | void Q3DScenePrivate::setLightPositionRelativeToCamera(const QVector3D &relativePosition, |
813 | float fixedRotation, float distanceModifier) |
814 | { |
815 | m_light->setPosition(m_camera->d_ptr->calculatePositionRelativeToCamera(relativePosition, |
816 | fixedRotation, |
817 | distanceModifier)); |
818 | } |
819 | |
820 | void Q3DScenePrivate::markDirty() |
821 | { |
822 | m_sceneDirty = true; |
823 | emit needRender(); |
824 | } |
825 | |
826 | bool Q3DScenePrivate::isInArea(const QRect &area, int x, int y) const |
827 | { |
828 | int areaMinX = area.x(); |
829 | int areaMaxX = area.x() + area.width(); |
830 | int areaMinY = area.y(); |
831 | int areaMaxY = area.y() + area.height(); |
832 | return ( x >= areaMinX && x <= areaMaxX && y >= areaMinY && y <= areaMaxY ); |
833 | } |
834 | |
835 | QT_END_NAMESPACE_DATAVISUALIZATION |
836 | |