| 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 |  |