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

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