// Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #include #include #include #include #include using namespace Qt3DCore; using namespace Qt3DRender; using namespace Qt3DRender::Render; using namespace Qt3DRender::RayCasting; class tst_RayCasting : public QObject { Q_OBJECT public: tst_RayCasting() {} ~tst_RayCasting() {} private Q_SLOTS: void initTestCase(); void shouldReturnValidHandle(); void shouldReturnResultForEachHandle(); void shouldReturnAllResults(); void shouldReturnHits(); void shouldReturnHits_data(); void shouldIntersect_data(); void shouldIntersect(); void shouldUseProvidedBoudingVolumes(); void mousePicking(); void cleanupTestCase(); private: Sphere *volumeAt(int index); QList boundingVolumes; }; void tst_RayCasting::initTestCase() { #if defined Q_OS_QNX QSKIP("This test times out on QNX (QTBUG-107694)"); #endif } void tst_RayCasting::shouldIntersect_data() { QTest::addColumn("ray"); QTest::addColumn("sphere"); QTest::addColumn("shouldIntersect"); QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); Sphere sphere1(Vector3D(1, 1, 1), 2); Sphere sphere2(Vector3D(0, 0, 0), 3); Sphere sphere3(Vector3D(0, 1, 3), 1); Sphere sphere4(Vector3D(4, 4, 5), 1); Sphere sphere5(Vector3D(2, 2, 11), 5); Sphere sphere6(Vector3D(2, 2, 13), 1); Sphere sphere7(Vector3D(2, 2, 15), 5); QTest::newRow("Ray starts inside sphere") << ray << sphere1 << true; QTest::newRow("Ray starts inside sphere") << ray << sphere2 << true; QTest::newRow("Ray intersects sphere tangentially") << ray << sphere3 << true; QTest::newRow("No intersection") << ray << sphere4 << false; QTest::newRow("Ray intersect sphere") << ray << sphere5 << true; QTest::newRow("No intersection") << ray << sphere6 << false; QTest::newRow("Ray intersect sphere") << ray << sphere7 << true; } void tst_RayCasting::shouldIntersect() { QFETCH(QRay3D, ray); QFETCH(Sphere, sphere); QFETCH(bool, shouldIntersect); Vector3D intersectionPoint; QCOMPARE(sphere.intersects(ray, &intersectionPoint), shouldIntersect); } class MyBoudingVolumesProvider : public QBoundingVolumeProvider { public: MyBoudingVolumesProvider(QList volumes) : m_volumes(volumes) {} QList boundingVolumes() const override { return m_volumes; } private: QList m_volumes; }; void tst_RayCasting::shouldReturnValidHandle() { // GIVEN QRay3D ray; Sphere v1; MyBoudingVolumesProvider provider = QList { &v1 }; QRayCastingService service; // WHEN QQueryHandle handle = service.query(ray, QAbstractCollisionQueryService::AllHits, &provider); // THEN QVERIFY(handle >= 0); // Wait the query to finish service.fetchResult(handle); } void tst_RayCasting::shouldReturnResultForEachHandle() { // GIVEN QRay3D ray; QList volumes; MyBoudingVolumesProvider provider(volumes); QRayCastingService service; QQueryHandle handle1 = service.query(ray, QAbstractCollisionQueryService::AllHits, &provider); QQueryHandle handle2 = service.query(ray, QAbstractCollisionQueryService::FirstHit, &provider); // WHEN QCollisionQueryResult result2 = service.fetchResult(handle2); QCollisionQueryResult result1 = service.fetchResult(handle1); // THEN QCOMPARE(result1.handle(), handle1); QCOMPARE(result2.handle(), handle2); } void tst_RayCasting::shouldReturnAllResults() { // GIVEN QRay3D ray; QList volumes; MyBoudingVolumesProvider provider(volumes); QRayCastingService service; QList handles; handles.append(service.query(ray, QAbstractCollisionQueryService::AllHits, &provider)); handles.append(service.query(ray, QAbstractCollisionQueryService::FirstHit, &provider)); // WHEN const QList results = service.fetchAllResults(); // THEN bool expectedHandlesFound = true; for (QQueryHandle expected : std::as_const(handles)) { bool found = false; for (QCollisionQueryResult result : results) { if (result.handle() == expected) found = true; } expectedHandlesFound &= found; } QVERIFY(expectedHandlesFound); } void tst_RayCasting::shouldReturnHits_data() { QTest::addColumn("ray"); QTest::addColumn>("volumes"); QTest::addColumn >("hits"); QTest::addColumn("queryMode"); QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); this->boundingVolumes.clear(); this->boundingVolumes.append(QList { Sphere(Vector3D(1, 1, 1), 3, QNodeId::createId()), Sphere(Vector3D(0, 0, 0), 3, QNodeId::createId()), Sphere(Vector3D(0, 1, 3), 1, QNodeId::createId()), Sphere(Vector3D(4, 4, 5), 1, QNodeId::createId()), Sphere(Vector3D(2, 2, 11), 5, QNodeId::createId()), Sphere(Vector3D(2, 2, 13), 1, QNodeId::createId()), Sphere(Vector3D(2, 2, 15), 5, QNodeId::createId()) }); QTest::newRow("All hits, One sphere intersect") << ray << (QList { volumeAt(0), volumeAt(3) }) << (std::vector{ volumeAt(0)->id() }) << QAbstractCollisionQueryService::AllHits; QTest::newRow("All hits, Three sphere intersect") << ray << (QList { volumeAt(0), volumeAt(3), volumeAt(6), volumeAt(2) }) << (std::vector{ volumeAt(0)->id(), volumeAt(2)->id(), volumeAt(6)->id() }) << QAbstractCollisionQueryService::AllHits; QTest::newRow("All hits, No sphere intersect") << ray << (QList { volumeAt(3), volumeAt(5) }) << (std::vector{}) << QAbstractCollisionQueryService::AllHits; QTest::newRow("Sphere 1 intersect, returns First Hit") << ray << (QList { volumeAt(0), volumeAt(3), volumeAt(6) }) << (std::vector{ volumeAt(0)->id() }) << QAbstractCollisionQueryService::FirstHit; QTest::newRow("Sphere 3 and 5 intersects, returns First Hit") << ray << (QList { volumeAt(3), volumeAt(6), volumeAt(4) }) << (std::vector{ volumeAt(4)->id() }) << QAbstractCollisionQueryService::FirstHit; QTest::newRow("Sphere 5 and 3 intersects, unordered list, returns First Hit") << ray << (QList { volumeAt(4), volumeAt(3), volumeAt(6) }) << (std::vector{volumeAt(4)->id() }) << QAbstractCollisionQueryService::FirstHit; QTest::newRow("No sphere intersect, returns First Hit") << ray << (QList { volumeAt(3), volumeAt(5) }) << (std::vector{}) << QAbstractCollisionQueryService::FirstHit; } void tst_RayCasting::shouldReturnHits() { // GIVEN QFETCH(QRay3D, ray); QFETCH(QList, volumes); QFETCH(std::vector, hits); QFETCH(QAbstractCollisionQueryService::QueryMode, queryMode); MyBoudingVolumesProvider provider(volumes); QRayCastingService service; // WHEN QQueryHandle handle = service.query(ray, queryMode, &provider); QCollisionQueryResult result = service.fetchResult(handle); // THEN QCOMPARE(result.entitiesHit().size(), hits.size()); QCOMPARE(result.entitiesHit(), hits); } void tst_RayCasting::shouldUseProvidedBoudingVolumes() { // GIVEN QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1)); Sphere sphere1(Vector3D(1, 1, 1), 3); Sphere sphere3(Vector3D(0, 1, 3), 1); Sphere sphere4(Vector3D(4, 4, 5), 1); MyBoudingVolumesProvider provider(QList { &sphere1, &sphere4, &sphere3 }); std::vector hits{ sphere1.id(), sphere3.id()}; QRayCastingService service; // WHEN QQueryHandle handle = service.query(ray, QAbstractCollisionQueryService::AllHits, &provider); QCollisionQueryResult result = service.fetchResult(handle); // THEN QCOMPARE(result.entitiesHit().size(), hits.size()); QCOMPARE(result.entitiesHit(), hits); } void tst_RayCasting::cleanupTestCase() { this->boundingVolumes.clear(); } Sphere *tst_RayCasting::volumeAt(int index) { return &*(boundingVolumes.begin() + index); } void tst_RayCasting::mousePicking() { // GIVEN Qt3DRender::QCamera camera; camera.setProjectionType(QCameraLens::PerspectiveProjection); camera.setFieldOfView(45.0f); camera.setAspectRatio(800.0f/600.0f); camera.setNearPlane(0.1f); camera.setFarPlane(1000.0f); camera.setPosition(QVector3D(0.0f, 0.0f, -40.0f)); camera.setUpVector(QVector3D(0.0f, 1.0f, 0.0f)); camera.setViewCenter(QVector3D(0.0f, 0.0f, 0.0f)); const QRectF viewport(0., 0., 800., 600.); // Window center on near plane QRay3D ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(viewport.center().toPoint(), Matrix4x4(camera.viewMatrix()), Matrix4x4(camera.projectionMatrix()), viewport.toRect()); Qt3DRender::Render::Sphere s(Vector3D(0.0f, 0.5f, 0.0f), 1.0f); // WHEN bool intersects = s.intersects(ray, nullptr); // THEN QVERIFY(intersects); // WHEN ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(viewport.topLeft().toPoint(), Matrix4x4(camera.viewMatrix()), Matrix4x4(camera.projectionMatrix()), viewport.toRect()); intersects = s.intersects(ray, nullptr); // THEN QVERIFY(!intersects); // WHEN ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(viewport.topRight().toPoint(), Matrix4x4(camera.viewMatrix()), Matrix4x4(camera.projectionMatrix()), viewport.toRect()); intersects = s.intersects(ray, nullptr); // THEN QVERIFY(!intersects); // WHEN ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(viewport.bottomLeft().toPoint(), Matrix4x4(camera.viewMatrix()), Matrix4x4(camera.projectionMatrix()), viewport.toRect()); intersects = s.intersects(ray, nullptr); // THEN QVERIFY(!intersects); // WHEN ray = Qt3DRender::Render::AbstractPickingJob::intersectionRay(viewport.bottomRight().toPoint(), Matrix4x4(camera.viewMatrix()), Matrix4x4(camera.projectionMatrix()), viewport.toRect()); intersects = s.intersects(ray, nullptr); // THEN QVERIFY(!intersects); } QTEST_APPLESS_MAIN(tst_RayCasting) #include "tst_raycasting.moc"