1// Copyright (C) 2015 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 "qraycastingservice_p.h"
5
6#include <Qt3DRender/private/qray3d_p.h>
7#include <Qt3DRender/private/sphere_p.h>
8#include <Qt3DRender/private/qboundingvolumeprovider_p.h>
9
10#include <QtConcurrent>
11
12#include "math.h"
13
14QT_BEGIN_NAMESPACE
15
16using namespace Qt3DCore;
17
18namespace Qt3DRender {
19namespace RayCasting {
20
21namespace {
22
23struct Hit
24{
25 Hit()
26 : intersects(false)
27 , distance(-1.0f)
28 {}
29
30 bool intersects;
31 float distance;
32 Qt3DCore::QNodeId id;
33 Vector3D intersection;
34 Vector3D uvw;
35};
36
37bool compareHitsDistance(const Hit &a, const Hit &b)
38{
39 return a.distance < b.distance;
40}
41
42Hit volumeRayIntersection(const QBoundingVolume *volume, const QRay3D &ray)
43{
44 Hit hit;
45 if ((hit.intersects = volume->intersects(ray, q: &hit.intersection, uvw: &hit.uvw))) {
46 hit.distance = ray.projectedDistance(point: hit.intersection);
47 hit.id = volume->id();
48 }
49 return hit;
50}
51
52Hit reduceToFirstHit(Hit &result, const Hit &intermediate)
53{
54 if (intermediate.intersects) {
55 if (result.distance == -1.0f ||
56 (intermediate.distance >= 0.0f &&
57 intermediate.distance < result.distance))
58 result = intermediate;
59 }
60 return result;
61}
62
63// Unordered
64QList<Hit> reduceToAllHits(QList<Hit> &results, const Hit &intermediate)
65{
66 if (intermediate.intersects)
67 results.push_back(t: intermediate);
68 return results;
69}
70
71struct CollisionGathererFunctor
72{
73 QRay3D ray;
74
75 typedef Hit result_type;
76
77 Hit operator ()(const QBoundingVolume *volume) const
78 {
79 return volumeRayIntersection(volume, ray);
80 }
81};
82
83} // anonymous
84
85
86QCollisionQueryResult QRayCastingServicePrivate::collides(const QRay3D &ray, QBoundingVolumeProvider *provider,
87 QAbstractCollisionQueryService::QueryMode mode, const QQueryHandle &handle)
88{
89 Q_Q(QRayCastingService);
90
91 const QList<QBoundingVolume *> volumes(provider->boundingVolumes());
92 QCollisionQueryResult result;
93 q->setResultHandle(result, handle);
94
95 CollisionGathererFunctor gathererFunctor;
96 gathererFunctor.ray = ray;
97
98 if (mode == QAbstractCollisionQueryService::FirstHit) {
99#if QT_CONFIG(concurrent)
100 Hit firstHit = QtConcurrent::blockingMappedReduced<Hit>(sequence: volumes, map&: gathererFunctor, reduce&: reduceToFirstHit);
101#else
102 Hit firstHit;
103 for (const QBoundingVolume *volume : volumes)
104 firstHit = reduceToFirstHit(firstHit, gathererFunctor(volume));
105#endif
106 if (firstHit.intersects)
107 q->addEntityHit(result, entity: firstHit.id, intersection: firstHit.intersection, distance: firstHit.distance, uvw: firstHit.uvw);
108 } else {
109#if QT_CONFIG(concurrent)
110 QList<Hit> hits = QtConcurrent::blockingMappedReduced<QList<Hit>>(sequence: volumes, map&: gathererFunctor, reduce&: reduceToAllHits);
111#else
112 QList<Hit> hits;
113 for (const QBoundingVolume *volume : volumes)
114 hits = reduceToAllHits(hits, gathererFunctor(volume));
115#endif
116 std::sort(first: hits.begin(), last: hits.end(), comp: compareHitsDistance);
117 for (const Hit &hit : std::as_const(t&: hits))
118 q->addEntityHit(result, entity: hit.id, intersection: hit.intersection, distance: hit.distance, uvw: hit.uvw);
119 }
120
121 return result;
122}
123
124QCollisionQueryResult::Hit QRayCastingServicePrivate::collides(const QRay3D &ray, const QBoundingVolume *volume)
125{
126 QCollisionQueryResult::Hit result;
127 Hit hit = volumeRayIntersection(volume, ray);
128 if (hit.intersects)
129 {
130 result.m_distance = hit.distance;
131 result.m_entityId = hit.id;
132 result.m_intersection = hit.intersection;
133 result.m_uvw = hit.uvw;
134 }
135 return result;
136}
137
138QRayCastingServicePrivate::QRayCastingServicePrivate(const QString &description)
139 : QAbstractCollisionQueryServicePrivate(description)
140 , m_handlesCount(0)
141{
142}
143
144QRayCastingService::QRayCastingService()
145 : QAbstractCollisionQueryService(*new QRayCastingServicePrivate(QStringLiteral("Collision detection service using Ray Casting")))
146{
147}
148
149QQueryHandle QRayCastingService::query(const QRay3D &ray,
150 QAbstractCollisionQueryService::QueryMode mode,
151 QBoundingVolumeProvider *provider)
152{
153 Q_D(QRayCastingService);
154
155 QQueryHandle handle = d->m_handlesCount.fetchAndStoreOrdered(newValue: 1);
156
157
158 // Blocking mapReduce
159
160#if QT_CONFIG(concurrent)
161 FutureQueryResult future = QtConcurrent::run(f: [d, ray, provider, mode, handle]{
162 return d->collides(ray, provider, mode, handle);
163 });
164 d->m_results.insert(key: handle, value: future);
165#else
166 d->m_results.insert(handle, d->collides(ray, provider, mode, handle));
167#endif
168
169 return handle;
170}
171
172QCollisionQueryResult::Hit QRayCastingService::query(const QRay3D &ray, const QBoundingVolume *volume)
173{
174 Q_D(QRayCastingService);
175
176 return d->collides(ray, volume);
177}
178
179QCollisionQueryResult QRayCastingService::fetchResult(const QQueryHandle &handle)
180{
181 Q_D(QRayCastingService);
182
183#if QT_CONFIG(concurrent)
184 return d->m_results.value(key: handle).result();
185#else
186 return d->m_results.value(handle);
187#endif
188}
189
190QList<QCollisionQueryResult> QRayCastingService::fetchAllResults() const
191{
192 Q_D(const QRayCastingService);
193
194 QList<QCollisionQueryResult> results;
195 results.reserve(asize: d->m_results.size());
196
197#if QT_CONFIG(concurrent)
198 for (const FutureQueryResult &future : d->m_results)
199 results.append(t: future.result());
200#else
201 for (const QCollisionQueryResult &result : d->m_results)
202 results.append(result);
203#endif
204
205 return results;
206}
207
208} // namespace RayCasting
209} // namespace Qt3DRender
210
211QT_END_NAMESPACE
212

source code of qt3d/src/render/raycasting/qraycastingservice.cpp