Revision control
++ // Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtTest/QTest>
++ #include <Qt3DRender/private/entity_p.h>
++ #include <Qt3DRender/private/qraycastingservice_p.h>
++ #include <Qt3DRender/private/sphere_p.h>
++ #include <Qt3DRender/private/entity_p.h>
++ #include <Qt3DRender/private/abstractpickingjob_p.h>
++ #include <Qt3DRender/private/qboundingvolumeprovider_p.h>
++ #include <Qt3DRender/private/qray3d_p.h>
++ #include <Qt3DRender/qcamera.h>
++
++ 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<Sphere> 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<QRay3D>("ray");
++ QTest::addColumn<Sphere>("sphere");
++ QTest::addColumn<bool>("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<QBoundingVolume *> volumes)
++ : m_volumes(volumes)
++ {}
++
++ QList<QBoundingVolume *> boundingVolumes() const override
++ {
++ return m_volumes;
++ }
++
++ private:
++ QList<QBoundingVolume *> m_volumes;
++ };
++
++ void tst_RayCasting::shouldReturnValidHandle()
++ {
++ // GIVEN
++ QRay3D ray;
++ Sphere v1;
++ MyBoudingVolumesProvider provider = QList<QBoundingVolume *> { &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<QBoundingVolume *> 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<QBoundingVolume *> volumes;
++ MyBoudingVolumesProvider provider(volumes);
++
++ QRayCastingService service;
++
++ QList<QQueryHandle> handles;
++ handles.append(service.query(ray,
++ QAbstractCollisionQueryService::AllHits,
++ &provider));
++ handles.append(service.query(ray,
++ QAbstractCollisionQueryService::FirstHit,
++ &provider));
++
++ // WHEN
++ const QList<QCollisionQueryResult> 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<QRay3D>("ray");
++ QTest::addColumn<QList<QBoundingVolume *>>("volumes");
++ QTest::addColumn<std::vector<QNodeId> >("hits");
++ QTest::addColumn<QAbstractCollisionQueryService::QueryMode>("queryMode");
++
++ QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1));
++
++ this->boundingVolumes.clear();
++ this->boundingVolumes.append(QList<Sphere>
++ { 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<QBoundingVolume *> { volumeAt(0), volumeAt(3) })
++ << (std::vector<QNodeId>{ volumeAt(0)->id() })
++ << QAbstractCollisionQueryService::AllHits;
++
++ QTest::newRow("All hits, Three sphere intersect") << ray
++ << (QList<QBoundingVolume *> { volumeAt(0), volumeAt(3), volumeAt(6), volumeAt(2) })
++ << (std::vector<QNodeId>{ volumeAt(0)->id(), volumeAt(2)->id(), volumeAt(6)->id() })
++ << QAbstractCollisionQueryService::AllHits;
++
++ QTest::newRow("All hits, No sphere intersect") << ray
++ << (QList<QBoundingVolume *> { volumeAt(3), volumeAt(5) })
++ << (std::vector<QNodeId>{})
++ << QAbstractCollisionQueryService::AllHits;
++
++ QTest::newRow("Sphere 1 intersect, returns First Hit") << ray
++ << (QList<QBoundingVolume *> { volumeAt(0), volumeAt(3), volumeAt(6) })
++ << (std::vector<QNodeId>{ volumeAt(0)->id() })
++ << QAbstractCollisionQueryService::FirstHit;
++
++ QTest::newRow("Sphere 3 and 5 intersects, returns First Hit") << ray
++ << (QList<QBoundingVolume *> { volumeAt(3), volumeAt(6), volumeAt(4) })
++ << (std::vector<QNodeId>{ volumeAt(4)->id() })
++ << QAbstractCollisionQueryService::FirstHit;
++
++ QTest::newRow("Sphere 5 and 3 intersects, unordered list, returns First Hit") << ray
++ << (QList<QBoundingVolume *> { volumeAt(4), volumeAt(3), volumeAt(6) })
++ << (std::vector<QNodeId>{volumeAt(4)->id() })
++ << QAbstractCollisionQueryService::FirstHit;
++
++ QTest::newRow("No sphere intersect, returns First Hit") << ray
++ << (QList<QBoundingVolume *> { volumeAt(3), volumeAt(5) })
++ << (std::vector<QNodeId>{})
++ << QAbstractCollisionQueryService::FirstHit;
++ }
++
++ void tst_RayCasting::shouldReturnHits()
++ {
++ // GIVEN
++ QFETCH(QRay3D, ray);
++ QFETCH(QList<QBoundingVolume *>, volumes);
++ QFETCH(std::vector<QNodeId>, 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<QBoundingVolume *> { &sphere1, &sphere4, &sphere3 });
++ std::vector<QNodeId> 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"