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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | using namespace Qt3DCore; |
17 | |
18 | namespace Qt3DRender { |
19 | namespace RayCasting { |
20 | |
21 | namespace { |
22 | |
23 | struct 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 | |
37 | bool compareHitsDistance(const Hit &a, const Hit &b) |
38 | { |
39 | return a.distance < b.distance; |
40 | } |
41 | |
42 | Hit 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 | |
52 | Hit 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 |
64 | QList<Hit> reduceToAllHits(QList<Hit> &results, const Hit &intermediate) |
65 | { |
66 | if (intermediate.intersects) |
67 | results.push_back(t: intermediate); |
68 | return results; |
69 | } |
70 | |
71 | struct 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 | |
86 | QCollisionQueryResult 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 | |
124 | QCollisionQueryResult::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 | |
138 | QRayCastingServicePrivate::QRayCastingServicePrivate(const QString &description) |
139 | : QAbstractCollisionQueryServicePrivate(description) |
140 | , m_handlesCount(0) |
141 | { |
142 | } |
143 | |
144 | QRayCastingService::QRayCastingService() |
145 | : QAbstractCollisionQueryService(*new QRayCastingServicePrivate(QStringLiteral("Collision detection service using Ray Casting" ))) |
146 | { |
147 | } |
148 | |
149 | QQueryHandle 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 | |
172 | QCollisionQueryResult::Hit QRayCastingService::query(const QRay3D &ray, const QBoundingVolume *volume) |
173 | { |
174 | Q_D(QRayCastingService); |
175 | |
176 | return d->collides(ray, volume); |
177 | } |
178 | |
179 | QCollisionQueryResult 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 | |
190 | QList<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 | |
211 | QT_END_NAMESPACE |
212 | |