1// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
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 "pickboundingvolumeutils_p.h"
5#include <Qt3DRender/private/geometryrenderer_p.h>
6#include <Qt3DRender/private/framegraphnode_p.h>
7#include <Qt3DRender/private/cameralens_p.h>
8#include <Qt3DRender/private/cameraselectornode_p.h>
9#include <Qt3DRender/private/viewportnode_p.h>
10#include <Qt3DRender/private/rendersurfaceselector_p.h>
11#include <Qt3DRender/private/triangleboundingvolume_p.h>
12#include <Qt3DRender/private/nodemanagers_p.h>
13#include <Qt3DRender/private/sphere_p.h>
14#include <Qt3DRender/private/entity_p.h>
15#include <Qt3DRender/private/trianglesvisitor_p.h>
16#include <Qt3DRender/private/segmentsvisitor_p.h>
17#include <Qt3DRender/private/pointsvisitor_p.h>
18#include <Qt3DRender/private/layer_p.h>
19#include <Qt3DRender/private/layerfilternode_p.h>
20#include <Qt3DRender/private/rendersettings_p.h>
21#include <Qt3DRender/private/filterlayerentityjob_p.h>
22
23#include <vector>
24#include <algorithm>
25#include <functional>
26
27QT_BEGIN_NAMESPACE
28
29namespace Qt3DRender {
30using namespace Qt3DRender::RayCasting;
31
32namespace Render {
33
34namespace PickingUtils {
35
36
37PickConfiguration::PickConfiguration(FrameGraphNode *frameGraphRoot, RenderSettings *renderSettings)
38{
39 ViewportCameraAreaGatherer vcaGatherer;
40 // TO DO: We could cache this and only gather when we know the FrameGraph tree has changed
41 vcaDetails = vcaGatherer.gather(root: frameGraphRoot);
42
43 // If we have no viewport / camera or area, return early
44 if (vcaDetails.empty())
45 return;
46
47 // TO DO:
48 // If we have move or hover move events that someone cares about, we try to avoid expensive computations
49 // by compressing them into a single one
50
51 trianglePickingRequested = (renderSettings->pickMethod() & QPickingSettings::TrianglePicking);
52 edgePickingRequested = (renderSettings->pickMethod() & QPickingSettings::LinePicking);
53 pointPickingRequested = (renderSettings->pickMethod() & QPickingSettings::PointPicking);
54 primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested;
55 frontFaceRequested = renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace;
56 backFaceRequested = renderSettings->faceOrientationPickingMode() != QPickingSettings::FrontFace;
57 pickWorldSpaceTolerance = renderSettings->pickWorldSpaceTolerance();
58}
59
60
61void ViewportCameraAreaGatherer::visit(FrameGraphNode *node)
62{
63 const auto children = node->children();
64 for (Render::FrameGraphNode *n : children)
65 visit(node: n);
66 if (node->childrenIds().empty())
67 m_leaves.push_back(x: node);
68}
69
70ViewportCameraAreaDetails ViewportCameraAreaGatherer::gatherUpViewportCameraAreas(Render::FrameGraphNode *node) const
71{
72 ViewportCameraAreaDetails vca;
73 vca.viewport = QRectF(0., 0., 1., 1.);
74
75 while (node) {
76 if (node->isEnabled()) {
77 switch (node->nodeType()) {
78 case FrameGraphNode::CameraSelector:
79 vca.cameraId = static_cast<const CameraSelector *>(node)->cameraUuid();
80 break;
81 case FrameGraphNode::Viewport: {
82 auto vnode = static_cast<const ViewportNode *>(node);
83 // we want the leaf viewport so if we have a viewport node already don't override it with its parent
84 if (!vca.viewportNodeId)
85 vca.viewportNodeId = vnode->peerId();
86 vca.viewport = ViewportNode::computeViewport(childViewport: vca.viewport, parentViewport: vnode);
87 break;
88 }
89 case FrameGraphNode::Surface: {
90 auto selector = static_cast<const RenderSurfaceSelector *>(node);
91 vca.area = selector->renderTargetSize();
92 vca.surface = selector->surface();
93 break;
94 }
95 case FrameGraphNode::NoPicking: {
96 // Return an empty/invalid ViewportCameraAreaDetails which will
97 // prevent picking in the presence of a NoPicking node
98 return {};
99 }
100 case FrameGraphNode::LayerFilter: {
101 auto fnode = static_cast<const LayerFilterNode *>(node);
102 vca.layersFilters.push_back(t: fnode->peerId());
103 break;
104 }
105 default:
106 break;
107 }
108 }
109 node = node->parent();
110 }
111 return vca;
112}
113
114std::vector<ViewportCameraAreaDetails> ViewportCameraAreaGatherer::gather(FrameGraphNode *root)
115{
116 // Retrieve all leaves
117 visit(node: root);
118 std::vector<ViewportCameraAreaDetails> vcaTriplets;
119 vcaTriplets.reserve(n: m_leaves.size());
120
121 // Find all viewport/camera pairs by traversing from leaf to root
122 for (Render::FrameGraphNode *leaf : m_leaves) {
123 ViewportCameraAreaDetails vcaDetails = gatherUpViewportCameraAreas(node: leaf);
124 if (!m_targetCamera.isNull() && vcaDetails.cameraId != m_targetCamera)
125 continue;
126 if (!vcaDetails.cameraId.isNull() && isUnique(vcaList: vcaTriplets, vca: vcaDetails))
127 vcaTriplets.push_back(x: vcaDetails);
128 }
129 return vcaTriplets;
130}
131
132bool ViewportCameraAreaGatherer::isUnique(const std::vector<ViewportCameraAreaDetails> &vcaList,
133 const ViewportCameraAreaDetails &vca) const
134{
135 for (const ViewportCameraAreaDetails &listItem : vcaList) {
136 if (vca.cameraId == listItem.cameraId &&
137 vca.viewport == listItem.viewport &&
138 vca.surface == listItem.surface &&
139 vca.area == listItem.area &&
140 vca.layersFilters == listItem.layersFilters)
141 return false;
142 }
143 return true;
144}
145
146class TriangleCollisionVisitor : public TrianglesVisitor
147{
148public:
149 HitList hits;
150
151 TriangleCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
152 bool frontFaceRequested, bool backFaceRequested)
153 : TrianglesVisitor(manager), m_root(root), m_ray(ray), m_triangleIndex(0)
154 , m_frontFaceRequested(frontFaceRequested), m_backFaceRequested(backFaceRequested)
155 {
156 m_worldMatrix = *m_root->worldTransform();
157 m_localRay = m_ray;
158 m_localRay.transform(matrix: m_worldMatrix.inverted());
159 }
160
161private:
162 const Entity *m_root;
163 RayCasting::QRay3D m_ray;
164 RayCasting::QRay3D m_localRay;
165 Matrix4x4 m_worldMatrix;
166 uint m_triangleIndex;
167 bool m_frontFaceRequested;
168 bool m_backFaceRequested;
169
170 void visit(uint andx, const Vector3D &a,
171 uint bndx, const Vector3D &b,
172 uint cndx, const Vector3D &c) override;
173 bool intersectsSegmentTriangle(uint andx, const Vector3D &a,
174 uint bndx, const Vector3D &b,
175 uint cndx, const Vector3D &c);
176};
177
178void TriangleCollisionVisitor::visit(uint andx, const Vector3D &a, uint bndx, const Vector3D &b, uint cndx, const Vector3D &c)
179{
180 bool intersected = m_frontFaceRequested && intersectsSegmentTriangle(andx: cndx, a: c, bndx, b, cndx: andx, c: a); // front facing
181 if (!intersected && m_backFaceRequested) {
182 intersected = intersectsSegmentTriangle(andx, a, bndx, b, cndx, c); // back facing
183 }
184
185 m_triangleIndex++;
186}
187
188bool TriangleCollisionVisitor::intersectsSegmentTriangle(uint andx, const Vector3D &a, uint bndx, const Vector3D &b, uint cndx, const Vector3D &c)
189{
190 float t = 0.0f;
191 Vector3D uvw;
192 bool intersected = Render::intersectsSegmentTriangle(ray: m_localRay, a, b, c, uvw, t);
193 if (intersected) {
194 QCollisionQueryResult::Hit queryResult;
195 queryResult.m_type = QCollisionQueryResult::Hit::Triangle;
196 queryResult.m_entityId = m_root->peerId();
197 queryResult.m_primitiveIndex = m_triangleIndex;
198 queryResult.m_vertexIndex[0] = andx;
199 queryResult.m_vertexIndex[1] = bndx;
200 queryResult.m_vertexIndex[2] = cndx;
201 queryResult.m_uvw = uvw;
202 queryResult.m_intersection = m_worldMatrix.map(point: m_localRay.point(t: t * m_localRay.distance()));
203 queryResult.m_distance = m_ray.projectedDistance(point: queryResult.m_intersection);
204 hits.push_back(x: queryResult);
205 }
206 return intersected;
207}
208
209class LineCollisionVisitor : public SegmentsVisitor
210{
211public:
212 HitList hits;
213
214 LineCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
215 float pickWorldSpaceTolerance)
216 : SegmentsVisitor(manager), m_root(root), m_ray(ray)
217 , m_segmentIndex(0), m_pickWorldSpaceTolerance(pickWorldSpaceTolerance)
218 {
219 }
220
221private:
222 const Entity *m_root;
223 RayCasting::QRay3D m_ray;
224 uint m_segmentIndex;
225 float m_pickWorldSpaceTolerance;
226
227 void visit(uint andx, const Vector3D &a,
228 uint bndx, const Vector3D &b) override;
229 bool intersectsSegmentSegment(uint andx, const Vector3D &a,
230 uint bndx, const Vector3D &b);
231 bool rayToLineSegment(const Vector3D& lineStart,const Vector3D& lineEnd,
232 float &distance, Vector3D &intersection) const;
233};
234
235void LineCollisionVisitor::visit(uint andx, const Vector3D &a, uint bndx, const Vector3D &b)
236{
237 const Matrix4x4 &mat = *m_root->worldTransform();
238 const Vector3D tA = mat.map(point: a);
239 const Vector3D tB = mat.map(point: b);
240
241 intersectsSegmentSegment(andx, a: tA, bndx, b: tB);
242
243 m_segmentIndex++;
244}
245
246bool LineCollisionVisitor::intersectsSegmentSegment(uint andx, const Vector3D &a,
247 uint bndx, const Vector3D &b)
248{
249 float distance = 0.f;
250 Vector3D intersection;
251 bool res = rayToLineSegment(lineStart: a, lineEnd: b, distance, intersection);
252 if (res) {
253 QCollisionQueryResult::Hit queryResult;
254 queryResult.m_type = QCollisionQueryResult::Hit::Edge;
255 queryResult.m_entityId = m_root->peerId();
256 queryResult.m_primitiveIndex = m_segmentIndex;
257 queryResult.m_vertexIndex[0] = andx;
258 queryResult.m_vertexIndex[1] = bndx;
259 queryResult.m_intersection = intersection;
260 queryResult.m_distance = m_ray.projectedDistance(point: queryResult.m_intersection);
261 hits.push_back(x: queryResult);
262 return true;
263 }
264 return false;
265}
266
267bool LineCollisionVisitor::rayToLineSegment(const Vector3D& lineStart,const Vector3D& lineEnd,
268 float &distance, Vector3D &intersection) const
269{
270 const float epsilon = 0.00000001f;
271
272 const Vector3D u = m_ray.direction() * m_ray.distance();
273 const Vector3D v = lineEnd - lineStart;
274 const Vector3D w = m_ray.origin() - lineStart;
275 const float a = Vector3D::dotProduct(a: u, b: u);
276 const float b = Vector3D::dotProduct(a: u, b: v);
277 const float c = Vector3D::dotProduct(a: v, b: v);
278 const float d = Vector3D::dotProduct(a: u, b: w);
279 const float e = Vector3D::dotProduct(a: v, b: w);
280 const float D = a * c - b * b;
281 float sc, sN, sD = D;
282 float tc, tN, tD = D;
283
284 if (D < epsilon) {
285 sN = 0.0;
286 sD = 1.0;
287 tN = e;
288 tD = c;
289 } else {
290 sN = (b * e - c * d);
291 tN = (a * e - b * d);
292 if (sN < 0.0) {
293 sN = 0.0;
294 tN = e;
295 tD = c;
296 }
297 }
298
299 if (tN < 0.0) {
300 tN = 0.0;
301 if (-d < 0.0)
302 sN = 0.0;
303 else {
304 sN = -d;
305 sD = a;
306 }
307 } else if (tN > tD) {
308 tN = tD;
309 if ((-d + b) < 0.0)
310 sN = 0;
311 else {
312 sN = (-d + b);
313 sD = a;
314 }
315 }
316
317 sc = (qAbs(t: sN) < epsilon ? 0.0f : sN / sD);
318 tc = (qAbs(t: tN) < epsilon ? 0.0f : tN / tD);
319
320 const Vector3D dP = w + (sc * u) - (tc * v);
321 const float f = dP.length();
322 if (f < m_pickWorldSpaceTolerance) {
323 distance = sc * u.length();
324 intersection = lineStart + v * tc;
325 return true;
326 }
327 return false;
328}
329
330class PointCollisionVisitor : public PointsVisitor
331{
332public:
333 HitList hits;
334
335 PointCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
336 float pickWorldSpaceTolerance)
337 : PointsVisitor(manager), m_root(root), m_ray(ray)
338 , m_pointIndex(0), m_pickWorldSpaceTolerance(pickWorldSpaceTolerance)
339 {
340 }
341
342private:
343 const Entity *m_root;
344 RayCasting::QRay3D m_ray;
345 uint m_pointIndex;
346 float m_pickWorldSpaceTolerance;
347
348 void visit(uint ndx, const Vector3D &p) override;
349
350 double pointToRayDistance(const Vector3D &a, Vector3D &p)
351 {
352 const Vector3D v = a - m_ray.origin();
353 const double t = Vector3D::dotProduct(a: v, b: m_ray.direction());
354 p = m_ray.origin() + t * m_ray.direction();
355 return (p - a).length();
356 }
357};
358
359
360void PointCollisionVisitor::visit(uint ndx, const Vector3D &p)
361{
362 const Matrix4x4 &mat = *m_root->worldTransform();
363 const Vector3D tP = mat.map(point: p);
364 Vector3D intersection;
365
366 float d = pointToRayDistance(a: tP, p&: intersection);
367 if (d < m_pickWorldSpaceTolerance) {
368 QCollisionQueryResult::Hit queryResult;
369 queryResult.m_type = QCollisionQueryResult::Hit::Point;
370 queryResult.m_entityId = m_root->peerId();
371 queryResult.m_primitiveIndex = m_pointIndex;
372 queryResult.m_vertexIndex[0] = ndx;
373 queryResult.m_intersection = intersection;
374 queryResult.m_distance = d;
375 hits.push_back(x: queryResult);
376 }
377
378 m_pointIndex++;
379}
380
381HitList reduceToFirstHit(HitList &result, const HitList &intermediate)
382{
383 if (!intermediate.empty()) {
384 if (result.empty())
385 result.push_back(x: intermediate.front());
386 float closest = result.front().m_distance;
387 for (const auto &v : intermediate) {
388 if (v.m_distance < closest) {
389 result.insert(position: result.begin(), x: v);
390 closest = v.m_distance;
391 }
392 }
393
394 while (result.size() > 1)
395 result.pop_back();
396 }
397 return result;
398}
399
400
401struct HighestPriorityHitReducer
402{
403 // No need to protect this from concurrent access as the table
404 // is read only
405 const QHash<Qt3DCore::QNodeId, int> entityToPriorityTable;
406
407 HitList operator()(HitList &result, const HitList &intermediate)
408 {
409 // Sort by priority first
410 // If we have equal priorities, we then sort by distance
411
412 if (!intermediate.empty()) {
413 if (result.empty())
414 result.push_back(x: intermediate.front());
415 int currentPriority = entityToPriorityTable.value(key: result.front().m_entityId, defaultValue: 0);
416 float closest = result.front().m_distance;
417
418 for (const auto &v : intermediate) {
419 const int newEntryPriority = entityToPriorityTable.value(key: v.m_entityId, defaultValue: 0);
420 if (newEntryPriority > currentPriority) {
421 result.insert(position: result.begin(), x: v);
422 currentPriority = newEntryPriority;
423 closest = v.m_distance;
424 } else if (newEntryPriority == currentPriority) {
425 if (v.m_distance < closest) {
426 result.insert(position: result.begin(), x: v);
427 closest = v.m_distance;
428 currentPriority = newEntryPriority;
429 }
430 }
431 }
432
433 while (result.size() > 1)
434 result.pop_back();
435 }
436 return result;
437 }
438};
439
440HitList reduceToAllHits(HitList &results, const HitList &intermediate)
441{
442 if (!intermediate.empty())
443 results.insert(position: results.end(), first: intermediate.begin(), last: intermediate.end());
444 return results;
445}
446
447AbstractCollisionGathererFunctor::AbstractCollisionGathererFunctor()
448 : m_manager(nullptr)
449{ }
450
451AbstractCollisionGathererFunctor::~AbstractCollisionGathererFunctor()
452{ }
453
454HitList AbstractCollisionGathererFunctor::operator ()(const Entity *entity) const
455{
456 if (m_objectPickersRequired) {
457 HObjectPicker objectPickerHandle = entity->componentHandle<ObjectPicker>();
458
459 // If the Entity which actually received the hit doesn't have
460 // an object picker component, we need to check the parent if it has one ...
461 auto parentEntity = entity;
462 while (objectPickerHandle.isNull() && parentEntity != nullptr) {
463 parentEntity = parentEntity->parent();
464 if (parentEntity != nullptr)
465 objectPickerHandle = parentEntity->componentHandle<ObjectPicker>();
466 }
467
468 ObjectPicker *objectPicker = m_manager->objectPickerManager()->data(handle: objectPickerHandle);
469 if (objectPicker == nullptr || !objectPicker->isEnabled())
470 return {}; // don't bother picking entities that don't
471 // have an object picker, or if it's disabled
472 }
473
474 return pick(entity);
475}
476
477bool AbstractCollisionGathererFunctor::rayHitsEntity(const Entity *entity) const
478{
479 QRayCastingService rayCasting;
480 const QCollisionQueryResult::Hit queryResult = rayCasting.query(ray: m_ray, volume: entity->worldBoundingVolume());
481 return queryResult.m_distance >= 0.f;
482}
483
484void AbstractCollisionGathererFunctor::sortHits(HitList &results)
485{
486 auto compareHitsDistance = [](const HitList::value_type &a,
487 const HitList::value_type &b) {
488 return a.m_distance < b.m_distance;
489 };
490 std::sort(first: results.begin(), last: results.end(), comp: compareHitsDistance);
491}
492
493namespace {
494
495// Workaround to avoid passing *this into the blockMappedReduce calls for the
496// mapFunctor which would cause an SSE alignment error on Windows Also note
497// that a lambda doesn't work since we need the typedef result_type defined to
498// work with QtConcurrent
499struct MapFunctorHolder
500{
501 MapFunctorHolder(const AbstractCollisionGathererFunctor *gatherer)
502 : m_gatherer(gatherer)
503 {}
504
505 // This define is required to work with QtConcurrent
506 typedef HitList result_type;
507 HitList operator ()(const Entity *e) const { return m_gatherer->operator ()(entity: e); }
508
509 const AbstractCollisionGathererFunctor *m_gatherer;
510};
511
512} // anonymous
513
514HitList EntityCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
515 Qt3DRender::QPickingSettings::PickResultMode mode)
516{
517 std::function<HitList (HitList &, const HitList &)> reducerOp;
518 switch (mode) {
519 case QPickingSettings::AllPicks:
520 reducerOp = PickingUtils::reduceToAllHits;
521 break;
522 case QPickingSettings::NearestPriorityPick:
523 reducerOp = HighestPriorityHitReducer{ .entityToPriorityTable: m_entityToPriorityTable };
524 break;
525 case QPickingSettings::NearestPick:
526 reducerOp = PickingUtils::reduceToFirstHit;
527 break;
528 }
529
530 const MapFunctorHolder holder(this);
531#if QT_CONFIG(concurrent)
532 return QtConcurrent::blockingMappedReduced<HitList>(sequence: entities, map: holder, reduce&: reducerOp);
533#else
534 HitList sphereHits;
535 QList<PickingUtils::EntityCollisionGathererFunctor::result_type> results;
536 for (const Entity *entity : entities)
537 sphereHits = reducerOp(sphereHits, holder(entity));
538 return sphereHits;
539#endif
540}
541
542HitList EntityCollisionGathererFunctor::pick(const Entity *entity) const
543{
544 HitList result;
545
546 QRayCastingService rayCasting;
547 const QCollisionQueryResult::Hit queryResult = rayCasting.query(ray: m_ray, volume: entity->worldBoundingVolume());
548 if (queryResult.m_distance >= 0.f)
549 result.push_back(x: queryResult);
550
551 return result;
552}
553
554HitList TriangleCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
555 Qt3DRender::QPickingSettings::PickResultMode mode)
556{
557 std::function<HitList (HitList &, const HitList &)> reducerOp;
558 switch (mode) {
559 case QPickingSettings::AllPicks:
560 reducerOp = PickingUtils::reduceToAllHits;
561 break;
562 case QPickingSettings::NearestPriorityPick:
563 reducerOp = HighestPriorityHitReducer { .entityToPriorityTable: m_entityToPriorityTable };
564 break;
565 case QPickingSettings::NearestPick:
566 reducerOp = PickingUtils::reduceToFirstHit;
567 break;
568 }
569
570 const MapFunctorHolder holder(this);
571#if QT_CONFIG(concurrent)
572 return QtConcurrent::blockingMappedReduced<HitList>(sequence: entities, map: holder, reduce&: reducerOp);
573#else
574 HitList sphereHits;
575 QList<PickingUtils::TriangleCollisionGathererFunctor::result_type> results;
576 for (const Entity *entity : entities)
577 sphereHits = reducerOp(sphereHits, holder(entity));
578 return sphereHits;
579#endif
580}
581
582HitList TriangleCollisionGathererFunctor::pick(const Entity *entity) const
583{
584 HitList result;
585
586 PickingProxy *proxy = entity->renderComponent<PickingProxy>();
587 if (proxy && proxy->isEnabled() && proxy->isValid()) {
588 if (rayHitsEntity(entity)) {
589 TriangleCollisionVisitor visitor(m_manager, entity, m_ray, m_frontFaceRequested, m_backFaceRequested);
590 visitor.apply(proxy, id: entity->peerId());
591 result = visitor.hits;
592
593 sortHits(results&: result);
594 }
595 } else {
596 GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
597 if (!gRenderer || !gRenderer->isEnabled())
598 return result;
599
600 if (rayHitsEntity(entity)) {
601 TriangleCollisionVisitor visitor(m_manager, entity, m_ray, m_frontFaceRequested, m_backFaceRequested);
602 visitor.apply(renderer: gRenderer, id: entity->peerId());
603 result = visitor.hits;
604
605 sortHits(results&: result);
606 }
607 }
608
609 return result;
610}
611
612HitList LineCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
613 Qt3DRender::QPickingSettings::PickResultMode mode)
614{
615 std::function<HitList (HitList &, const HitList &)> reducerOp;
616 switch (mode) {
617 case QPickingSettings::AllPicks:
618 reducerOp = PickingUtils::reduceToAllHits;
619 break;
620 case QPickingSettings::NearestPriorityPick:
621 reducerOp = HighestPriorityHitReducer { .entityToPriorityTable: m_entityToPriorityTable };
622 break;
623 case QPickingSettings::NearestPick:
624 reducerOp = PickingUtils::reduceToFirstHit;
625 break;
626 }
627
628 const MapFunctorHolder holder(this);
629#if QT_CONFIG(concurrent)
630 return QtConcurrent::blockingMappedReduced<HitList>(sequence: entities, map: holder, reduce&: reducerOp);
631#else
632 HitList sphereHits;
633 QList<PickingUtils::LineCollisionGathererFunctor::result_type> results;
634 for (const Entity *entity : entities)
635 sphereHits = reducerOp(sphereHits, holder(entity));
636 return sphereHits;
637#endif
638}
639
640HitList LineCollisionGathererFunctor::pick(const Entity *entity) const
641{
642 HitList result;
643
644 PickingProxy *proxy = entity->renderComponent<PickingProxy>();
645 if (proxy && proxy->isEnabled() && proxy->isValid()) {
646 if (rayHitsEntity(entity)) {
647 LineCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
648 visitor.apply(proxy, id: entity->peerId());
649 result = visitor.hits;
650
651 sortHits(results&: result);
652 }
653 } else {
654 GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
655 if (!gRenderer)
656 return result;
657
658 if (rayHitsEntity(entity)) {
659 LineCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
660 visitor.apply(renderer: gRenderer, id: entity->peerId());
661 result = visitor.hits;
662 sortHits(results&: result);
663 }
664 }
665
666 return result;
667}
668
669HitList PointCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
670 Qt3DRender::QPickingSettings::PickResultMode mode)
671{
672 std::function<HitList (HitList &, const HitList &)> reducerOp;
673 switch (mode) {
674 case QPickingSettings::AllPicks:
675 reducerOp = PickingUtils::reduceToAllHits;
676 break;
677 case QPickingSettings::NearestPriorityPick:
678 reducerOp = HighestPriorityHitReducer { .entityToPriorityTable: m_entityToPriorityTable };
679 break;
680 case QPickingSettings::NearestPick:
681 reducerOp = PickingUtils::reduceToFirstHit;
682 break;
683 }
684
685 const MapFunctorHolder holder(this);
686#if QT_CONFIG(concurrent)
687 return QtConcurrent::blockingMappedReduced<HitList>(sequence: entities, map: holder, reduce&: reducerOp);
688#else
689 HitList sphereHits;
690 QList<PickingUtils::PointCollisionGathererFunctor::result_type> results;
691 for (const Entity *entity : entities)
692 sphereHits = reducerOp(sphereHits, holder(entity));
693 return sphereHits;
694#endif
695}
696
697HitList PointCollisionGathererFunctor::pick(const Entity *entity) const
698{
699 HitList result;
700
701 PickingProxy *proxy = entity->renderComponent<PickingProxy>();
702 if (proxy && proxy->isEnabled() && proxy->isValid() && proxy->primitiveType() != Qt3DCore::QGeometryView::Points) {
703 if (rayHitsEntity(entity)) {
704 PointCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
705 visitor.apply(proxy, id: entity->peerId());
706 result = visitor.hits;
707
708 sortHits(results&: result);
709 }
710 } else {
711 GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
712 if (!gRenderer)
713 return result;
714
715 if (gRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Points)
716 return result;
717
718 if (rayHitsEntity(entity)) {
719 PointCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
720 visitor.apply(renderer: gRenderer, id: entity->peerId());
721 result = visitor.hits;
722 sortHits(results&: result);
723 }
724 }
725
726 return result;
727}
728
729HierarchicalEntityPicker::HierarchicalEntityPicker(const QRay3D &ray, bool requireObjectPicker)
730 : m_ray(ray)
731 , m_objectPickersRequired(requireObjectPicker)
732{
733}
734
735void HierarchicalEntityPicker::setLayerFilterIds(const Qt3DCore::QNodeIdVector &layerFilterIds)
736{
737 m_layerFilterIds = layerFilterIds;
738}
739
740void HierarchicalEntityPicker::setLayerIds(const Qt3DCore::QNodeIdVector &layerIds,
741 QAbstractRayCaster::FilterMode mode)
742{
743 m_layerIds = layerIds;
744 m_layerFilterMode = mode;
745}
746
747bool HierarchicalEntityPicker::collectHits(NodeManagers *manager, Entity *root)
748{
749 m_hits.clear();
750 m_entities.clear();
751 m_entityToPriorityTable.clear();
752
753 QRayCastingService rayCasting;
754 struct EntityData {
755 Entity* entity;
756 bool hasObjectPicker;
757 int priority;
758 };
759 std::vector<EntityData> worklist;
760 worklist.push_back(x: {.entity: root, .hasObjectPicker: !root->componentHandle<ObjectPicker>().isNull(), .priority: 0});
761
762 // Record all entities that satisfy layerFiltering. We can then check against
763 // that to see if a picked Entity also satisfies the layer filtering
764
765 // Note: PickBoundingVolumeJob filters against LayerFilter nodes (FG) whereas
766 // the RayCastingJob filters only against a set of Layers and a filter Mode
767 const bool hasLayerFilters = m_layerFilterIds.size() > 0;
768 const bool hasLayers = m_layerIds.size() > 0;
769 const bool hasLayerFiltering = hasLayerFilters || hasLayers;
770 std::vector<Entity *> layerFilterEntities;
771 FilterLayerEntityJob layerFilterJob;
772 layerFilterJob.setManager(manager);
773
774 if (hasLayerFilters) {
775 // Note: we expect UpdateEntityLayersJob was called beforehand to handle layer recursivness
776 // Filtering against LayerFilters (PickBoundingVolumeJob)
777 if (m_layerFilterIds.size()) {
778 layerFilterJob.setLayerFilters(m_layerFilterIds);
779 layerFilterJob.run();
780 layerFilterEntities = layerFilterJob.filteredEntities();
781 }
782 }
783
784 while (!worklist.empty()) {
785 EntityData current = worklist.back();
786 worklist.pop_back();
787
788 // first pick entry sub-scene-graph
789 QCollisionQueryResult::Hit queryResult =
790 rayCasting.query(ray: m_ray, volume: current.entity->worldBoundingVolumeWithChildren());
791 if (queryResult.m_distance < 0.f)
792 continue;
793
794 // if we get a hit, we check again for this specific entity
795 queryResult = rayCasting.query(ray: m_ray, volume: current.entity->worldBoundingVolume());
796
797 // Check Entity is in selected Layers if we have LayerIds or LayerFilterIds
798 // Note: it's not because a parent doesn't satisfy the layerFiltering that a child might not.
799 // Therefore we need to keep traversing children in all cases
800
801 // Are we filtering against layerIds (RayCastingJob)
802 if (hasLayers) {
803 // QLayerFilter::FilterMode and QAbstractRayCaster::FilterMode are the same
804 layerFilterJob.filterEntityAgainstLayers(entity: current.entity, layerIds: m_layerIds, filterMode: static_cast<QLayerFilter::FilterMode>(m_layerFilterMode));
805 layerFilterEntities = layerFilterJob.filteredEntities();
806 }
807
808 const bool isInLayers = !hasLayerFiltering || Qt3DCore::contains(destination: layerFilterEntities, element: current.entity);
809
810 if (isInLayers && queryResult.m_distance >= 0.f && (current.hasObjectPicker || !m_objectPickersRequired)) {
811 m_entities.push_back(x: current.entity);
812 m_hits.push_back(x: queryResult);
813 // Record entry for entity/priority
814 m_entityToPriorityTable.insert(key: current.entity->peerId(), value: current.priority);
815 }
816
817 // and pick children
818 const auto &childrenHandles = current.entity->childrenHandles();
819 for (const HEntity &handle : childrenHandles) {
820 Entity *child = manager->renderNodesManager()->data(handle);
821 if (child) {
822 ObjectPicker *childPicker = child->renderComponent<ObjectPicker>();
823 worklist.push_back(x: {.entity: child, .hasObjectPicker: current.hasObjectPicker || childPicker,
824 .priority: (childPicker ? childPicker->priority() : current.priority)});
825 }
826 }
827 }
828
829 return !m_hits.empty();
830}
831
832} // PickingUtils
833
834} // Render
835
836} // Qt3DRender
837
838QT_END_NAMESPACE
839

source code of qt3d/src/render/jobs/pickboundingvolumeutils.cpp