1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 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:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QtTest> |
30 | #include <Qt3DRender/private/entity_p.h> |
31 | #include <Qt3DRender/private/qraycastingservice_p.h> |
32 | #include <Qt3DRender/private/sphere_p.h> |
33 | #include <Qt3DRender/private/entity_p.h> |
34 | #include <Qt3DRender/private/abstractpickingjob_p.h> |
35 | #include <Qt3DRender/private/qboundingvolumeprovider_p.h> |
36 | #include <Qt3DRender/private/qray3d_p.h> |
37 | #include <Qt3DRender/qcamera.h> |
38 | |
39 | using namespace Qt3DCore; |
40 | using namespace Qt3DRender; |
41 | using namespace Qt3DRender::Render; |
42 | using namespace Qt3DRender::RayCasting; |
43 | |
44 | class tst_RayCasting : public QObject |
45 | { |
46 | Q_OBJECT |
47 | public: |
48 | tst_RayCasting() {} |
49 | ~tst_RayCasting() {} |
50 | |
51 | private Q_SLOTS: |
52 | void shouldReturnValidHandle(); |
53 | void shouldReturnResultForEachHandle(); |
54 | void shouldReturnAllResults(); |
55 | void shouldReturnHits(); |
56 | void shouldReturnHits_data(); |
57 | void shouldIntersect_data(); |
58 | void shouldIntersect(); |
59 | void shouldUseProvidedBoudingVolumes(); |
60 | void mousePicking(); |
61 | |
62 | void cleanupTestCase(); |
63 | |
64 | private: |
65 | Sphere *volumeAt(int index); |
66 | QVector<Sphere> boundingVolumes; |
67 | }; |
68 | |
69 | void tst_RayCasting::shouldIntersect_data() |
70 | { |
71 | QTest::addColumn<QRay3D>(name: "ray" ); |
72 | QTest::addColumn<Sphere>(name: "sphere" ); |
73 | QTest::addColumn<bool>(name: "shouldIntersect" ); |
74 | |
75 | QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); |
76 | |
77 | Sphere sphere1(Vector3D(1, 1, 1), 2); |
78 | Sphere sphere2(Vector3D(0, 0, 0), 3); |
79 | Sphere sphere3(Vector3D(0, 1, 3), 1); |
80 | Sphere sphere4(Vector3D(4, 4, 5), 1); |
81 | Sphere sphere5(Vector3D(2, 2, 11), 5); |
82 | Sphere sphere6(Vector3D(2, 2, 13), 1); |
83 | Sphere sphere7(Vector3D(2, 2, 15), 5); |
84 | |
85 | QTest::newRow(dataTag: "Ray starts inside sphere" ) << ray << sphere1 << true; |
86 | QTest::newRow(dataTag: "Ray starts inside sphere" ) << ray << sphere2 << true; |
87 | QTest::newRow(dataTag: "Ray intersects sphere tangentially" ) << ray << sphere3 << true; |
88 | QTest::newRow(dataTag: "No intersection" ) << ray << sphere4 << false; |
89 | QTest::newRow(dataTag: "Ray intersect sphere" ) << ray << sphere5 << true; |
90 | QTest::newRow(dataTag: "No intersection" ) << ray << sphere6 << false; |
91 | QTest::newRow(dataTag: "Ray intersect sphere" ) << ray << sphere7 << true; |
92 | } |
93 | |
94 | void tst_RayCasting::shouldIntersect() |
95 | { |
96 | QFETCH(QRay3D, ray); |
97 | QFETCH(Sphere, sphere); |
98 | QFETCH(bool, shouldIntersect); |
99 | |
100 | Vector3D intersectionPoint; |
101 | |
102 | QCOMPARE(sphere.intersects(ray, &intersectionPoint), shouldIntersect); |
103 | } |
104 | |
105 | class MyBoudingVolumesProvider : public QBoundingVolumeProvider |
106 | { |
107 | public: |
108 | MyBoudingVolumesProvider(QVector<QBoundingVolume *> volumes) |
109 | : m_volumes(volumes) |
110 | {} |
111 | |
112 | QVector<QBoundingVolume *> boundingVolumes() const |
113 | { |
114 | return m_volumes; |
115 | } |
116 | |
117 | private: |
118 | QVector<QBoundingVolume *> m_volumes; |
119 | }; |
120 | |
121 | void tst_RayCasting::shouldReturnValidHandle() |
122 | { |
123 | // GIVEN |
124 | QRay3D ray; |
125 | Sphere v1; |
126 | MyBoudingVolumesProvider provider = QVector<QBoundingVolume *>() << &v1; |
127 | |
128 | QRayCastingService service; |
129 | |
130 | // WHEN |
131 | QQueryHandle handle = service.query(ray, |
132 | mode: QAbstractCollisionQueryService::AllHits, |
133 | provider: &provider); |
134 | |
135 | // THEN |
136 | QVERIFY(handle >= 0); |
137 | |
138 | // Wait the query to finish |
139 | service.fetchResult(handle); |
140 | } |
141 | |
142 | void tst_RayCasting::shouldReturnResultForEachHandle() |
143 | { |
144 | // GIVEN |
145 | QRay3D ray; |
146 | QVector<QBoundingVolume *> volumes; |
147 | MyBoudingVolumesProvider provider(volumes); |
148 | |
149 | QRayCastingService service; |
150 | |
151 | QQueryHandle handle1 = service.query(ray, |
152 | mode: QAbstractCollisionQueryService::AllHits, |
153 | provider: &provider); |
154 | QQueryHandle handle2 = service.query(ray, |
155 | mode: QAbstractCollisionQueryService::FirstHit, |
156 | provider: &provider); |
157 | |
158 | // WHEN |
159 | QCollisionQueryResult result2 = service.fetchResult(handle: handle2); |
160 | QCollisionQueryResult result1 = service.fetchResult(handle: handle1); |
161 | |
162 | // THEN |
163 | QCOMPARE(result1.handle(), handle1); |
164 | QCOMPARE(result2.handle(), handle2); |
165 | } |
166 | |
167 | void tst_RayCasting::shouldReturnAllResults() |
168 | { |
169 | // GIVEN |
170 | QRay3D ray; |
171 | QVector<QBoundingVolume *> volumes; |
172 | MyBoudingVolumesProvider provider(volumes); |
173 | |
174 | QRayCastingService service; |
175 | |
176 | QVector<QQueryHandle> handles; |
177 | handles.append(t: service.query(ray, |
178 | mode: QAbstractCollisionQueryService::AllHits, |
179 | provider: &provider)); |
180 | handles.append(t: service.query(ray, |
181 | mode: QAbstractCollisionQueryService::FirstHit, |
182 | provider: &provider)); |
183 | |
184 | // WHEN |
185 | const QVector<QCollisionQueryResult> results = service.fetchAllResults(); |
186 | |
187 | // THEN |
188 | bool expectedHandlesFound = true; |
189 | for (QQueryHandle expected : qAsConst(t&: handles)) { |
190 | bool found = false; |
191 | for (QCollisionQueryResult result : results) { |
192 | if (result.handle() == expected) |
193 | found = true; |
194 | } |
195 | |
196 | expectedHandlesFound &= found; |
197 | } |
198 | |
199 | QVERIFY(expectedHandlesFound); |
200 | } |
201 | |
202 | void tst_RayCasting::shouldReturnHits_data() |
203 | { |
204 | QTest::addColumn<QRay3D>(name: "ray" ); |
205 | QTest::addColumn<QVector<QBoundingVolume *> >(name: "volumes" ); |
206 | QTest::addColumn<QVector<QNodeId> >(name: "hits" ); |
207 | QTest::addColumn<QAbstractCollisionQueryService::QueryMode >(name: "queryMode" ); |
208 | |
209 | QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); |
210 | |
211 | this->boundingVolumes.clear(); |
212 | this->boundingVolumes.append(l: QVector<Sphere>() << Sphere(Vector3D(1, 1, 1), 3, QNodeId::createId()) |
213 | << Sphere(Vector3D(0, 0, 0), 3, QNodeId::createId()) |
214 | << Sphere(Vector3D(0, 1, 3), 1, QNodeId::createId()) |
215 | << Sphere(Vector3D(4, 4, 5), 1, QNodeId::createId()) |
216 | << Sphere(Vector3D(2, 2, 11), 5, QNodeId::createId()) |
217 | << Sphere(Vector3D(2, 2, 13), 1, QNodeId::createId()) |
218 | << Sphere(Vector3D(2, 2, 15), 5, QNodeId::createId())); |
219 | |
220 | QTest::newRow(dataTag: "All hits, One sphere intersect" ) << ray |
221 | << (QVector<QBoundingVolume *> () << volumeAt(index: 0) << volumeAt(index: 3)) |
222 | << (QVector<QNodeId>() << volumeAt(index: 0)->id()) |
223 | << QAbstractCollisionQueryService::AllHits; |
224 | |
225 | QTest::newRow(dataTag: "All hits, Three sphere intersect" ) << ray |
226 | << (QVector<QBoundingVolume *> () << volumeAt(index: 0) << volumeAt(index: 3) << volumeAt(index: 6) << volumeAt(index: 2)) |
227 | << (QVector<QNodeId>() << volumeAt(index: 0)->id() << volumeAt(index: 2)->id() << volumeAt(index: 6)->id()) |
228 | << QAbstractCollisionQueryService::AllHits; |
229 | |
230 | QTest::newRow(dataTag: "All hits, No sphere intersect" ) << ray |
231 | << (QVector<QBoundingVolume *> () << volumeAt(index: 3) << volumeAt(index: 5)) |
232 | << (QVector<QNodeId>()) |
233 | << QAbstractCollisionQueryService::AllHits; |
234 | |
235 | QTest::newRow(dataTag: "Sphere 1 intersect, returns First Hit" ) << ray |
236 | << (QVector<QBoundingVolume *> () << volumeAt(index: 0) << volumeAt(index: 3) << volumeAt(index: 6)) |
237 | << (QVector<QNodeId>() << volumeAt(index: 0)->id()) |
238 | << QAbstractCollisionQueryService::FirstHit; |
239 | |
240 | QTest::newRow(dataTag: "Sphere 3 and 5 intersects, returns First Hit" ) << ray |
241 | << (QVector<QBoundingVolume *> () << volumeAt(index: 3) << volumeAt(index: 6) << volumeAt(index: 4)) |
242 | << (QVector<QNodeId>() << volumeAt(index: 4)->id()) |
243 | << QAbstractCollisionQueryService::FirstHit; |
244 | |
245 | QTest::newRow(dataTag: "Sphere 5 and 3 intersects, unordered list, returns First Hit" ) << ray |
246 | << (QVector<QBoundingVolume *> () << volumeAt(index: 4) << volumeAt(index: 3) << volumeAt(index: 6)) |
247 | << (QVector<QNodeId>() << volumeAt(index: 4)->id()) |
248 | << QAbstractCollisionQueryService::FirstHit; |
249 | |
250 | QTest::newRow(dataTag: "No sphere intersect, returns First Hit" ) << ray |
251 | << (QVector<QBoundingVolume *> () << volumeAt(index: 3) << volumeAt(index: 5)) |
252 | << (QVector<QNodeId>()) |
253 | << QAbstractCollisionQueryService::FirstHit; |
254 | } |
255 | |
256 | void tst_RayCasting::shouldReturnHits() |
257 | { |
258 | // GIVEN |
259 | QFETCH(QRay3D, ray); |
260 | QFETCH(QVector<QBoundingVolume *>, volumes); |
261 | QFETCH(QVector<QNodeId>, hits); |
262 | QFETCH(QAbstractCollisionQueryService::QueryMode, queryMode); |
263 | |
264 | MyBoudingVolumesProvider provider(volumes); |
265 | QRayCastingService service; |
266 | |
267 | // WHEN |
268 | QQueryHandle handle = service.query(ray, mode: queryMode, provider: &provider); |
269 | QCollisionQueryResult result = service.fetchResult(handle); |
270 | |
271 | // THEN |
272 | QCOMPARE(result.entitiesHit().size(), hits.size()); |
273 | QCOMPARE(result.entitiesHit(), hits); |
274 | } |
275 | |
276 | void tst_RayCasting::shouldUseProvidedBoudingVolumes() |
277 | { |
278 | // GIVEN |
279 | QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); |
280 | |
281 | Sphere sphere1(Vector3D(1, 1, 1), 3); |
282 | Sphere sphere3(Vector3D(0, 1, 3), 1); |
283 | Sphere sphere4(Vector3D(4, 4, 5), 1); |
284 | |
285 | MyBoudingVolumesProvider provider(QVector<QBoundingVolume *>() << &sphere1 << &sphere4 << &sphere3); |
286 | QVector<QNodeId> hits(QVector<QNodeId>() << sphere1.id() << sphere3.id()); |
287 | |
288 | QRayCastingService service; |
289 | |
290 | // WHEN |
291 | QQueryHandle handle = service.query(ray, |
292 | mode: QAbstractCollisionQueryService::AllHits, |
293 | provider: &provider); |
294 | QCollisionQueryResult result = service.fetchResult(handle); |
295 | |
296 | // THEN |
297 | QCOMPARE(result.entitiesHit().size(), hits.size()); |
298 | QCOMPARE(result.entitiesHit(), hits); |
299 | } |
300 | |
301 | void tst_RayCasting::cleanupTestCase() |
302 | { |
303 | this->boundingVolumes.clear(); |
304 | } |
305 | |
306 | Sphere *tst_RayCasting::volumeAt(int index) |
307 | { |
308 | return &*(boundingVolumes.begin() + index); |
309 | } |
310 | |
311 | void tst_RayCasting::mousePicking() |
312 | { |
313 | // GIVEN |
314 | Qt3DRender::QCamera camera; |
315 | camera.setProjectionType(QCameraLens::PerspectiveProjection); |
316 | camera.setFieldOfView(45.0f); |
317 | camera.setAspectRatio(800.0f/600.0f); |
318 | camera.setNearPlane(0.1f); |
319 | camera.setFarPlane(1000.0f); |
320 | camera.setPosition(QVector3D(0.0f, 0.0f, -40.0f)); |
321 | camera.setUpVector(QVector3D(0.0f, 1.0f, 0.0f)); |
322 | camera.setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); |
323 | |
324 | const QRectF viewport(0.0f, 0.0f, 800.0f, 600.0f); |
325 | |
326 | // Window center on near plane |
327 | QRay3D ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(pos: viewport.center().toPoint(), |
328 | viewMatrix: Matrix4x4(camera.viewMatrix()), |
329 | projectionMatrix: Matrix4x4(camera.projectionMatrix()), |
330 | viewport: viewport.toRect()); |
331 | Qt3DRender::Render::Sphere s(Vector3D(0.0f, 0.5f, 0.0f), 1.0f); |
332 | |
333 | // WHEN |
334 | bool intersects = s.intersects(ray, q: nullptr); |
335 | |
336 | // THEN |
337 | QVERIFY(intersects); |
338 | |
339 | // WHEN |
340 | ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(pos: viewport.topLeft().toPoint(), |
341 | viewMatrix: Matrix4x4(camera.viewMatrix()), |
342 | projectionMatrix: Matrix4x4(camera.projectionMatrix()), |
343 | viewport: viewport.toRect()); |
344 | intersects = s.intersects(ray, q: nullptr); |
345 | |
346 | // THEN |
347 | QVERIFY(!intersects); |
348 | |
349 | // WHEN |
350 | ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(pos: viewport.topRight().toPoint(), |
351 | viewMatrix: Matrix4x4(camera.viewMatrix()), |
352 | projectionMatrix: Matrix4x4(camera.projectionMatrix()), |
353 | viewport: viewport.toRect()); |
354 | intersects = s.intersects(ray, q: nullptr); |
355 | |
356 | // THEN |
357 | QVERIFY(!intersects); |
358 | |
359 | // WHEN |
360 | ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(pos: viewport.bottomLeft().toPoint(), |
361 | viewMatrix: Matrix4x4(camera.viewMatrix()), |
362 | projectionMatrix: Matrix4x4(camera.projectionMatrix()), |
363 | viewport: viewport.toRect()); |
364 | intersects = s.intersects(ray, q: nullptr); |
365 | |
366 | // THEN |
367 | QVERIFY(!intersects); |
368 | |
369 | // WHEN |
370 | ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(pos: viewport.bottomRight().toPoint(), |
371 | viewMatrix: Matrix4x4(camera.viewMatrix()), |
372 | projectionMatrix: Matrix4x4(camera.projectionMatrix()), |
373 | viewport: viewport.toRect()); |
374 | intersects = s.intersects(ray, q: nullptr); |
375 | |
376 | // THEN |
377 | QVERIFY(!intersects); |
378 | } |
379 | |
380 | QTEST_APPLESS_MAIN(tst_RayCasting) |
381 | |
382 | #include "tst_raycasting.moc" |
383 | |