1// Copyright (C) 2018 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 "raycastingjob_p.h"
5#include <Qt3DCore/private/qaspectmanager_p.h>
6#include <Qt3DRender/qgeometryrenderer.h>
7#include <Qt3DRender/private/entity_p.h>
8#include <Qt3DRender/private/geometryrenderer_p.h>
9#include <Qt3DRender/private/job_common_p.h>
10#include <Qt3DRender/private/managers_p.h>
11#include <Qt3DRender/private/nodemanagers_p.h>
12#include <Qt3DRender/private/pickboundingvolumeutils_p.h>
13#include <Qt3DRender/private/qray3d_p.h>
14#include <Qt3DRender/private/sphere_p.h>
15#include <Qt3DRender/private/rendersettings_p.h>
16#include <Qt3DRender/private/trianglesvisitor_p.h>
17#include <Qt3DRender/private/entityvisitor_p.h>
18#include <Qt3DRender/private/qabstractraycaster_p.h>
19
20QT_BEGIN_NAMESPACE
21
22using namespace Qt3DRender;
23using namespace Qt3DRender::RayCasting;
24using namespace Render;
25
26namespace {
27
28class EntityCasterGatherer : public EntityVisitor
29{
30public:
31 using EntityCasterList = QList<QPair<Entity *, RayCaster *>>;
32 EntityCasterList m_result;
33 RayCaster *m_trigger;
34
35 explicit EntityCasterGatherer(NodeManagers *manager, RayCaster *trigger = nullptr)
36 : EntityVisitor(manager), m_trigger(trigger) {
37 setPruneDisabled(true);
38 }
39
40 Operation visit(Entity *entity) override {
41 const std::vector<RayCaster *> &components = entity->renderComponents<RayCaster>();
42 for (const auto c: components) {
43 if ((m_trigger == nullptr && c->isEnabled()) || (m_trigger != nullptr && m_trigger == c))
44 m_result.push_back(t: qMakePair(value1&: entity, value2: c));
45 }
46
47 return Continue;
48 }
49};
50
51} // anonymous
52
53
54class Qt3DRender::Render::RayCastingJobPrivate : public Qt3DCore::QAspectJobPrivate
55{
56public:
57 RayCastingJobPrivate(RayCastingJob *q) : q_ptr(q) { }
58 ~RayCastingJobPrivate() override { Q_ASSERT(dispatches.isEmpty()); }
59
60 bool isRequired() const override;
61 void postFrame(Qt3DCore::QAspectManager *manager) override;
62
63 QList<QPair<RayCaster *, QAbstractRayCaster::Hits>> dispatches;
64
65 RayCastingJob *q_ptr;
66 Q_DECLARE_PUBLIC(RayCastingJob)
67};
68
69
70bool RayCastingJobPrivate::isRequired() const
71{
72 Q_Q(const RayCastingJob);
73 return q->m_castersDirty || q->m_oneEnabledAtLeast;
74}
75
76void RayCastingJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
77{
78 for (auto res: std::as_const(t&: dispatches)) {
79 QAbstractRayCaster *node = qobject_cast<QAbstractRayCaster *>(object: manager->lookupNode(id: res.first->peerId()));
80 if (!node)
81 continue;
82
83 QAbstractRayCasterPrivate *d = QAbstractRayCasterPrivate::get(obj: node);
84 d->dispatchHits(hits: res.second);
85
86 if (node->runMode() == QAbstractRayCaster::SingleShot) {
87 node->setEnabled(false);
88 res.first->setEnabled(false);
89 }
90 }
91
92 dispatches.clear();
93}
94
95
96RayCastingJob::RayCastingJob()
97 : AbstractPickingJob(*new RayCastingJobPrivate(this))
98 , m_castersDirty(true)
99{
100 SET_JOB_RUN_STAT_TYPE(this, JobTypes::RayCasting, 0)
101}
102
103void RayCastingJob::markCastersDirty()
104{
105 m_castersDirty = true;
106}
107
108bool RayCastingJob::runHelper()
109{
110 // Quickly look which caster settings we've got
111 // NOTE: should not really cached, we're tracking the state of
112 // RayCaster components but not of the parent entities
113 if (m_castersDirty) {
114 m_castersDirty = false;
115 m_oneEnabledAtLeast = false;
116
117 const auto activeHandles = m_manager->rayCasterManager()->activeHandles();
118 for (const auto &handle : activeHandles) {
119 const auto caster = m_manager->rayCasterManager()->data(handle);
120 m_oneEnabledAtLeast |= caster->isEnabled();
121 if (m_oneEnabledAtLeast)
122 break;
123 }
124 }
125
126 // bail out early if no caster is enabled
127 if (!m_oneEnabledAtLeast)
128 return false;
129
130 EntityCasterGatherer gatherer(m_manager);
131 gatherer.apply(root: m_node);
132 const EntityCasterGatherer::EntityCasterList &entities = gatherer.m_result;
133 return pick(entities);
134}
135
136bool RayCastingJob::pick(const QList<QPair<Entity *, RayCaster *>> &entities)
137{
138 const PickingUtils::PickConfiguration pickConfiguration(m_frameGraphRoot, m_renderSettings);
139 if (pickConfiguration.vcaDetails.empty())
140 return false;
141
142 const float sceneRayLength = m_node->worldBoundingVolumeWithChildren()->radius() * 3.f;
143
144 for (const EntityCasterGatherer::EntityCasterList::value_type &pair: entities) {
145 std::vector<QRay3D> rays;
146
147 switch (pair.second->type()) {
148 case QAbstractRayCasterPrivate::WorldSpaceRayCaster:
149 rays.emplace_back(args: Vector3D(pair.second->origin()),
150 args: Vector3D(pair.second->direction()),
151 args: pair.second->length() > 0.f ? pair.second->length() : sceneRayLength);
152 rays.back().transform(matrix: *pair.first->worldTransform());
153 break;
154 case QAbstractRayCasterPrivate::ScreenScapeRayCaster:
155 for (const PickingUtils::ViewportCameraAreaDetails &vca : pickConfiguration.vcaDetails) {
156 const auto ray = rayForViewportAndCamera(vca, eventSource: nullptr, pos: pair.second->position());
157 if (ray.isValid())
158 rays.push_back(x: ray);
159 }
160 break;
161 default:
162 Q_UNREACHABLE();
163 }
164
165 for (const QRay3D &ray: std::as_const(t&: rays)) {
166 PickingUtils::HitList sphereHits;
167 PickingUtils::HierarchicalEntityPicker entityPicker(ray, false);
168 entityPicker.setLayerIds(layerIds: pair.second->layerIds(), mode: pair.second->filterMode());
169 if (entityPicker.collectHits(manager: m_manager, root: m_node)) {
170 if (pickConfiguration.trianglePickingRequested) {
171 PickingUtils::TriangleCollisionGathererFunctor gathererFunctor;
172 gathererFunctor.m_frontFaceRequested = pickConfiguration.frontFaceRequested;
173 gathererFunctor.m_backFaceRequested = pickConfiguration.backFaceRequested;
174 gathererFunctor.m_manager = m_manager;
175 gathererFunctor.m_ray = ray;
176 gathererFunctor.m_objectPickersRequired = false;
177 PickingUtils::HitList hits = gathererFunctor.computeHits(entities: entityPicker.entities(), mode: QPickingSettings::AllPicks);
178 Qt3DCore::moveAtEnd(destination&: sphereHits, source: std::move(hits));
179 }
180 if (pickConfiguration.edgePickingRequested) {
181 PickingUtils::LineCollisionGathererFunctor gathererFunctor;
182 gathererFunctor.m_manager = m_manager;
183 gathererFunctor.m_ray = ray;
184 gathererFunctor.m_pickWorldSpaceTolerance = pickConfiguration.pickWorldSpaceTolerance;
185 gathererFunctor.m_objectPickersRequired = false;
186 PickingUtils::HitList hits = gathererFunctor.computeHits(entities: entityPicker.entities(), mode: QPickingSettings::AllPicks);
187 Qt3DCore::moveAtEnd(destination&: sphereHits, source: std::move(hits));
188 PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits);
189 }
190 if (pickConfiguration.pointPickingRequested) {
191 PickingUtils::PointCollisionGathererFunctor gathererFunctor;
192 gathererFunctor.m_manager = m_manager;
193 gathererFunctor.m_ray = ray;
194 gathererFunctor.m_pickWorldSpaceTolerance = pickConfiguration.pickWorldSpaceTolerance;
195 gathererFunctor.m_objectPickersRequired = false;
196 PickingUtils::HitList hits = gathererFunctor.computeHits(entities: entityPicker.entities(), mode: QPickingSettings::AllPicks);
197 Qt3DCore::moveAtEnd(destination&: sphereHits, source: std::move(hits));
198 PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits);
199 }
200 if (!pickConfiguration.primitivePickingRequested) {
201 PickingUtils::HitList hits = entityPicker.hits();
202 Qt3DCore::moveAtEnd(destination&: sphereHits, source: std::move(hits));
203 PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits);
204 }
205 }
206
207 dispatchHits(rayCaster: pair.second, sphereHits);
208 }
209 }
210
211 return true;
212}
213
214QAbstractRayCaster::Hits RayCastingJob::pick(Qt3DRender::QAbstractRayCaster *rayCaster)
215{
216 const PickingUtils::PickConfiguration pickConfiguration(m_frameGraphRoot, m_renderSettings);
217 if (pickConfiguration.vcaDetails.empty())
218 return {};
219
220 auto backendRayCaster = m_manager->rayCasterManager()->lookupResource(id: rayCaster->id());
221 if (!backendRayCaster)
222 return {};
223
224 backendRayCaster->syncFromFrontEnd(frontEnd: rayCaster, firstTime: false);
225
226 EntityCasterGatherer gatherer(m_manager, backendRayCaster);
227 gatherer.apply(root: m_node);
228 const EntityCasterGatherer::EntityCasterList &entities = gatherer.m_result;
229
230 if (!pick(entities))
231 return {};
232
233 Q_D(RayCastingJob);
234 QAbstractRayCaster::Hits res;
235 for (const auto &hit: d->dispatches) {
236 if (hit.first->peerId() == rayCaster->id()) {
237 res = hit.second;
238 break;
239 }
240 }
241 d->dispatches.clear();
242 return res;
243}
244
245void RayCastingJob::dispatchHits(RayCaster *rayCaster, const PickingUtils::HitList &sphereHits)
246{
247 QAbstractRayCaster::Hits hits;
248 for (const PickingUtils::HitList::value_type &sphereHit: sphereHits) {
249 Entity *entity = m_manager->renderNodesManager()->lookupResource(id: sphereHit.m_entityId);
250 Vector3D localIntersection = sphereHit.m_intersection;
251 if (entity && entity->worldTransform())
252 localIntersection = entity->worldTransform()->inverted().map(point: localIntersection);
253
254 QRayCasterHit::HitType hitType = QRayCasterHit::EntityHit;
255 switch (sphereHit.m_type) {
256 case RayCasting::QCollisionQueryResult::Hit::Entity:
257 break;
258 case RayCasting::QCollisionQueryResult::Hit::Triangle:
259 hitType = QRayCasterHit::TriangleHit;
260 break;
261 case RayCasting::QCollisionQueryResult::Hit::Edge:
262 hitType = QRayCasterHit::LineHit;
263 break;
264 case RayCasting::QCollisionQueryResult::Hit::Point:
265 hitType = QRayCasterHit::PointHit;
266 break;
267 default: Q_UNREACHABLE();
268 }
269
270 hits << QRayCasterHit{
271 hitType,
272 sphereHit.m_entityId,
273 sphereHit.m_distance,
274 convertToQVector3D(v: localIntersection),
275 convertToQVector3D(v: sphereHit.m_intersection),
276 sphereHit.m_primitiveIndex,
277 sphereHit.m_vertexIndex[0],
278 sphereHit.m_vertexIndex[1],
279 sphereHit.m_vertexIndex[2]
280 };
281 }
282
283 Q_D(RayCastingJob);
284 d->dispatches.push_back(t: {rayCaster, hits});
285}
286
287QT_END_NAMESPACE
288

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