1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 Paul Lemire paul.lemire350@gmail.com
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 "pickboundingvolumejob_p.h"
41 #include "qpicktriangleevent.h"
42 #include "qpicklineevent.h"
43 #include "qpickpointevent.h"
44 #include <Qt3DCore/private/qaspectmanager_p.h>
45 #include <Qt3DRender/qobjectpicker.h>
46 #include <Qt3DRender/qviewport.h>
47 #include <Qt3DRender/qgeometryrenderer.h>
48 #include <Qt3DRender/private/qobjectpicker_p.h>
49 #include <Qt3DRender/private/nodemanagers_p.h>
50 #include <Qt3DRender/private/entity_p.h>
51 #include <Qt3DRender/private/objectpicker_p.h>
52 #include <Qt3DRender/private/managers_p.h>
53 #include <Qt3DRender/private/geometryrenderer_p.h>
54 #include <Qt3DRender/private/rendersettings_p.h>
55 #include <Qt3DRender/private/trianglesvisitor_p.h>
56 #include <Qt3DRender/private/job_common_p.h>
57 #include <Qt3DRender/private/qpickevent_p.h>
58 #include <Qt3DRender/private/pickboundingvolumeutils_p.h>
59 
60 #include <QSurface>
61 #include <QWindow>
62 #include <QOffscreenSurface>
63 
64 QT_BEGIN_NAMESPACE
65 
66 namespace Qt3DRender {
67 using namespace Qt3DRender::RayCasting;
68 
69 namespace Render {
70 
71 class PickBoundingVolumeJobPrivate : public Qt3DCore::QAspectJobPrivate
72 {
73 public:
PickBoundingVolumeJobPrivate(PickBoundingVolumeJob * q)74     PickBoundingVolumeJobPrivate(PickBoundingVolumeJob *q) : q_ptr(q) { }
75     ~PickBoundingVolumeJobPrivate() override = default;
76 
77     bool isRequired() const override;
78     void postFrame(Qt3DCore::QAspectManager *manager) override;
79 
80     enum CustomEventType {
81         MouseButtonClick = QEvent::User,
82     };
83 
84     struct EventDetails {
85         Qt3DCore::QNodeId pickerId;
86         int sourceEventType;
87         QPickEventPtr resultingEvent;
88         Qt3DCore::QNodeId viewportNodeId;
89     };
90 
91     QVector<EventDetails> dispatches;
92     PickBoundingVolumeJob *q_ptr;
93     Q_DECLARE_PUBLIC(PickBoundingVolumeJob)
94 };
95 
96 
isRequired() const97 bool PickBoundingVolumeJobPrivate::isRequired() const
98 {
99     Q_Q(const PickBoundingVolumeJob);
100     return !q->m_pendingMouseEvents.isEmpty() || q->m_pickersDirty || q->m_oneEnabledAtLeast;
101 }
102 
postFrame(Qt3DCore::QAspectManager * manager)103 void PickBoundingVolumeJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
104 {
105     using namespace Qt3DCore;
106     QNodeId previousId;
107     QObjectPicker *node = nullptr;
108 
109     for (auto res: qAsConst(dispatches)) {
110         if (previousId != res.pickerId) {
111             node = qobject_cast<QObjectPicker *>(manager->lookupNode(res.pickerId));
112             previousId = res.pickerId;
113         }
114         if (!node)
115             continue;
116 
117         QObjectPickerPrivate *dnode = static_cast<QObjectPickerPrivate *>(QObjectPickerPrivate::get(node));
118 
119         // resolve front end details
120         QPickEvent *pickEvent = res.resultingEvent.data();
121         if (pickEvent) {
122             QPickEventPrivate *dpickEvent = QPickEventPrivate::get(pickEvent);
123             dpickEvent->m_viewport = static_cast<QViewport *>(manager->lookupNode(res.viewportNodeId));
124             dpickEvent->m_entityPtr = static_cast<QEntity *>(manager->lookupNode(dpickEvent->m_entity));
125         }
126 
127         // dispatch event
128         switch (res.sourceEventType) {
129         case QEvent::MouseButtonPress:
130             dnode->pressedEvent(pickEvent);
131             break;
132         case QEvent::MouseButtonRelease:
133             dnode->releasedEvent(pickEvent);
134             break;
135         case MouseButtonClick:
136             dnode->clickedEvent(pickEvent);
137             break;
138         case QEvent::MouseMove:
139             dnode->movedEvent(pickEvent);
140             break;
141         case QEvent::Enter:
142             emit node->entered();
143             dnode->setContainsMouse(true);
144             break;
145         case QEvent::Leave:
146             dnode->setContainsMouse(false);
147             emit node->exited();
148             break;
149         default: Q_UNREACHABLE();
150         }
151     }
152 
153     dispatches.clear();
154 }
155 
156 namespace {
157 
setEventButtonAndModifiers(const QMouseEvent & event,QPickEvent::Buttons & eventButton,int & eventButtons,int & eventModifiers)158 void setEventButtonAndModifiers(const QMouseEvent &event, QPickEvent::Buttons &eventButton, int &eventButtons, int &eventModifiers)
159 {
160     switch (event.button()) {
161     case Qt::LeftButton:
162         eventButton = QPickEvent::LeftButton;
163         break;
164     case Qt::RightButton:
165         eventButton = QPickEvent::RightButton;
166         break;
167     case Qt::MiddleButton:
168         eventButton = QPickEvent::MiddleButton;
169         break;
170     case Qt::BackButton:
171         eventButton = QPickEvent::BackButton;
172         break;
173     default:
174         break;
175     }
176 
177     if (event.buttons() & Qt::LeftButton)
178         eventButtons |= QPickEvent::LeftButton;
179     if (event.buttons() & Qt::RightButton)
180         eventButtons |= QPickEvent::RightButton;
181     if (event.buttons() & Qt::MiddleButton)
182         eventButtons |= QPickEvent::MiddleButton;
183     if (event.buttons() & Qt::BackButton)
184         eventButtons |= QPickEvent::BackButton;
185     if (event.modifiers() & Qt::ShiftModifier)
186         eventModifiers |= QPickEvent::ShiftModifier;
187     if (event.modifiers() & Qt::ControlModifier)
188         eventModifiers |= QPickEvent::ControlModifier;
189     if (event.modifiers() & Qt::AltModifier)
190         eventModifiers |= QPickEvent::AltModifier;
191     if (event.modifiers() & Qt::MetaModifier)
192         eventModifiers |= QPickEvent::MetaModifier;
193     if (event.modifiers() & Qt::KeypadModifier)
194         eventModifiers |= QPickEvent::KeypadModifier;
195 }
196 
197 } // anonymous
198 
PickBoundingVolumeJob()199 PickBoundingVolumeJob::PickBoundingVolumeJob()
200     : AbstractPickingJob(*new PickBoundingVolumeJobPrivate(this))
201     , m_pickersDirty(true)
202 {
203     SET_JOB_RUN_STAT_TYPE(this, JobTypes::PickBoundingVolume, 0)
204 }
205 
setRoot(Entity * root)206 void PickBoundingVolumeJob::setRoot(Entity *root)
207 {
208     m_node = root;
209 }
210 
setMouseEvents(const QList<QPair<QObject *,QMouseEvent>> & pendingEvents)211 void PickBoundingVolumeJob::setMouseEvents(const QList<QPair<QObject*, QMouseEvent>> &pendingEvents)
212 {
213     m_pendingMouseEvents.append(pendingEvents);
214 }
215 
setKeyEvents(const QList<QKeyEvent> & pendingEvents)216 void PickBoundingVolumeJob::setKeyEvents(const QList<QKeyEvent> &pendingEvents)
217 {
218     m_pendingKeyEvents.append(pendingEvents);
219 }
220 
markPickersDirty()221 void PickBoundingVolumeJob::markPickersDirty()
222 {
223     m_pickersDirty = true;
224 }
225 
runHelper()226 bool PickBoundingVolumeJob::runHelper()
227 {
228     // Move to clear the events so that we don't process them several times
229     // if run is called several times
230     const auto mouseEvents = std::move(m_pendingMouseEvents);
231 
232     // If we have no events return early
233     if (mouseEvents.empty())
234         return false;
235 
236     // Quickly look which picker settings we've got
237     if (m_pickersDirty) {
238         m_pickersDirty = false;
239         m_oneEnabledAtLeast = false;
240         m_oneHoverAtLeast = false;
241 
242         const auto activeHandles = m_manager->objectPickerManager()->activeHandles();
243         for (const auto &handle : activeHandles) {
244             auto picker = m_manager->objectPickerManager()->data(handle);
245             m_oneEnabledAtLeast |= picker->isEnabled();
246             m_oneHoverAtLeast |= picker->isHoverEnabled();
247             if (m_oneEnabledAtLeast && m_oneHoverAtLeast)
248                 break;
249         }
250     }
251 
252     // bail out early if no picker is enabled
253     if (!m_oneEnabledAtLeast)
254         return false;
255 
256     bool hasMoveEvent = false;
257     bool hasOtherEvent = false;
258     // Quickly look which types of events we've got
259     for (const auto &event : mouseEvents) {
260         const bool isMove = (event.second.type() == QEvent::MouseMove);
261         hasMoveEvent |= isMove;
262         hasOtherEvent |= !isMove;
263     }
264 
265     // In the case we have a move event, find if we actually have
266     // an object picker that cares about these
267     if (!hasOtherEvent) {
268         // Retrieve the last used object picker
269         ObjectPicker *lastCurrentPicker = m_manager->objectPickerManager()->data(m_currentPicker);
270 
271         // The only way to set lastCurrentPicker is to click
272         // so we can return since if we're there it means we
273         // have only move events. But keep on if hover support
274         // is needed
275         if (lastCurrentPicker == nullptr && !m_oneHoverAtLeast)
276             return false;
277 
278         const bool caresAboutMove = (hasMoveEvent &&
279                                       (m_oneHoverAtLeast ||
280                                         (lastCurrentPicker && lastCurrentPicker->isDragEnabled())));
281         // Early return if the current object picker doesn't care about move events
282         if (!caresAboutMove)
283             return false;
284     }
285 
286     PickingUtils::ViewportCameraAreaGatherer vcaGatherer;
287     // TO DO: We could cache this and only gather when we know the FrameGraph tree has changed
288     const QVector<PickingUtils::ViewportCameraAreaDetails> vcaDetails = vcaGatherer.gather(m_frameGraphRoot);
289 
290     // If we have no viewport / camera or area, return early
291     if (vcaDetails.empty())
292         return false;
293 
294     // TO DO:
295     // If we have move or hover move events that someone cares about, we try to avoid expensive computations
296     // by compressing them into a single one
297 
298     const bool trianglePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::TrianglePicking);
299     const bool edgePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::LinePicking);
300     const bool pointPickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::PointPicking);
301     const bool primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested;
302     const bool frontFaceRequested =
303             m_renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace;
304     const bool backFaceRequested =
305             m_renderSettings->faceOrientationPickingMode() != QPickingSettings::FrontFace;
306     const float pickWorldSpaceTolerance = m_renderSettings->pickWorldSpaceTolerance();
307 
308     // For each mouse event
309     for (const auto &event : mouseEvents) {
310         m_hoveredPickersToClear = m_hoveredPickers;
311 
312         QPickEvent::Buttons eventButton = QPickEvent::NoButton;
313         int eventButtons = 0;
314         int eventModifiers = QPickEvent::NoModifier;
315 
316         setEventButtonAndModifiers(event.second, eventButton, eventButtons, eventModifiers);
317 
318         // For each Viewport / Camera and Area entry
319         for (const PickingUtils::ViewportCameraAreaDetails &vca : vcaDetails) {
320             PickingUtils::HitList sphereHits;
321             QRay3D ray = rayForViewportAndCamera(vca, event.first, event.second.pos());
322             if (!ray.isValid()) {
323                 // An invalid rays is when we've lost our surface or the mouse
324                 // has moved out of the viewport In case of a button released
325                 // outside of the viewport, we still want to notify the
326                 // lastCurrent entity about this.
327                 dispatchPickEvents(event.second, PickingUtils::HitList(), eventButton, eventButtons, eventModifiers, m_renderSettings->pickResultMode(),
328                                    vca.viewportNodeId);
329                 continue;
330             }
331 
332             PickingUtils::HierarchicalEntityPicker entityPicker(ray);
333             entityPicker.setLayerFilterIds(vca.layersFilters);
334             if (entityPicker.collectHits(m_manager, m_node)) {
335                 if (trianglePickingRequested) {
336                     PickingUtils::TriangleCollisionGathererFunctor gathererFunctor;
337                     gathererFunctor.m_frontFaceRequested = frontFaceRequested;
338                     gathererFunctor.m_backFaceRequested = backFaceRequested;
339                     gathererFunctor.m_manager = m_manager;
340                     gathererFunctor.m_ray = ray;
341                     gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable();
342                     sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode());
343                 }
344                 if (edgePickingRequested) {
345                     PickingUtils::LineCollisionGathererFunctor gathererFunctor;
346                     gathererFunctor.m_manager = m_manager;
347                     gathererFunctor.m_ray = ray;
348                     gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance;
349                     gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable();
350                     sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode());
351                     PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
352                 }
353                 if (pointPickingRequested) {
354                     PickingUtils::PointCollisionGathererFunctor gathererFunctor;
355                     gathererFunctor.m_manager = m_manager;
356                     gathererFunctor.m_ray = ray;
357                     gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance;
358                     gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable();
359                     sphereHits << gathererFunctor.computeHits(entityPicker.entities(), m_renderSettings->pickResultMode());
360                     PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
361                 }
362                 if (!primitivePickingRequested) {
363                     sphereHits << entityPicker.hits();
364                     PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
365                     if (m_renderSettings->pickResultMode() != QPickingSettings::AllPicks)
366                         sphereHits = { sphereHits.front() };
367                 }
368             }
369 
370             // Dispatch events based on hit results
371             dispatchPickEvents(event.second, sphereHits, eventButton, eventButtons, eventModifiers, m_renderSettings->pickResultMode(),
372                                vca.viewportNodeId);
373         }
374     }
375 
376     // Clear Hovered elements that needs to be cleared
377     // Send exit event to object pickers on which we
378     // had set the hovered flag for a previous frame
379     // and that aren't being hovered any longer
380     clearPreviouslyHoveredPickers();
381     return true;
382 }
383 
dispatchPickEvents(const QMouseEvent & event,const PickingUtils::HitList & sphereHits,QPickEvent::Buttons eventButton,int eventButtons,int eventModifiers,bool allHitsRequested,Qt3DCore::QNodeId viewportNodeId)384 void PickBoundingVolumeJob::dispatchPickEvents(const QMouseEvent &event,
385                                                const PickingUtils::HitList &sphereHits,
386                                                QPickEvent::Buttons eventButton,
387                                                int eventButtons,
388                                                int eventModifiers,
389                                                bool allHitsRequested,
390                                                Qt3DCore::QNodeId viewportNodeId)
391 {
392     Q_D(PickBoundingVolumeJob);
393 
394     ObjectPicker *lastCurrentPicker = m_manager->objectPickerManager()->data(m_currentPicker);
395     // If we have hits
396     if (!sphereHits.isEmpty()) {
397         // Note: how can we control that we want the first/last/all elements along the ray to be picked
398 
399         // How do we differentiate betwnee an Entity with no GeometryRenderer and one with one, both having
400         // an ObjectPicker component when it comes
401 
402         for (const QCollisionQueryResult::Hit &hit : qAsConst(sphereHits)) {
403             Entity *entity = m_manager->renderNodesManager()->lookupResource(hit.m_entityId);
404             HObjectPicker objectPickerHandle = entity->componentHandle<ObjectPicker>();
405 
406             // If the Entity which actually received the hit doesn't have
407             // an object picker component, we need to check the parent if it has one ...
408             while (objectPickerHandle.isNull() && entity != nullptr) {
409                 entity = entity->parent();
410                 if (entity != nullptr)
411                     objectPickerHandle = entity->componentHandle<ObjectPicker>();
412             }
413 
414             ObjectPicker *objectPicker = m_manager->objectPickerManager()->data(objectPickerHandle);
415             if (objectPicker != nullptr && objectPicker->isEnabled()) {
416 
417                 if (lastCurrentPicker && !allHitsRequested) {
418                     // if there is a current picker, it will "grab" all events until released.
419                     // Clients should test that entity is what they expect (or only use
420                     // world coordinates)
421                     objectPicker = lastCurrentPicker;
422                 }
423 
424                 // Send the corresponding event
425                 Vector3D localIntersection = hit.m_intersection;
426                 if (entity && entity->worldTransform())
427                     localIntersection = entity->worldTransform()->inverted() * hit.m_intersection;
428 
429                 QPickEventPtr pickEvent;
430                 switch (hit.m_type) {
431                 case QCollisionQueryResult::Hit::Triangle:
432                     pickEvent.reset(new QPickTriangleEvent(event.localPos(),
433                                                            convertToQVector3D(hit.m_intersection),
434                                                            convertToQVector3D(localIntersection),
435                                                            hit.m_distance,
436                                                            hit.m_primitiveIndex,
437                                                            hit.m_vertexIndex[0],
438                                                            hit.m_vertexIndex[1],
439                                                            hit.m_vertexIndex[2],
440                                                            eventButton, eventButtons,
441                                                            eventModifiers,
442                                                            convertToQVector3D(hit.m_uvw)));
443                     break;
444                 case QCollisionQueryResult::Hit::Edge:
445                     pickEvent.reset(new QPickLineEvent(event.localPos(),
446                                                        convertToQVector3D(hit.m_intersection),
447                                                        convertToQVector3D(localIntersection),
448                                                        hit.m_distance,
449                                                        hit.m_primitiveIndex,
450                                                        hit.m_vertexIndex[0], hit.m_vertexIndex[1],
451                                                        eventButton, eventButtons, eventModifiers));
452                     break;
453                 case QCollisionQueryResult::Hit::Point:
454                     pickEvent.reset(new QPickPointEvent(event.localPos(),
455                                                         convertToQVector3D(hit.m_intersection),
456                                                         convertToQVector3D(localIntersection),
457                                                         hit.m_distance,
458                                                         hit.m_vertexIndex[0],
459                                                         eventButton, eventButtons, eventModifiers));
460                     break;
461                 case QCollisionQueryResult::Hit::Entity:
462                     pickEvent.reset(new QPickEvent(event.localPos(),
463                                                    convertToQVector3D(hit.m_intersection),
464                                                    convertToQVector3D(localIntersection),
465                                                    hit.m_distance,
466                                                    eventButton, eventButtons, eventModifiers));
467                     break;
468                 default:
469                     Q_UNREACHABLE();
470                 }
471                 Qt3DRender::QPickEventPrivate::get(pickEvent.data())->m_entity = hit.m_entityId;
472                 switch (event.type()) {
473                 case QEvent::MouseButtonPress: {
474                     // Store pressed object handle
475                     m_currentPicker = objectPickerHandle;
476                     m_currentViewport = viewportNodeId;
477                     // Send pressed event to m_currentPicker
478                     d->dispatches.push_back({objectPicker->peerId(), event.type(), pickEvent, viewportNodeId});
479                     objectPicker->setPressed(true);
480                     break;
481                 }
482 
483                 case QEvent::MouseButtonRelease: {
484                     // Only send the release event if it was pressed
485                     if (objectPicker->isPressed()) {
486                         d->dispatches.push_back({objectPicker->peerId(), event.type(), pickEvent, viewportNodeId});
487                         objectPicker->setPressed(false);
488                     }
489                     if (lastCurrentPicker != nullptr && m_currentPicker == objectPickerHandle) {
490                         d->dispatches.push_back({objectPicker->peerId(),
491                                                  PickBoundingVolumeJobPrivate::MouseButtonClick,
492                                                  pickEvent, viewportNodeId});
493                         m_currentPicker = HObjectPicker();
494                         m_currentViewport = {};
495                     }
496                     break;
497                 }
498 #if QT_CONFIG(gestures)
499                 case QEvent::Gesture: {
500                     d->dispatches.push_back({objectPicker->peerId(),
501                                              PickBoundingVolumeJobPrivate::MouseButtonClick,
502                                              pickEvent, viewportNodeId});
503                     break;
504                 }
505 #endif
506                 case QEvent::MouseMove: {
507                     if ((objectPicker->isPressed() || objectPicker->isHoverEnabled()) && objectPicker->isDragEnabled())
508                         d->dispatches.push_back({objectPicker->peerId(), event.type(), pickEvent, viewportNodeId});
509                     Q_FALLTHROUGH(); // fallthrough
510                 }
511                 case QEvent::HoverMove: {
512                     if (!m_hoveredPickers.contains(objectPickerHandle)) {
513                         if (objectPicker->isHoverEnabled()) {
514                             // Send entered event to objectPicker
515                             d->dispatches.push_back({objectPicker->peerId(), QEvent::Enter, pickEvent, viewportNodeId});
516                             // and save it in the hoveredPickers
517                             m_hoveredPickers.push_back(objectPickerHandle);
518                         }
519                     }
520                     break;
521                 }
522 
523                 default:
524                     break;
525                 }
526             }
527 
528             // The ObjectPicker was hit -> it is still being hovered
529             m_hoveredPickersToClear.removeAll(objectPickerHandle);
530 
531             lastCurrentPicker = m_manager->objectPickerManager()->data(m_currentPicker);
532         }
533 
534         // Otherwise no hits
535     } else {
536         switch (event.type()) {
537         case QEvent::MouseButtonRelease: {
538             // Send release event to m_currentPicker
539             if (lastCurrentPicker != nullptr && m_currentViewport == viewportNodeId) {
540                 m_currentPicker = HObjectPicker();
541                 m_currentViewport = {};
542                 QPickEventPtr pickEvent(new QPickEvent);
543                 lastCurrentPicker->setPressed(false);
544                 d->dispatches.push_back({lastCurrentPicker->peerId(), event.type(), pickEvent, viewportNodeId});
545             }
546             break;
547         }
548         default:
549             break;
550         }
551     }
552 }
553 
clearPreviouslyHoveredPickers()554 void PickBoundingVolumeJob::clearPreviouslyHoveredPickers()
555 {
556     Q_D(PickBoundingVolumeJob);
557 
558     for (const HObjectPicker &pickHandle : qAsConst(m_hoveredPickersToClear)) {
559         ObjectPicker *pick = m_manager->objectPickerManager()->data(pickHandle);
560         if (pick)
561             d->dispatches.push_back({pick->peerId(), QEvent::Leave, {}, {}});
562         m_hoveredPickers.removeAll(pickHandle);
563     }
564 
565     m_hoveredPickersToClear.clear();
566 }
567 
568 } // namespace Render
569 
570 } // namespace Qt3DRender
571 
572 QT_END_NAMESPACE
573