blob: 9178b9d6f6fccda09e0d6e2450bede1c4f306350 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#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 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);
QVector<Sphere> boundingVolumes;
};
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(QVector<QBoundingVolume *> volumes)
: m_volumes(volumes)
{}
QVector<QBoundingVolume *> boundingVolumes() const
{
return m_volumes;
}
private:
QVector<QBoundingVolume *> m_volumes;
};
void tst_RayCasting::shouldReturnValidHandle()
{
// GIVEN
QRay3D ray;
Sphere v1;
MyBoudingVolumesProvider provider = QVector<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;
QVector<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;
QVector<QBoundingVolume *> volumes;
MyBoudingVolumesProvider provider(volumes);
QRayCastingService service;
QVector<QQueryHandle> handles;
handles.append(service.query(ray,
QAbstractCollisionQueryService::AllHits,
&provider));
handles.append(service.query(ray,
QAbstractCollisionQueryService::FirstHit,
&provider));
// WHEN
const QVector<QCollisionQueryResult> results = service.fetchAllResults();
// THEN
bool expectedHandlesFound = true;
for (QQueryHandle expected : qAsConst(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<QVector<QBoundingVolume *> >("volumes");
QTest::addColumn<QVector<QNodeId> >("hits");
QTest::addColumn<QAbstractCollisionQueryService::QueryMode >("queryMode");
QRay3D ray(Vector3D(1, 1, 1), Vector3D(0, 0, 1));
this->boundingVolumes.clear();
this->boundingVolumes.append(QVector<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
<< (QVector<QBoundingVolume *> () << volumeAt(0) << volumeAt(3))
<< (QVector<QNodeId>() << volumeAt(0)->id())
<< QAbstractCollisionQueryService::AllHits;
QTest::newRow("All hits, Three sphere intersect") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(0) << volumeAt(3) << volumeAt(6) << volumeAt(2))
<< (QVector<QNodeId>() << volumeAt(0)->id() << volumeAt(2)->id() << volumeAt(6)->id())
<< QAbstractCollisionQueryService::AllHits;
QTest::newRow("All hits, No sphere intersect") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(3) << volumeAt(5))
<< (QVector<QNodeId>())
<< QAbstractCollisionQueryService::AllHits;
QTest::newRow("Sphere 1 intersect, returns First Hit") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(0) << volumeAt(3) << volumeAt(6))
<< (QVector<QNodeId>() << volumeAt(0)->id())
<< QAbstractCollisionQueryService::FirstHit;
QTest::newRow("Sphere 3 and 5 intersects, returns First Hit") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(3) << volumeAt(6) << volumeAt(4))
<< (QVector<QNodeId>() << volumeAt(4)->id())
<< QAbstractCollisionQueryService::FirstHit;
QTest::newRow("Sphere 5 and 3 intersects, unordered list, returns First Hit") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(4) << volumeAt(3) << volumeAt(6))
<< (QVector<QNodeId>() << volumeAt(4)->id())
<< QAbstractCollisionQueryService::FirstHit;
QTest::newRow("No sphere intersect, returns First Hit") << ray
<< (QVector<QBoundingVolume *> () << volumeAt(3) << volumeAt(5))
<< (QVector<QNodeId>())
<< QAbstractCollisionQueryService::FirstHit;
}
void tst_RayCasting::shouldReturnHits()
{
// GIVEN
QFETCH(QRay3D, ray);
QFETCH(QVector<QBoundingVolume *>, volumes);
QFETCH(QVector<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(QVector<QBoundingVolume *>() << &sphere1 << &sphere4 << &sphere3);
QVector<QNodeId> hits(QVector<QNodeId>() << 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.0f, 0.0f, 800.0f, 600.0f);
// 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"