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

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