diff options
Diffstat (limited to 'tests/auto/viewer')
-rw-r--r-- | tests/auto/viewer/tst_qt3dsviewer.cpp | 762 | ||||
-rw-r--r-- | tests/auto/viewer/tst_qt3dsviewer.h | 80 | ||||
-rw-r--r-- | tests/auto/viewer/tst_qt3dsviewer.qml | 62 | ||||
-rw-r--r-- | tests/auto/viewer/viewer.pro | 21 | ||||
-rw-r--r-- | tests/auto/viewer/viewer.qrc | 16 |
5 files changed, 941 insertions, 0 deletions
diff --git a/tests/auto/viewer/tst_qt3dsviewer.cpp b/tests/auto/viewer/tst_qt3dsviewer.cpp new file mode 100644 index 0000000..c9787fd --- /dev/null +++ b/tests/auto/viewer/tst_qt3dsviewer.cpp @@ -0,0 +1,762 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2012 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $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 "tst_qt3dsviewer.h" + +#include <QtQuick/QQuickItem> +#include <QtGui/QSurfaceFormat> +#include <QtStudio3D/Q3DSPresentation> +#include <QtStudio3D/Q3DSElement> +#include <QtStudio3D/Q3DSViewerSettings> +#include <QtStudio3D/Q3DSGeometry> +#include <QtCore/QRandomGenerator> +#include <QtCore/QFile> +#include <QtCore/QTextStream> +#include <QtCore/QRegularExpression> + +void messageOutput(QtMsgType type, const QMessageLogContext &context, + const QString &msg) +{ + Q_UNUSED(context); + switch (type) { + case QtDebugMsg: + case QtInfoMsg: + case QtWarningMsg: + case QtCriticalMsg: + break; // swallow + case QtFatalMsg: + QFAIL(msg.toLocal8Bit().constData()); + } +} + +void tst_qt3dsviewer::initTestCase() +{ + qInstallMessageHandler(messageOutput); +} + +void tst_qt3dsviewer::cleanupTestCase() +{ +} + +void tst_qt3dsviewer::init() +{ +#if defined(Q_OS_ANDROID) + QSurfaceFormat format; + format.setDepthBufferSize(32); + format.setVersion(3, 2); + format.setProfile(QSurfaceFormat::CompatibilityProfile); + format.setRenderableType(QSurfaceFormat::OpenGLES); +#else + QSurfaceFormat format; + format.setDepthBufferSize(32); + format.setVersion(4, 3); + format.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(format); +#endif + + m_viewer = new QQuickView; + m_viewer->setTitle(QStringLiteral("tst_qt3dsviewer")); + m_viewer->setSource(QUrl("qrc:/tst_qt3dsviewer.qml")); + m_studio3DItem = m_viewer->rootObject(); + m_presentation = nullptr; + m_settings = nullptr; + m_ignoreError = false; + + QVERIFY(m_studio3DItem); + + const auto children = m_studio3DItem->children(); + for (auto &child : children) { + if (!m_presentation) + m_presentation = qobject_cast<Q3DSPresentation *>(child); + if (!m_settings) + m_settings = qobject_cast<Q3DSViewerSettings *>(child); + } + + QVERIFY(m_presentation); + QVERIFY(m_settings); +} + +void tst_qt3dsviewer::cleanup() +{ + deleteCreated(); + if (!m_ignoreError) + QCOMPARE(m_studio3DItem->property("error").toString(), {}); + m_studio3DItem = nullptr; + m_viewer->hide(); + m_viewer->deleteLater(); + m_viewer = nullptr; +} + +void tst_qt3dsviewer::testEmpty() +{ + m_presentation->setProperty("source", QUrl()); + m_viewer->show(); + QTest::qWait(1000); + QCOMPARE(m_studio3DItem->property("running").toBool(), false); + QVERIFY(!m_studio3DItem->property("error").toString().isEmpty()); + m_ignoreError = true; // To avoid triggering cleanup() fail as we are expecting an error +} + +void tst_qt3dsviewer::testLoading() +{ + QCOMPARE(m_studio3DItem->property("running").toBool(), false); + m_viewer->show(); + QTest::qWait(1000); + QCOMPARE(m_studio3DItem->property("running").toBool(), true); +} + + +void tst_qt3dsviewer::testSlides() +{ + QSignalSpy spyEntered(m_presentation, + SIGNAL(slideEntered(const QString &, unsigned int, const QString &))); + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + QCOMPARE(spyEntered.count(), 0); + QCOMPARE(spyExited.count(), 0); + + m_viewer->show(); + QTest::qWait(1000); + + QCOMPARE(spyEntered.count(), 1); + QCOMPARE(spyExited.count(), 0); + + QVERIFY(spyExited.wait(12000)); + QCOMPARE(spyEntered.count(), 2); + QCOMPARE(spyExited.count(), 1); +} + +void tst_qt3dsviewer::testFrameUpdates() +{ + QSignalSpy spyFrames(m_studio3DItem, SIGNAL(frameUpdate())); + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + m_viewer->show(); + QVERIFY(spyExited.wait(12000)); + // Just ensure we get some frames, exact count will vary a lot due to external factors + QVERIFY(spyFrames.count() > 10); +} + +void tst_qt3dsviewer::testSettings() +{ + m_viewer->show(); + m_settings->setMatteColor(QColor("#0000ff")); + QVERIFY(m_settings->matteColor() == QColor("#0000ff")); + + // Save and change matte color + m_settings->save("", "tst_qt3dsviewer", "tst_qt3dsviewer"); + m_settings->setMatteColor(QColor("#00ff00")); + QVERIFY(m_settings->matteColor() == QColor("#00ff00")); + // Load and previous matte color should be back + m_settings->load("", "tst_qt3dsviewer", "tst_qt3dsviewer"); + QVERIFY(m_settings->matteColor() == QColor("#0000ff")); +} + +void tst_qt3dsviewer::testCreateElement() +{ + m_viewer->show(); + + m_settings->setShowRenderStats(true); + m_settings->setScaleMode(Q3DSViewerSettings::ScaleModeFill); + + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + QSignalSpy spyElemCreated(m_presentation, SIGNAL(elementsCreated(const QStringList &, + const QString &))); + + QObject::connect(m_presentation, &Q3DSPresentation::elementsCreated, + [this](const QStringList &elementNames, const QString &error) { + QCOMPARE(error, QString()); + for (const auto &elementName : elementNames) + QVERIFY(m_createdElements.contains(elementName)); + }); + + auto loadMatDefFile = [&](const QString &fileName) -> QString { + QFile matDefFile(fileName); + if (!matDefFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return {}; + + QTextStream in(&matDefFile); + return in.readAll(); + }; + + int animValue = 0; + + QString md = loadMatDefFile(QStringLiteral( + ":/scenes/simple_cube_animation/materials/Basic Red.materialdef")); + m_presentation->createMaterial(md); + m_createdMaterials << QStringLiteral("materials/Basic Red"); + md = loadMatDefFile(QStringLiteral( + ":/scenes/simple_cube_animation/materials/Basic Green.materialdef")); + m_presentation->createMaterial(md); + m_createdMaterials << QStringLiteral("materials/Basic Green"); + + + QHash<QString, QVariant> data; + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QString()); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 4500); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(200, 300, 200))); + data.insert(QStringLiteral("opacity"), 20.0); + data.insert(QStringLiteral("controlledproperty"), QStringLiteral("@newDataInput opacity")); + + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + + // Elements can be registered before they are created + Q3DSElement newCylinder(m_presentation, QStringLiteral("Scene.Layer.New Cylinder")); + Q3DSElement newCylinder2(m_presentation, + QStringLiteral("Scene.Layer.New Cylinder.New Cylinder 2")); + Q3DSElement newGroup(m_presentation, QStringLiteral("Scene.Layer.New Group")); + Q3DSElement newSphere(m_presentation, QStringLiteral("Scene.Layer.Cube2.New Sphere")); + + QTimer animationTimer; + animationTimer.setInterval(10); + int animDir = 1; + QObject::connect(&animationTimer, &QTimer::timeout, [&]() { + if (qAbs(animValue) > 100) + animDir = -animDir; + animValue += animDir; + newCylinder.setAttribute(QStringLiteral("rotation.x"), animValue * 4); + newCylinder2.setAttribute(QStringLiteral("position.y"), animValue * 3); + newSphere.setAttribute(QStringLiteral("position.x"), 50 + animValue * 2); + newGroup.setAttribute(QStringLiteral("opacity"), qAbs(animValue)); + m_presentation->setDataInputValue(QStringLiteral("newDataInput"), qAbs(animValue / 2)); + }); + + // Create objects to slides 1 & 2 while slide 1 is executing + QTimer::singleShot(1000, [&]() { + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder 2")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Red")); + data.insert(QStringLiteral("starttime"), 500); + data.insert(QStringLiteral("endtime"), 5000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(50, animValue, 50))); + + createElement(QStringLiteral("Scene.Layer.New Cylinder"), + QStringLiteral("Slide1"), data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Sphere")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 1000); + data.insert(QStringLiteral("endtime"), 4000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(animValue, 75, 0))); + + createElement(QStringLiteral("Scene.Layer.Cube2"), QStringLiteral("Slide2"), data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("Sphere To Delete")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Red")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 10000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-100, -100, 0))); + + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide2"), data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Group")); + data.insert(QStringLiteral("type"), QStringLiteral("group")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 10000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(50, -100, 0))); + + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + + QVector<QHash<QString, QVariant>> groupElemProps; + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("Child 1 of Group")); + data.insert(QStringLiteral("type"), QStringLiteral("model")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 1000); + data.insert(QStringLiteral("endtime"), 4000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(0, 0, 0))); + groupElemProps << data; + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("Child 2 of Group")); + data.insert(QStringLiteral("type"), QStringLiteral("model")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 2000); + data.insert(QStringLiteral("endtime"), 4000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(100, 0, 0))); + groupElemProps << data; + + m_createdElements << QStringLiteral("Scene.Layer.New Group.Child 1 of Group") + << QStringLiteral("Scene.Layer.New Group.Child 2 of Group"); + + m_presentation->createElements(QStringLiteral("Scene.Layer.New Group"), + QStringLiteral("Slide1"), groupElemProps); + + animationTimer.start(); + }); + + // Switch to slide 2 + QVERIFY(spyExited.wait(20000)); + + // Remove dynamically added object + QTimer::singleShot(3000, [&]() { + m_presentation->deleteElement(QStringLiteral("Scene.Layer.Sphere To Delete")); + m_createdElements.removeOne(QStringLiteral("Scene.Layer.Sphere To Delete")); + }); + + // Create objects to slides 1 and 2 while slide 2 is executing + QTimer::singleShot(2000, [&]() { + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Cylinder 3")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 3000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-100, -100, 0))); + + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + + data.clear(); + data.insert(QStringLiteral("name"), QStringLiteral("New Sphere 2")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Green")); + data.insert(QStringLiteral("starttime"), 0); + data.insert(QStringLiteral("endtime"), 5000); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-100, 100, 0))); + + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide2"), data); + }); + + // Switch to slide 1 + QVERIFY(spyExited.wait(20000)); + + QRandomGenerator rnd; + QVector<QHash<QString, QVariant>> massProps; + for (int i = 0; i < 1000; ++i) { + data.clear(); + QString elementName = QStringLiteral("MassElement_%1").arg(i); + data.insert(QStringLiteral("name"), elementName); + data.insert(QStringLiteral("sourcepath"), + i % 2 ? QStringLiteral("#Cube") : QStringLiteral("#Cone")); + data.insert(QStringLiteral("material"), + i % 2 ? QStringLiteral("Basic Green") : QStringLiteral("Basic Red")); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(rnd.bounded(-600, 600), + rnd.bounded(-600, 600), + rnd.bounded(800, 1200)))); + massProps << data; + m_createdElements << QStringLiteral("Scene.Layer.") + elementName; + } + m_presentation->createElements(QStringLiteral("Scene.Layer"), QStringLiteral("Slide2"), + massProps); + + // Switch to slide 2 + QVERIFY(spyExited.wait(20000)); + + QTest::qWait(500); + QCOMPARE(spyElemCreated.count(), 9); + const QStringList createdElements = m_presentation->createdElements(); + QCOMPARE(createdElements.size(), m_createdElements.size()); + for (const auto &elementName : createdElements) + QVERIFY(m_createdElements.contains(elementName)); + deleteCreated(); + + // Switch to slide 1 + QVERIFY(spyExited.wait(20000)); + QTest::qWait(1000); +} + +void tst_qt3dsviewer::testCreateMaterial() +{ + m_viewer->show(); + + m_settings->setShowRenderStats(true); + m_settings->setScaleMode(Q3DSViewerSettings::ScaleModeFill); + + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + QSignalSpy spyMatCreated(m_presentation, SIGNAL(materialsCreated(const QStringList &, + const QString &))); + QSignalSpy spyElemCreated(m_presentation, SIGNAL(elementsCreated(const QStringList &, + const QString &))); + + QStringList materialDefinitions; + // Create material via .materialdef file in resources + materialDefinitions + << QStringLiteral(":/scenes/simple_cube_animation/materials/Basic Blue.materialdef") + << QStringLiteral(":/scenes/simple_cube_animation/materials/Basic Texture.materialdef"); + + // Create material directly from materialdef content + auto loadMatDefFile = [&](const QString &fileName) -> QString { + QFile matDefFile(fileName); + if (!matDefFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return {}; + + QTextStream in(&matDefFile); + return in.readAll(); + }; + QString matDef = loadMatDefFile( + QStringLiteral(":/scenes/simple_cube_animation/materials/Copper.materialdef")); + QVERIFY(!matDef.isEmpty()); + materialDefinitions << matDef; + + m_presentation->createMaterials(materialDefinitions); + m_createdMaterials << QStringLiteral("materials/Basic Blue") + << QStringLiteral("materials/Basic Texture") + << QStringLiteral("materials/Copper"); + + QObject::connect(m_presentation, &Q3DSPresentation::materialsCreated, + [this](const QStringList &materialNames, const QString &error) { + QCOMPARE(error, QString()); + for (auto &name : materialNames) { + QVERIFY(m_createdMaterials.contains(name)); + QHash<QString, QVariant> data; + if (name == QLatin1String("materials/Basic Blue")) { + data.insert(QStringLiteral("name"), QStringLiteral("Blue Cylinder")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cylinder")); + data.insert(QStringLiteral("material"), name); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(200, 300, 200))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + } else if (name == QLatin1String("materials/Basic Texture")) { + data.insert(QStringLiteral("name"), QStringLiteral("Textured Cone")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cone")); + data.insert(QStringLiteral("material"), name); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-200, -300, 200))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + } else if (name == QLatin1String("materials/Copper")) { + data.insert(QStringLiteral("name"), QStringLiteral("Copper Sphere")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Sphere")); + data.insert(QStringLiteral("material"), name); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-200, 300, 200))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + } else if (name == QLatin1String("materials/Just Yellow")) { + QHash<QString, QVariant> data; + data.insert(QStringLiteral("name"), QStringLiteral("Yellow Cube")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cube")); + data.insert(QStringLiteral("material"), name); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(200, -300, 200))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + } else { + QVERIFY(false); + } + } + }); + + // Create material after start + QTimer::singleShot(1000, [&]() { + QString md = loadMatDefFile(QStringLiteral( + ":/scenes/simple_cube_animation/materials/Basic Blue.materialdef")); + // Modify the diffuse color and material name so that we can be sure it is a new one + md.replace(QStringLiteral("Basic Blue"), QStringLiteral("Just Yellow")); + md.replace(QRegularExpression(QStringLiteral("\"diffuse\">.*<")), + QStringLiteral("\"diffuse\">1 1 0 1<")); + m_presentation->createMaterial(md); + m_createdMaterials << QStringLiteral("materials/Just Yellow"); + }); + + // Delete material + QTimer::singleShot(2500, [&]() { + m_presentation->deleteElement(QStringLiteral("Scene.Layer.Textured Cone")); + m_presentation->deleteMaterial("materials/Basic Texture"); + m_createdMaterials.removeOne(QStringLiteral("materials/Basic Texture")); + + // Try to use the deleted material - should find a fallback material + QHash<QString, QVariant> data; + data.insert(QStringLiteral("name"), QStringLiteral("Textured Cone 2")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("#Cone")); + data.insert(QStringLiteral("material"), QStringLiteral("materials/Basic Texture")); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(-100, -300, 200))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + }); + + QVERIFY(spyExited.wait(20000)); + QCOMPARE(spyMatCreated.count(), 2); + QCOMPARE(spyElemCreated.count(), 5); + const QStringList createdMaterials = m_presentation->createdMaterials(); + QCOMPARE(createdMaterials.size(), m_createdMaterials.size()); + for (const auto &name : createdMaterials) + QVERIFY(m_createdMaterials.contains(name)); + deleteCreated(); + QTest::qWait(200); // Extra wait to verify slide change visually +} + +void tst_qt3dsviewer::testCreateMesh() +{ + m_viewer->show(); + + m_settings->setShowRenderStats(true); + m_settings->setScaleMode(Q3DSViewerSettings::ScaleModeFill); + + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + QSignalSpy spyMeshCreated(m_presentation, SIGNAL(meshesCreated(const QStringList &, + const QString &))); + QSignalSpy spyElemCreated(m_presentation, SIGNAL(elementsCreated(const QStringList &, + const QString &))); + Q3DSGeometry pyramid; + Q3DSGeometry star; + createGeometries(pyramid, star); + + Q3DSElement pyramidElem(m_presentation, QStringLiteral("Scene.Layer.Pyramid")); + Q3DSElement starElem(m_presentation, QStringLiteral("Scene.Layer.Star")); + + int animValue = 0; + QTimer animationTimer; + animationTimer.setInterval(10); + QObject::connect(&animationTimer, &QTimer::timeout, [&]() { + animValue++; + pyramidElem.setAttribute(QStringLiteral("rotation.x"), animValue * 2); + pyramidElem.setAttribute(QStringLiteral("rotation.y"), animValue); + starElem.setAttribute(QStringLiteral("rotation.x"), -animValue * 2); + starElem.setAttribute(QStringLiteral("rotation.y"), -animValue); + }); + + m_presentation->createMaterial( + QStringLiteral(":/scenes/simple_cube_animation/materials/Basic Texture.materialdef")); + m_createdMaterials << QStringLiteral("materials/Basic Texture"); + m_presentation->createMesh(QStringLiteral("Pyramid"), pyramid); + m_createdMeshes << QStringLiteral("Pyramid"); + + QObject::connect(m_presentation, &Q3DSPresentation::meshesCreated, + [&](const QStringList &meshNames, const QString &error) { + QCOMPARE(error, QString()); + for (auto &name : meshNames) { + QVERIFY(m_createdMeshes.contains(name)); + QHash<QString, QVariant> data; + if (name == QLatin1String("Pyramid")) { + data.insert(QStringLiteral("name"), QStringLiteral("Pyramid")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("Pyramid")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Texture")); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(100, 150, 500))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + animationTimer.start(); + } else if (name == QLatin1String("Star")) { + data.insert(QStringLiteral("name"), QStringLiteral("Star")); + data.insert(QStringLiteral("sourcepath"), QStringLiteral("Star")); + data.insert(QStringLiteral("material"), QStringLiteral("Basic Texture")); + data.insert(QStringLiteral("position"), + QVariant::fromValue<QVector3D>(QVector3D(100, -150, 500))); + createElement(QStringLiteral("Scene.Layer"), QStringLiteral("Slide1"), data); + } else { + QVERIFY(false); + } + } + }); + + // Create mesh after start + QTimer::singleShot(1000, [&]() { + m_presentation->createMesh(QStringLiteral("Star"), star); + m_createdMeshes << QStringLiteral("Star"); + }); + + QTimer::singleShot(3000, [&]() { + m_presentation->deleteElement(QStringLiteral("Scene.Layer.Star")); + m_presentation->deleteMesh(QStringLiteral("Star")); + m_createdMeshes.removeOne(QStringLiteral("Star")); + }); + + QVERIFY(spyExited.wait(20000)); + QCOMPARE(spyMeshCreated.count(), 2); + QCOMPARE(spyElemCreated.count(), 2); + const QStringList createdMeshes = m_presentation->createdMeshes(); + QCOMPARE(createdMeshes.size(), m_createdMeshes.size()); + for (const auto &name : createdMeshes) + QVERIFY(m_createdMeshes.contains(name)); + deleteCreated(); + QTest::qWait(200); // Extra wait to verify slide change visually +} + +void tst_qt3dsviewer::testMouseEvents() +{ + m_viewer->show(); + QTest::qWait(1000); + + QSignalSpy spyEvents(m_studio3DItem, + SIGNAL(ignoredEventsChanged())); + QSignalSpy spyExited(m_presentation, + SIGNAL(slideExited(const QString &, unsigned int, const QString &))); + + QCOMPARE(spyEvents.count(), 0); + QCOMPARE(spyExited.count(), 0); + + // Ignore mouse, so slide doesn't change + m_studio3DItem->setProperty("ignoredEvents", 1); + QTest::mousePress(m_viewer, Qt::LeftButton); + QTest::qWait(1000); + QTest::mouseRelease(m_viewer, Qt::LeftButton); + QCOMPARE(spyEvents.count(), 1); + QCOMPARE(spyExited.count(), 0); + + // Enable mouse, clicking switches slide + m_studio3DItem->setProperty("ignoredEvents", 0); + QTest::mousePress(m_viewer, Qt::LeftButton); + QTest::qWait(1000); + QTest::mouseRelease(m_viewer, Qt::LeftButton); + QCOMPARE(spyEvents.count(), 2); + QCOMPARE(spyExited.count(), 1); +} + +void tst_qt3dsviewer::deleteCreated() +{ + m_presentation->deleteElements(m_createdElements); + m_presentation->deleteMaterials(m_createdMaterials); + m_presentation->deleteMeshes(m_createdMeshes); + m_createdElements.clear(); + m_createdMaterials.clear(); + m_createdMeshes.clear(); + QVERIFY(m_presentation->createdElements().size() == 0); + QVERIFY(m_presentation->createdMaterials().size() == 0); + QVERIFY(m_presentation->createdMeshes().size() == 0); +} + +void tst_qt3dsviewer::createElement(const QString &parentElementPath, const QString &slideName, + const QHash<QString, QVariant> &properties) +{ + m_createdElements << parentElementPath + QLatin1Char('.') + + properties[QStringLiteral("name")].toString(); + m_presentation->createElement(parentElementPath, slideName, properties); +} + +void tst_qt3dsviewer::createGeometries(Q3DSGeometry &pyramid, Q3DSGeometry &star) +{ + struct Vertex { + QVector3D position; + QVector3D normal; + QVector2D uv; + }; + + QVector<Vertex> vertices; + + auto createVertex = [&](const QVector3D &xyz, const QVector3D &n, const QVector2D &uv) { + Vertex newVertex; + newVertex.position = xyz; + if (n.isNull()) + newVertex.normal = xyz; // This is almost never the correct normal + else + newVertex.normal = n.normalized(); + newVertex.uv = uv; + vertices.append(newVertex); + }; + + auto createTriangle = [&](const QVector3D &xyz1, const QVector2D &uv1, + const QVector3D &xyz2, const QVector2D &uv2, + const QVector3D &xyz3, const QVector2D &uv3) { + QVector3D n; + n = QVector3D::crossProduct(xyz2 - xyz1, xyz3 - xyz1).normalized(); + + createVertex(xyz1, n, uv1); + createVertex(xyz2, n, uv2); + createVertex(xyz3, n, uv3); + }; + + // Pyramid (no index buffer) + { + QVector3D xyz[5] = {{0, 0, 50}, {50, 50, -50}, {50, -50, -50}, {-50, -50, -50}, + {-50, 50, -50}}; + QVector2D uv[4] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}}; + createTriangle(xyz[0], uv[0], xyz[1], uv[1], xyz[2], uv[2]); + createTriangle(xyz[0], uv[0], xyz[2], uv[1], xyz[3], uv[2]); + createTriangle(xyz[0], uv[0], xyz[3], uv[1], xyz[4], uv[2]); + createTriangle(xyz[0], uv[0], xyz[4], uv[1], xyz[1], uv[2]); + createTriangle(xyz[1], uv[0], xyz[4], uv[2], xyz[3], uv[1]); + createTriangle(xyz[1], uv[0], xyz[3], uv[3], xyz[2], uv[2]); + + QByteArray vertexBuffer(reinterpret_cast<const char *>(vertices.constData()), + vertices.size() * int(sizeof(Vertex))); + + pyramid.clear(); + pyramid.setVertexData(vertexBuffer); + pyramid.addAttribute(Q3DSGeometry::Attribute::PositionSemantic); + pyramid.addAttribute(Q3DSGeometry::Attribute::NormalSemantic); + pyramid.addAttribute(Q3DSGeometry::Attribute::TexCoordSemantic); + } + + vertices.clear(); + + // Star (using index buffer) + { + // Note: Since faces share vertices, the normals on the vertices are not correct + // for any face, leading to weird lighting behavior + createVertex({0, 150, 0}, {}, {0.5f, 1}); + createVertex({50, 50, -50}, {}, {0.66f, 0.66f}); + createVertex({150, 0, 0}, {}, {1, 0.5f}); + createVertex({50, -50, -50}, {}, {0.66f, 0.33f}); + createVertex({0, -150, 0}, {}, {0.5f, 0}); + createVertex({-50, -50, -50}, {}, {0.33f, 0.33f}); + createVertex({-150, 0, 0}, {}, {0, 0.5f}); + createVertex({-50, 50, -50}, {}, {0.33f, 0.66f}); + createVertex({50, 50, 50}, {}, {0.66f, 0.66f}); + createVertex({50, -50, 50}, {}, {0.66f, 0.33f}); + createVertex({-50, -50, 50}, {}, {0.33f, 0.33f}); + createVertex({-50, 50, 50}, {}, {0.33f, 0.66f}); + + QVector<quint16> indices = { + 0, 1, 8, 0, 7, 1, 0, 11, 7, 0, 8, 11, // Top pyramid + 2, 1, 3, 2, 3, 9, 2, 9, 8, 2, 8, 1, // Right pyramid + 4, 3, 5, 4, 5, 10, 4, 10, 9, 4, 9, 3, // Bottom pyramid + 6, 5, 7, 6, 7, 11, 6, 11, 10, 6, 10, 5, // Left pyramid + 1, 7, 5, 1, 5, 3, // Front center rect + 8, 10, 11, 8, 9, 10 // Back center rect + }; + + QByteArray vertexBuffer(reinterpret_cast<const char *>(vertices.constData()), + vertices.size() * int(sizeof(Vertex))); + QByteArray indexBuffer(reinterpret_cast<const char *>(indices.constData()), + indices.size() * int(sizeof(quint16))); + + Q3DSGeometry::Attribute indexAtt; + indexAtt.semantic = Q3DSGeometry::Attribute::IndexSemantic; + indexAtt.componentType = Q3DSGeometry::Attribute::ComponentType::U16Type; + + star.clear(); + star.setVertexData(vertexBuffer); + star.setIndexData(indexBuffer); + star.addAttribute(Q3DSGeometry::Attribute::PositionSemantic); + star.addAttribute(Q3DSGeometry::Attribute::NormalSemantic); + star.addAttribute(Q3DSGeometry::Attribute::TexCoordSemantic); + star.addAttribute(indexAtt); + } +} + +QTEST_MAIN(tst_qt3dsviewer) diff --git a/tests/auto/viewer/tst_qt3dsviewer.h b/tests/auto/viewer/tst_qt3dsviewer.h new file mode 100644 index 0000000..3d1f947 --- /dev/null +++ b/tests/auto/viewer/tst_qt3dsviewer.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2012 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $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$ +** +****************************************************************************/ + +#ifndef TST_QT3DSVIEWER +#define TST_QT3DSVIEWER + +#include <QtTest/QtTest> +#include <QtTest/QSignalSpy> +#include <QtQuick/QQuickView> +#include <QtStudio3D/q3dspresentation.h> +#include <QtStudio3D/q3dsviewersettings.h> + +class tst_qt3dsviewer : public QObject +{ + Q_OBJECT +public: + tst_qt3dsviewer() + { + } + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testEmpty(); + void testLoading(); + void testSlides(); + void testFrameUpdates(); + void testSettings(); + void testCreateElement(); + void testCreateMaterial(); + void testCreateMesh(); + void testMouseEvents(); + +private: + void deleteCreated(); + void createElement(const QString &parentElementPath, const QString &slideName, + const QHash<QString, QVariant> &properties); + void createGeometries(Q3DSGeometry &pyramid, Q3DSGeometry &star); + + QQuickView *m_viewer = nullptr; + QObject *m_studio3DItem = nullptr; + Q3DSPresentation *m_presentation = nullptr; + Q3DSViewerSettings *m_settings = nullptr; + QStringList m_createdElements; + QStringList m_createdMaterials; + QStringList m_createdMeshes; + bool m_ignoreError = false; + +}; + +#endif // TST_QT3DSVIEWER diff --git a/tests/auto/viewer/tst_qt3dsviewer.qml b/tests/auto/viewer/tst_qt3dsviewer.qml new file mode 100644 index 0000000..e65a49d --- /dev/null +++ b/tests/auto/viewer/tst_qt3dsviewer.qml @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtStudio3D.OpenGL 2.4 + +Studio3D { + id: studio3D + width: 800 + height: 800 + Presentation { + source: "qrc:/scenes/simple_cube_animation/simple_cube_animation.uia" + } + ViewerSettings { + } +} diff --git a/tests/auto/viewer/viewer.pro b/tests/auto/viewer/viewer.pro new file mode 100644 index 0000000..9a8da77 --- /dev/null +++ b/tests/auto/viewer/viewer.pro @@ -0,0 +1,21 @@ +TEMPLATE = app +CONFIG += testcase +include($$PWD/../../../commoninclude.pri) + +TARGET = tst_qt3dsviewer +QT += testlib gui quick studio3d +RESOURCES += viewer.qrc + +HEADERS += \ + tst_qt3dsviewer.h + +SOURCES += \ + tst_qt3dsviewer.cpp + +LIBS += \ + -lqt3dsopengl$$qtPlatformTargetSuffix() \ + -lqt3dsqmlstreamer$$qtPlatformTargetSuffix() + +ANDROID_EXTRA_LIBS = \ + libqt3dsopengl.so \ + libqt3dsqmlstreamer.so diff --git a/tests/auto/viewer/viewer.qrc b/tests/auto/viewer/viewer.qrc new file mode 100644 index 0000000..dcdddbe --- /dev/null +++ b/tests/auto/viewer/viewer.qrc @@ -0,0 +1,16 @@ +<RCC> + <qresource prefix="/"> + <file>../../scenes/simple_cube_animation/simple_cube_animation.uia</file> + <file>../../scenes/simple_cube_animation/presentations/simple_cube_animation.uip</file> + <file>../../scenes/simple_cube_animation/materials/Basic Green.materialdef</file> + <file>../../scenes/simple_cube_animation/materials/Basic Red.materialdef</file> + <file>../../scenes/simple_cube_animation/materials/Basic Blue.materialdef</file> + <file>../../scenes/simple_cube_animation/materials/Basic Texture.materialdef</file> + <file>../../scenes/simple_cube_animation/materials/Copper.materialdef</file> + <file>../../scenes/simple_cube_animation/materials/copper.shader</file> + <file>../../scenes/simple_cube_animation/maps/materials/shadow.png</file> + <file>../../scenes/simple_cube_animation/maps/materials/spherical_checker.png</file> + <file>../../scenes/simple_cube_animation/maps/QT-symbol.png</file> + <file>tst_qt3dsviewer.qml</file> + </qresource> +</RCC> |