1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qobjectpicker.h" |
41 | #include "qobjectpicker_p.h" |
42 | #include <Qt3DCore/qentity.h> |
43 | #include <Qt3DCore/private/qcomponent_p.h> |
44 | #include <Qt3DCore/private/qscene_p.h> |
45 | #include <Qt3DRender/qpickevent.h> |
46 | #include <Qt3DRender/QViewport> |
47 | #include <Qt3DRender/private/qpickevent_p.h> |
48 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | namespace Qt3DRender { |
52 | |
53 | /*! |
54 | \class Qt3DRender::QObjectPicker |
55 | \inmodule Qt3DRender |
56 | |
57 | \brief The QObjectPicker class instantiates a component that can |
58 | be used to interact with a QEntity by a process known as picking. |
59 | |
60 | For every combination of viewport and camera, picking casts a ray through the scene to |
61 | find entities who's bounding volume intersects the ray. The bounding volume is computed using |
62 | the values in the attribute buffer specified by the boundingVolumePositionAttribute of the |
63 | geometry. |
64 | |
65 | The signals pressed(), released(), clicked(), moved(), entered(), and exited() are |
66 | emitted when the bounding volume defined by the pickAttribute property intersects |
67 | with a ray. |
68 | |
69 | Most signals carry a QPickEvent instance. If QPickingSettings::pickMode() is set to |
70 | QPickingSettings::TrianglePicking, the actual type of the pick parameter will be |
71 | QPickTriangleEvent. |
72 | |
73 | Pick queries are performed on mouse press and mouse release. |
74 | If drag is enabled, queries also happen on each mouse move while any button is pressed. |
75 | If hover is enabled, queries happen on every mouse move even if no button is pressed. |
76 | |
77 | For generalised ray casting queries, see Qt3DRender::QRayCaster and Qt3DRender::QScreenRayCaster. |
78 | |
79 | \sa Qt3DRender::QPickingSettings, Qt3DRender::QGeometry, Qt3DRender::QAttribute, |
80 | Qt3DRender::QPickEvent, Qt3DRender::QPickTriangleEvent, Qt3DRender::QNoPicking |
81 | |
82 | \note Instances of this component shouldn't be shared, not respecting that |
83 | condition will most likely result in undefined behavior. |
84 | |
85 | \note The camera far plane value affects picking and produces incorrect results due to |
86 | floating-point precision if it is greater than ~100 000. |
87 | |
88 | \since 5.6 |
89 | */ |
90 | |
91 | /*! |
92 | \qmltype ObjectPicker |
93 | \instantiates Qt3DRender::QObjectPicker |
94 | \inqmlmodule Qt3D.Render |
95 | \brief The ObjectPicker class instantiates a component that can |
96 | be used to interact with an Entity by a process known as picking. |
97 | |
98 | For every combination of viewport and camera, picking casts a ray through the scene to |
99 | find entities who's bounding volume intersects the ray. The bounding volume is computed using |
100 | the values in the attribute buffer specified by the boundingVolumePositionAttribute of the |
101 | geometry. |
102 | |
103 | The signals pressed(), released(), clicked(), moved(), entered(), and exited() are |
104 | emitted when the bounding volume defined by the pickAttribute property intersects |
105 | with a ray. |
106 | |
107 | Most signals carry a PickEvent instance. If PickingSettings.pickMode is set to |
108 | PickingSettings.TrianglePicking, the actual type of the pick parameter will be |
109 | PickTriangleEvent. |
110 | |
111 | Pick queries are performed on mouse press and mouse release. |
112 | If drag is enabled, queries also happen on each mouse move while any button is pressed. |
113 | If hover is enabled, queries happen on every mouse move even if no button is pressed. |
114 | |
115 | \sa PickingSettings, Geometry, Attribute, PickEvent, PickTriangleEvent, NoPicking |
116 | |
117 | \note To receive hover events in QtQuick, the hoverEnabled property of Scene3D must also be set. |
118 | |
119 | \note Instances of this component shouldn't be shared, not respecting that |
120 | condition will most likely result in undefined behavior. |
121 | |
122 | \note The camera far plane value affects picking and produces incorrect results due to |
123 | floating-point precision if it is greater than ~100 000. |
124 | */ |
125 | |
126 | /*! |
127 | \qmlsignal Qt3D.Render::ObjectPicker::clicked(PickEvent pick) |
128 | |
129 | This signal is emitted when the bounding volume defined by the pickAttribute |
130 | property intersects with a ray on a mouse click the PickEvent \a pick contains |
131 | details of the event. |
132 | */ |
133 | |
134 | /*! |
135 | \qmlsignal Qt3D.Render::ObjectPicker::pressed(PickEvent pick) |
136 | |
137 | This signal is emitted when the bounding volume defined by the |
138 | pickAttribute property intersects with a ray on a mouse press. Intersection |
139 | information are accessible through the \a pick parameter. |
140 | */ |
141 | |
142 | /*! |
143 | \qmlsignal Qt3D.Render::ObjectPicker::released(PickEvent pick) |
144 | |
145 | This signal is emitted when the bounding volume defined by the |
146 | pickAttribute property intersects with a ray on a mouse release. |
147 | Intersection information are accessible through the \a pick parameter. |
148 | */ |
149 | |
150 | /*! |
151 | \qmlsignal Qt3D.Render::ObjectPicker::clicked(PickEvent pick) |
152 | |
153 | This signal is emitted when the bounding volume defined by the |
154 | pickAttribute property intersects with a ray on a mouse click. Intersection |
155 | information are accessible through the \a pick parameter. |
156 | */ |
157 | |
158 | /*! |
159 | \qmlsignal Qt3D.Render::ObjectPicker::moved(PickEvent pick) |
160 | |
161 | This signal is emitted when the bounding volume defined by the |
162 | pickAttribute property intersects with a ray on a mouse move with a button |
163 | pressed. Intersection information are accessible through the \a pick |
164 | parameter. |
165 | */ |
166 | |
167 | /*! |
168 | \qmlsignal Qt3D.Render::ObjectPicker::entered() |
169 | |
170 | This signal is emitted when the bounding volume defined by the pickAttribute |
171 | property intersects with a ray on the mouse entering the volume. |
172 | */ |
173 | |
174 | /*! |
175 | \qmlsignal Qt3D.Render::ObjectPicker::exited() |
176 | |
177 | This signal is emitted when the bounding volume defined by the pickAttribute |
178 | property intersects with a ray on the ray exiting the volume. |
179 | */ |
180 | |
181 | /*! |
182 | \fn Qt3DRender::QObjectPicker::clicked(Qt3DRender::QPickEvent *pick) |
183 | |
184 | This signal is emitted when the bounding volume defined by the pickAttribute |
185 | property intersects with a ray on a mouse click the QPickEvent \a pick contains |
186 | details of the event. |
187 | */ |
188 | |
189 | /*! |
190 | \fn Qt3DRender::QObjectPicker::entered() |
191 | |
192 | This signal is emitted when the bounding volume defined by the pickAttribute |
193 | property intersects with a ray on the mouse entering the volume. |
194 | */ |
195 | |
196 | /*! |
197 | \fn Qt3DRender::QObjectPicker::exited() |
198 | |
199 | This signal is emitted when the bounding volume defined by the pickAttribute |
200 | property intersects with a ray on the ray exiting the volume. |
201 | */ |
202 | |
203 | /*! |
204 | \fn Qt3DRender::QObjectPicker::moved(Qt3DRender::QPickEvent *pick) |
205 | |
206 | This signal is emitted when the bounding volume defined by the |
207 | pickAttribute property intersects with a ray on a mouse move with a button |
208 | pressed the QPickEvent \a pick contains details of the event. |
209 | */ |
210 | |
211 | /*! |
212 | \fn Qt3DRender::QObjectPicker::pressed(Qt3DRender::QPickEvent *pick) |
213 | |
214 | This signal is emitted when the bounding volume defined by the |
215 | pickAttribute property intersects with a ray on a mouse press the |
216 | QPickEvent \a pick contains details of the event. |
217 | */ |
218 | |
219 | /*! |
220 | \fn Qt3DRender::QObjectPicker::released(Qt3DRender::QPickEvent *pick) |
221 | |
222 | This signal is emitted when the bounding volume defined by the |
223 | pickAttribute property intersects with a ray on a mouse release the |
224 | QPickEvent \a pick contains details of the event. |
225 | */ |
226 | |
227 | QObjectPicker::QObjectPicker(Qt3DCore::QNode *parent) |
228 | : Qt3DCore::QComponent(*new QObjectPickerPrivate(), parent) |
229 | { |
230 | } |
231 | |
232 | /*! \internal */ |
233 | QObjectPicker::~QObjectPicker() |
234 | { |
235 | } |
236 | |
237 | /*! |
238 | * Sets the hoverEnabled Property to \a hoverEnabled |
239 | */ |
240 | void QObjectPicker::setHoverEnabled(bool hoverEnabled) |
241 | { |
242 | Q_D(QObjectPicker); |
243 | if (hoverEnabled != d->m_hoverEnabled) { |
244 | d->m_hoverEnabled = hoverEnabled; |
245 | emit hoverEnabledChanged(hoverEnabled); |
246 | } |
247 | } |
248 | |
249 | /*! |
250 | \qmlproperty bool Qt3D.Render::ObjectPicker::hoverEnabled |
251 | Specifies if hover is enabled |
252 | */ |
253 | /*! |
254 | \property Qt3DRender::QObjectPicker::hoverEnabled |
255 | Specifies if hover is enabled |
256 | */ |
257 | /*! |
258 | * \return true if hover enabled |
259 | */ |
260 | bool QObjectPicker::isHoverEnabled() const |
261 | { |
262 | Q_D(const QObjectPicker); |
263 | return d->m_hoverEnabled; |
264 | } |
265 | |
266 | /*! |
267 | * Sets the dragEnabled Property to \a dragEnabled |
268 | */ |
269 | void QObjectPicker::setDragEnabled(bool dragEnabled) |
270 | { |
271 | Q_D(QObjectPicker); |
272 | if (dragEnabled != d->m_dragEnabled) { |
273 | d->m_dragEnabled = dragEnabled; |
274 | emit dragEnabledChanged(dragEnabled); |
275 | } |
276 | } |
277 | |
278 | /*! |
279 | * Sets the picker's priority to \a priority. This is used when the pick result |
280 | * mode on QPickingSettings is set to QPickingSettings::NearestPriorityPick. |
281 | * Picking results are sorted by highest priority and shortest picking |
282 | * distance. |
283 | * |
284 | * \since 5.13 |
285 | */ |
286 | void QObjectPicker::setPriority(int priority) |
287 | { |
288 | Q_D(QObjectPicker); |
289 | if (priority != d->m_priority) { |
290 | d->m_priority = priority; |
291 | emit priorityChanged(priority); |
292 | } |
293 | } |
294 | |
295 | // TODO Unused remove in Qt6 |
296 | void QObjectPicker::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &) |
297 | { |
298 | } |
299 | |
300 | /*! |
301 | \qmlproperty bool Qt3D.Render::ObjectPicker::dragEnabled |
302 | */ |
303 | /*! |
304 | \property Qt3DRender::QObjectPicker::dragEnabled |
305 | Specifies if drag is enabled |
306 | */ |
307 | /*! |
308 | * \return true if dragging is enabled |
309 | */ |
310 | bool QObjectPicker::isDragEnabled() const |
311 | { |
312 | Q_D(const QObjectPicker); |
313 | return d->m_dragEnabled; |
314 | } |
315 | |
316 | /*! |
317 | \qmlproperty bool Qt3D.Render::ObjectPicker::containsMouse |
318 | Specifies if the object picker currently contains the mouse |
319 | */ |
320 | /*! |
321 | \property Qt3DRender::QObjectPicker::containsMouse |
322 | Specifies if the object picker currently contains the mouse |
323 | */ |
324 | /*! |
325 | * \return true if the object picker currently contains the mouse |
326 | */ |
327 | bool QObjectPicker::containsMouse() const |
328 | { |
329 | Q_D(const QObjectPicker); |
330 | return d->m_containsMouse; |
331 | } |
332 | |
333 | /*! |
334 | \qmlproperty bool Qt3D.Render::ObjectPicker::pressed |
335 | Specifies if the object picker is currently pressed |
336 | */ |
337 | /*! |
338 | \property Qt3DRender::QObjectPicker::pressed |
339 | Specifies if the object picker is currently pressed |
340 | */ |
341 | bool QObjectPicker::isPressed() const |
342 | { |
343 | Q_D(const QObjectPicker); |
344 | return d->m_pressed; |
345 | } |
346 | |
347 | /*! |
348 | \qmlproperty int Qt3D.Render::ObjectPicker::priority |
349 | |
350 | The priority to be used when filtering pick results by priority when |
351 | PickingSettings.pickResultMode is set to PickingSettings.PriorityPick. |
352 | */ |
353 | /*! |
354 | \property Qt3DRender::QObjectPicker::priority |
355 | |
356 | The priority to be used when filtering pick results by priority when |
357 | QPickingSettings::pickResultMode is set to |
358 | QPickingSettings::NearestPriorityPick. |
359 | */ |
360 | int QObjectPicker::priority() const |
361 | { |
362 | Q_D(const QObjectPicker); |
363 | return d->m_priority; |
364 | } |
365 | |
366 | /*! |
367 | \internal |
368 | */ |
369 | void QObjectPickerPrivate::setPressed(bool pressed) |
370 | { |
371 | Q_Q(QObjectPicker); |
372 | if (m_pressed != pressed) { |
373 | const bool blocked = q->blockNotifications(block: true); |
374 | m_pressed = pressed; |
375 | emit q->pressedChanged(pressed); |
376 | q->blockNotifications(block: blocked); |
377 | } |
378 | } |
379 | |
380 | /*! |
381 | \internal |
382 | */ |
383 | void QObjectPickerPrivate::setContainsMouse(bool containsMouse) |
384 | { |
385 | Q_Q(QObjectPicker); |
386 | if (m_containsMouse != containsMouse) { |
387 | const bool blocked = q->blockNotifications(block: true); |
388 | m_containsMouse = containsMouse; |
389 | emit q->containsMouseChanged(containsMouse); |
390 | q->blockNotifications(block: blocked); |
391 | } |
392 | } |
393 | |
394 | void QObjectPickerPrivate::propagateEvent(QPickEvent *event, EventType type) |
395 | { |
396 | if (!m_entities.isEmpty()) { |
397 | Qt3DCore::QEntity *entity = m_entities.first(); |
398 | Qt3DCore::QEntity *parentEntity = nullptr; |
399 | while (entity != nullptr && entity->parentEntity() != nullptr && !event->isAccepted()) { |
400 | parentEntity = entity->parentEntity(); |
401 | const auto components = parentEntity->components(); |
402 | for (Qt3DCore::QComponent *c : components) { |
403 | if (auto objectPicker = qobject_cast<Qt3DRender::QObjectPicker *>(object: c)) { |
404 | QObjectPickerPrivate *objectPickerPrivate = static_cast<QObjectPickerPrivate *>(QObjectPickerPrivate::get(q: objectPicker)); |
405 | switch (type) { |
406 | case Pressed: |
407 | objectPickerPrivate->pressedEvent(event); |
408 | break; |
409 | case Released: |
410 | objectPickerPrivate->releasedEvent(event); |
411 | break; |
412 | case Clicked: |
413 | objectPickerPrivate->clickedEvent(event); |
414 | break; |
415 | case EventType::Moved: |
416 | objectPickerPrivate->movedEvent(event); |
417 | break; |
418 | } |
419 | break; |
420 | } |
421 | } |
422 | entity = parentEntity; |
423 | } |
424 | } |
425 | } |
426 | |
427 | /*! |
428 | \internal |
429 | */ |
430 | void QObjectPickerPrivate::pressedEvent(QPickEvent *event) |
431 | { |
432 | Q_Q(QObjectPicker); |
433 | emit q->pressed(pick: event); |
434 | |
435 | m_acceptedLastPressedEvent = event->isAccepted(); |
436 | if (!m_acceptedLastPressedEvent) { |
437 | // Travel parents to transmit the event |
438 | propagateEvent(event, type: Pressed); |
439 | } else { |
440 | setPressed(true); |
441 | } |
442 | } |
443 | |
444 | /*! |
445 | \internal |
446 | */ |
447 | void QObjectPickerPrivate::clickedEvent(QPickEvent *event) |
448 | { |
449 | Q_Q(QObjectPicker); |
450 | emit q->clicked(pick: event); |
451 | if (!event->isAccepted()) |
452 | propagateEvent(event, type: Clicked); |
453 | } |
454 | |
455 | /*! |
456 | \internal |
457 | */ |
458 | void QObjectPickerPrivate::movedEvent(QPickEvent *event) |
459 | { |
460 | Q_Q(QObjectPicker); |
461 | emit q->moved(pick: event); |
462 | if (!event->isAccepted()) |
463 | propagateEvent(event, type: EventType::Moved); |
464 | } |
465 | |
466 | /*! |
467 | \internal |
468 | */ |
469 | void QObjectPickerPrivate::releasedEvent(QPickEvent *event) |
470 | { |
471 | Q_Q(QObjectPicker); |
472 | if (m_acceptedLastPressedEvent) { |
473 | emit q->released(pick: event); |
474 | setPressed(false); |
475 | } else { |
476 | event->setAccepted(false); |
477 | propagateEvent(event, type: Released); |
478 | } |
479 | } |
480 | |
481 | Qt3DCore::QNodeCreatedChangeBasePtr QObjectPicker::createNodeCreationChange() const |
482 | { |
483 | auto creationChange = Qt3DCore::QNodeCreatedChangePtr<QObjectPickerData>::create(arguments: this); |
484 | auto &data = creationChange->data; |
485 | Q_D(const QObjectPicker); |
486 | data.hoverEnabled = d->m_hoverEnabled; |
487 | data.dragEnabled = d->m_dragEnabled; |
488 | data.priority = d->m_priority; |
489 | return creationChange; |
490 | } |
491 | |
492 | } // Qt3DRender |
493 | |
494 | QT_END_NAMESPACE |
495 | |