Revision control
Copy as Markdown
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest>
#include <QtQuick3DRuntimeRender/private/qssgrenderray_p.h>
class intersection : public QObject
{
Q_OBJECT
public:
intersection() = default;
~intersection() = default;
private slots:
void test_aabbIntersectionv2();
void test_aabbIntersectionScaledv2();
void test_aabbIntersectionTranslatedv2();
void test_aabbIntersectionRotatedv2();
private:
static QSSGRenderRay::IntersectionResult intersectWithAABBv2_proxy(const QMatrix4x4 &inGlobalTransform,
const QSSGBounds3 &inBounds,
const QSSGRenderRay &ray)
{
QSSGRenderRay::RayData data = QSSGRenderRay::createRayData(inGlobalTransform, ray);
const auto hit = QSSGRenderRay::intersectWithAABBv2(data, inBounds);
return hit.intersects() ? QSSGRenderRay::createIntersectionResult(data, hit)
: QSSGRenderRay::IntersectionResult();
}
using IntersectionFunc = std::function<QSSGRenderRay::IntersectionResult(const QMatrix4x4 &, const QSSGBounds3 &, const QSSGRenderRay &)>;
void aabbIntersection(const IntersectionFunc &intersectFunc);
void aabbIntersectionScaled(const IntersectionFunc &intersectFunc);
void aabbIntersectionTranslated(const IntersectionFunc &intersectFunc);
void aabbIntersectionRotated(const IntersectionFunc &intersectFunc);
};
void intersection::test_aabbIntersectionv2()
{
aabbIntersection(&intersection::intersectWithAABBv2_proxy);
}
void intersection::test_aabbIntersectionScaledv2()
{
aabbIntersectionScaled(&intersection::intersectWithAABBv2_proxy);
}
void intersection::test_aabbIntersectionTranslatedv2()
{
aabbIntersectionTranslated(&intersection::intersectWithAABBv2_proxy);
}
void intersection::test_aabbIntersectionRotatedv2()
{
aabbIntersectionRotated(&intersection::intersectWithAABBv2_proxy);
}
void intersection::aabbIntersection(const IntersectionFunc &intersectFunc)
{
QSSGRenderRay::IntersectionResult res;
QMatrix4x4 globalTransform; // Identity
// Bounding box of size 1, placed at origin
QSSGBounds3 bounds { /*min=*/ QVector3D{ -0.5f, -0.5f, -0.5f }, /*max=*/ QVector3D{ 0.5f, 0.5f, 0.5f } };
// Pick ray goes directly down the z-axis and should hit directly in the middle of the box
auto pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
{ // Test against a flat bounding volume
QSSGBounds3 boundsFlat { /*min=*/ QVector3D{ -1.0f, 0.0f, -1.0f }, /*max=*/ QVector3D{ 1.0f, 0.0f, 1.0f } };
res = intersectFunc(globalTransform, boundsFlat, QSSGRenderRay(/*Origin=*/{0.0f, 10.0f, 0.0f}, /*Direction=*/{0.0f, -1.0f, 0.0f}));
QCOMPARE(res.intersects, true);
}
{ // horizontal scan
const int steps = 10;
const float stepping = (bounds.maximum.x() - bounds.minimum.x()) / float(steps);
float rayX = bounds.minimum.x() - stepping;
const float rayY = (bounds.maximum.y() - bounds.minimum.y()) / 2.0f;
// First pick should fail, as it's just outside the bound's x extents
QVERIFY(rayX < bounds.maximum.x());
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
// scan through the bounding box along the x-axis
rayX = bounds.minimum.x();
for (int i = 0; i != steps; ++i, rayX += stepping) {
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
// Do one more step to go outside the box and test that we don't hit.
rayX += stepping;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
}
{ // vertical scan
const int steps = 10;
const float stepping = (bounds.maximum.y() - bounds.minimum.y()) / float(steps);
float rayY = bounds.minimum.y() - stepping;
const float rayX = (bounds.maximum.x() - bounds.minimum.x()) / 2.0f;
// First pick should fail, as it's just outside the bound's y extents
QVERIFY(rayY < bounds.maximum.y());
pickRay = QSSGRenderRay(/*Origin=*/{rayY, rayX, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
// scan through the bounding box along the y-axis
rayY = bounds.minimum.y();
for (int i = 0; i != steps; ++i, rayY += stepping) {
pickRay = QSSGRenderRay(/*Origin=*/{rayY, rayX, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
// Do one more step to go outside the box and test that we don't hit.
rayY += stepping;
pickRay = QSSGRenderRay(/*Origin=*/{rayY, rayX, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
}
}
void intersection::aabbIntersectionScaled(const IntersectionFunc &intersectFunc)
{
QSSGRenderRay::IntersectionResult res;
QMatrix4x4 globalTransform; // Identity
// Bounding box of size 1, placed at origin
QSSGBounds3 bounds { /*min=*/ QVector3D{ -0.5f, -0.5f, -0.5f }, /*max=*/ QVector3D{ 0.5f, 0.5f, 0.5f } };
// Pick ray goes directly down the z-axis and should hit directly in the middle of the box
auto pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
{ // Scale +
globalTransform.setToIdentity();
const float scale = 2.0f;
globalTransform.scale(scale);
const float rayY = bounds.minimum.y() * scale;
const float rayX = bounds.minimum.x() * scale;
pickRay = QSSGRenderRay(/*Origin=*/{rayY, rayX, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
{ // Scale -
globalTransform.setToIdentity();
const float scale = -2.0f;
globalTransform.scale(scale);
const float rayY = bounds.maximum.y() * scale;
const float rayX = bounds.maximum.x() * scale;
pickRay = QSSGRenderRay(/*Origin=*/{rayY, rayX, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
}
void intersection::aabbIntersectionTranslated(const IntersectionFunc &intersectFunc)
{
QSSGRenderRay::IntersectionResult res;
QMatrix4x4 globalTransform; // Identity
// Bounding box of size 1, placed at origin
QSSGBounds3 bounds { /*min=*/ QVector3D{ -0.5f, -0.5f, -0.5f }, /*max=*/ QVector3D{ 0.5f, 0.5f, 0.5f } };
// Pick ray goes directly down the z-axis and should hit directly in the middle of the box
auto pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
{ // Translate 1
globalTransform.setToIdentity();
const float uniformTranslation = 1.0f;
globalTransform.translate(uniformTranslation, uniformTranslation);
// Verify that we can get a hit in the center of the bounding box
pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
const float rayY = bounds.minimum.y() + uniformTranslation;
const float rayX = bounds.minimum.x() + uniformTranslation;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
{ // Translate 2
globalTransform.setToIdentity();
const float translationX = -1.0f;
const float translationY = 1.0f;
globalTransform.translate(translationX, translationY);
// Verify that we can get a hit in the center of the bounding box
pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
const float rayY = bounds.minimum.y() + translationY;
const float rayX = bounds.minimum.x() + translationX;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
{ // Translate 3
globalTransform.setToIdentity();
const float uniformTranslation = -1.0f;
globalTransform.translate(uniformTranslation, uniformTranslation);
// Verify that we can get a hit in the center of the bounding box
pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
const float rayY = bounds.maximum.y() + uniformTranslation;
const float rayX = bounds.maximum.x() + uniformTranslation;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
{ // Translate 4
globalTransform.setToIdentity();
const float translationX = 1.0f;
const float translationY = -1.0f;
globalTransform.translate(translationX, translationY);
// Verify that we can get a hit in the center of the bounding box
pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
const float rayY = bounds.minimum.y() + translationY;
const float rayX = bounds.minimum.x() + translationX;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
}
void intersection::aabbIntersectionRotated(const IntersectionFunc &intersectFunc)
{
QSSGRenderRay::IntersectionResult res;
QMatrix4x4 globalTransform; // Identity
// Bounding box of size 1, placed at origin
QSSGBounds3 bounds { /*min=*/ QVector3D{ -0.5f, -0.5f, -0.5f }, /*max=*/ QVector3D{ 0.5f, 0.5f, 0.5f } };
// Pick ray goes directly down the z-axis and should hit directly in the middle of the box
auto pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
{ // Rotate
globalTransform.setToIdentity();
const float rotation = 45.0f;
globalTransform.rotate(rotation, {0.0f, 0.0f, 1.0f});
// Verify that we can get a hit in the center of the bounding box
pickRay = QSSGRenderRay(/*Origin=*/{0.0f, 0.0f, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
float rayX = bounds.minimum.x();
float rayY = bounds.minimum.y();
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
rayX = bounds.maximum.x();
rayY = bounds.maximum.y();
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, false);
const auto center = bounds.center();
rayX = center.x();
rayY = center.y() + std::sqrt(bounds.dimensions().x() + bounds.dimensions().y()) * 0.5f;
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
rayX = center.x() + std::sqrt(bounds.dimensions().x() + bounds.dimensions().y()) * 0.5f;
rayY = center.y();
pickRay = QSSGRenderRay(/*Origin=*/{rayX, rayY, 10.0f}, /*Direction=*/{0.0f, 0.0f, -1.0f});
res = intersectFunc(globalTransform, bounds, pickRay);
QCOMPARE(res.intersects, true);
}
}
QTEST_APPLESS_MAIN(intersection)
#include "tst_intersection.moc"