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