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
39using namespace Qt3DCore;
40using namespace Qt3DRender;
41using namespace Qt3DRender::Render;
42using namespace Qt3DRender::RayCasting;
43
44class tst_RayCasting : public QObject
45{
46 Q_OBJECT
47public:
48 tst_RayCasting() {}
49 ~tst_RayCasting() {}
50
51private 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
64private:
65 Sphere *volumeAt(int index);
66 QVector<Sphere> boundingVolumes;
67};
68
69void 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
94void 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
105class MyBoudingVolumesProvider : public QBoundingVolumeProvider
106{
107public:
108 MyBoudingVolumesProvider(QVector<QBoundingVolume *> volumes)
109 : m_volumes(volumes)
110 {}
111
112 QVector<QBoundingVolume *> boundingVolumes() const
113 {
114 return m_volumes;
115 }
116
117private:
118 QVector<QBoundingVolume *> m_volumes;
119};
120
121void 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
142void 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
167void 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
202void 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
256void 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
276void 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
301void tst_RayCasting::cleanupTestCase()
302{
303 this->boundingVolumes.clear();
304}
305
306Sphere *tst_RayCasting::volumeAt(int index)
307{
308 return &*(boundingVolumes.begin() + index);
309}
310
311void 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
380QTEST_APPLESS_MAIN(tst_RayCasting)
381
382#include "tst_raycasting.moc"
383

source code of qt3d/tests/auto/render/raycasting/tst_raycasting.cpp