// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtTest/qtest.h>
#include <QtCore/qtemporarydir.h>
#include <QtGui/qimage.h>

#include <private/qsceneimporter_p.h>
#include <private/qsceneexportfactory_p.h>
#include <private/qsceneexporter_p.h>
#include <private/qsceneimportfactory_p.h>
#include <private/qsceneimporter_p.h>

#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>

#include <Qt3DRender/qcamera.h>
#include <Qt3DRender/qcameralens.h>
#include <Qt3DRender/qtextureimage.h>
#include <Qt3DRender/qspotlight.h>
#include <Qt3DRender/qdirectionallight.h>
#include <Qt3DRender/qpointlight.h>
#include <Qt3DCore/qattribute.h>
#include <Qt3DCore/qbuffer.h>
#include <Qt3DRender/qeffect.h>
#include <Qt3DRender/qshaderprogram.h>
#include <Qt3DRender/qtechnique.h>
#include <Qt3DRender/qparameter.h>
#include <Qt3DRender/qgraphicsapifilter.h>
#include <Qt3DRender/qfilterkey.h>
#include <Qt3DRender/qtexture.h>
#include <Qt3DRender/qcolormask.h>
#include <Qt3DRender/qblendequation.h>
#include <Qt3DRender/qgeometryrenderer.h>

#include <Qt3DExtras/qconemesh.h>
#include <Qt3DExtras/qcuboidmesh.h>
#include <Qt3DExtras/qcylindermesh.h>
#include <Qt3DExtras/qplanemesh.h>
#include <Qt3DExtras/qspheremesh.h>
#include <Qt3DExtras/qtorusmesh.h>
#include <Qt3DExtras/qt3dwindow.h>
#include <Qt3DExtras/qphongmaterial.h>
#include <Qt3DExtras/qphongalphamaterial.h>
#include <Qt3DExtras/qdiffusemapmaterial.h>
#include <Qt3DExtras/qdiffusespecularmapmaterial.h>
#include <Qt3DExtras/qnormaldiffusemapmaterial.h>
#include <Qt3DExtras/qnormaldiffusemapalphamaterial.h>
#include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h>
#include <Qt3DExtras/qgoochmaterial.h>
#include <Qt3DExtras/qpervertexcolormaterial.h>
#include <Qt3DExtras/qforwardrenderer.h>

//#define VISUAL_CHECK 5000  // The value indicates the time for visual check in ms
//#define PRESERVE_EXPORT  // Uncomment to preserve export directory contents for analysis

class tst_gltfPlugins : public QObject
{
    Q_OBJECT

private Q_SLOTS:

    void initTestCase();
    void init();
    void cleanup();
    void exportAndImport_data();
    void exportAndImport();

private:
    void createTestScene();
    Qt3DCore::QEntity *findCameraChild(Qt3DCore::QEntity *entity,
                                       Qt3DRender::QCameraLens::ProjectionType type);
    void walkEntity(Qt3DCore::QEntity *entity, int depth);
    void createAndAddEntity(const QString &name,
                            Qt3DCore::QComponent *comp1 = nullptr,
                            Qt3DCore::QComponent *comp2 = nullptr,
                            Qt3DCore::QComponent *comp3 = nullptr,
                            Qt3DCore::QEntity *parent = nullptr);
    void addPositionAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                        Qt3DCore::QBuffer *buffer, int count);
    void addIndexAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                     Qt3DCore::QBuffer *buffer, int count);
    void addColorAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                     Qt3DCore::QBuffer *buffer, int count);
    Qt3DCore::QEntity *findChildEntity(Qt3DCore::QEntity *entity, const QString &name);
    Qt3DCore::QTransform *transformComponent(Qt3DCore::QEntity *entity);
    Qt3DRender::QAbstractLight *lightComponent(Qt3DCore::QEntity *entity);
    Qt3DRender::QCameraLens *cameraComponent(Qt3DCore::QEntity *entity);
    Qt3DRender::QGeometryRenderer *meshComponent(Qt3DCore::QEntity *entity);
    Qt3DRender::QMaterial *materialComponent(Qt3DCore::QEntity *entity);
    void compareComponents(Qt3DCore::QComponent *c1, Qt3DCore::QComponent *c2);
    Qt3DCore::QAttribute *findAttribute(const QString &name,
                                          Qt3DCore::QAttribute::AttributeType type,
                                          Qt3DCore::QGeometry *geometry);
    void compareAttributes(Qt3DCore::QAttribute *a1, Qt3DCore::QAttribute *a2);
    void compareParameters(const QList<Qt3DRender::QParameter *> &params1,
                           const QList<Qt3DRender::QParameter *> &params2);
    void compareRenderPasses(const QList<Qt3DRender::QRenderPass *> &passes1,
                             const QList<Qt3DRender::QRenderPass *> &passes2);
    void compareFilterKeys(const QList<Qt3DRender::QFilterKey *> &keys1,
                           const QList<Qt3DRender::QFilterKey *> &keys2);
    QUrl getTextureUrl(Qt3DRender::QAbstractTexture *tex);
    Qt3DRender::QGeometryRenderer *createCustomCube();
    Qt3DRender::QEffect *createOnTopEffect();

    QTemporaryDir *m_exportDir;
#ifdef VISUAL_CHECK
    Qt3DExtras::Qt3DWindow *m_view1;
    Qt3DExtras::Qt3DWindow *m_view2;
#endif
    Qt3DCore::QEntity *m_sceneRoot1;
    Qt3DCore::QEntity *m_sceneRoot2;
    QHash<QString, Qt3DCore::QEntity *> m_entityMap;
};

void tst_gltfPlugins::initTestCase()
{
#ifndef VISUAL_CHECK
    // QEffect doesn't get registered unless aspects are initialized, generating warnings in
    // material comparisons.
    qRegisterMetaType<Qt3DRender::QEffect *>();
#endif
}

void tst_gltfPlugins::init()
{
    m_sceneRoot1 = m_sceneRoot2 = nullptr;
    m_exportDir = new QTemporaryDir;
#ifdef VISUAL_CHECK
    m_view1 = new Qt3DExtras::Qt3DWindow;
    m_view1->setTitle(QStringLiteral("Original scene"));
    m_view2 = new Qt3DExtras::Qt3DWindow;
    m_view2->setTitle(QStringLiteral("Imported scene"));
#endif
}

void tst_gltfPlugins::cleanup()
{
    delete m_sceneRoot1;
    delete m_sceneRoot2;
    m_entityMap.clear();
#ifdef VISUAL_CHECK
    delete m_view1;
    delete m_view2;
#endif
    delete m_exportDir;
    // Make sure the slate is clean for the next case
    QCoreApplication::processEvents();
    QTest::qWait(0);
}

void tst_gltfPlugins::walkEntity(Qt3DCore::QEntity *entity, int depth)
{
    QString indent;
    indent.fill(' ', depth * 2);
    qDebug().noquote() << indent << "Entity:" << entity << "Components:" << entity->components();
    for (auto child : entity->children()) {
        if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(child))
            walkEntity(childEntity, depth + 1);
    }
}

void tst_gltfPlugins::createAndAddEntity(const QString &name,
                                         Qt3DCore::QComponent *comp1,
                                         Qt3DCore::QComponent *comp2,
                                         Qt3DCore::QComponent *comp3,
                                         Qt3DCore::QEntity *parent)
{
    Qt3DCore::QEntity *parentEntity = parent ? parent : m_sceneRoot1;
    Qt3DCore::QEntity *entity = new Qt3DCore::QEntity(parentEntity);
    entity->setObjectName(name);
    if (comp1) {
        entity->addComponent(comp1);
        comp1->setObjectName(comp1->metaObject()->className());
    }
    if (comp2) {
        entity->addComponent(comp2);
        comp2->setObjectName(comp2->metaObject()->className());
    }
    if (comp3) {
        entity->addComponent(comp3);
        comp3->setObjectName(comp3->metaObject()->className());
    }

    m_entityMap.insert(name, entity);
}

void tst_gltfPlugins::createTestScene()
{
#ifdef VISUAL_CHECK
    m_view1->defaultFrameGraph()->setClearColor(Qt::lightGray);
    m_view2->defaultFrameGraph()->setClearColor(Qt::lightGray);
#endif
    m_sceneRoot1 = new Qt3DCore::QEntity();
    m_sceneRoot1->setObjectName(QStringLiteral("Scene root"));
    m_sceneRoot2 = new Qt3DCore::QEntity();
    m_sceneRoot2->setObjectName(QStringLiteral("Imported scene parent"));

    // Perspective camera
    {
        Qt3DRender::QCamera *camera = new Qt3DRender::QCamera(m_sceneRoot1);
        camera->setProjectionType(Qt3DRender::QCameraLens::PerspectiveProjection);
        camera->setViewCenter(QVector3D(0.0f, 1.5f, 0.0f));
        camera->setPosition(QVector3D(0.0f, 3.5f, 15.0f));
        camera->setNearPlane(0.001f);
        camera->setFarPlane(10000.0f);
        camera->setObjectName(QStringLiteral("Main camera"));
        camera->transform()->setObjectName(QStringLiteral("Main camera transform"));
        camera->lens()->setObjectName(QStringLiteral("Main camera lens"));
        camera->setFieldOfView(30.0f);
        camera->setAspectRatio(1.0f);
        m_entityMap.insert(camera->objectName(), camera);
    }
    // Ortho camera
    {
        Qt3DCore::QEntity *camera = new Qt3DCore::QEntity(m_sceneRoot1);
        camera->setObjectName(QStringLiteral("Ortho camera"));

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 0.0f, -15.0f));
        transform->setObjectName(QStringLiteral("Ortho camera transform"));
        camera->addComponent(transform);

        Qt3DRender::QCameraLens *lens = new Qt3DRender::QCameraLens;
        lens->setProjectionType(Qt3DRender::QCameraLens::OrthographicProjection);
        lens->setNearPlane(0.001f);
        lens->setFarPlane(10000.0f);
        lens->setRight(7.0f);
        lens->setLeft(-7.0f);
        lens->setTop(5.0f);
        lens->setBottom(-5.0f);
        lens->setObjectName(QStringLiteral("Ortho camera lens"));
        camera->addComponent(lens);

        m_entityMap.insert(camera->objectName(), camera);
#ifdef VISUAL_CHECK
        m_view1->defaultFrameGraph()->setCamera(camera);
#endif
    }

    // Point light
    {
        Qt3DRender::QPointLight *light = new Qt3DRender::QPointLight;
        light->setColor(QColor("#FFDDAA"));
        light->setIntensity(0.9f);
        light->setConstantAttenuation(0.03f);
        light->setLinearAttenuation(0.04f);
        light->setQuadraticAttenuation(0.01f);
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(-6.0f, 2.0f, 3.0f));
        createAndAddEntity(QStringLiteral("Point Light"), light, transform);
    }
    // Directional light
    {
        Qt3DRender::QDirectionalLight *light = new Qt3DRender::QDirectionalLight;
        light->setColor(QColor("#BBCCEE"));
        light->setIntensity(0.75f);
        light->setWorldDirection(QVector3D(-1.0f, -1.0f, -1.0f));
        createAndAddEntity(QStringLiteral("Directional Light"), light);
    }
    // Spot light
    {
        Qt3DRender::QSpotLight *light = new Qt3DRender::QSpotLight;
        light->setColor(QColor("#5599DD"));
        light->setIntensity(2.0f);
        light->setConstantAttenuation(0.03f);
        light->setLinearAttenuation(0.04f);
        light->setQuadraticAttenuation(0.01f);
        light->setLocalDirection(QVector3D(0.0f, -1.0f, -1.0f));
        light->setCutOffAngle(30.0f);
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 5.0f, 0.0f));
        createAndAddEntity(QStringLiteral("Spot Light"), light, transform);
    }
    // Cube with DiffuseMap
    {
        Qt3DExtras::QDiffuseMapMaterial *material = new Qt3DExtras::QDiffuseMapMaterial();
        Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage();
        material->diffuse()->addTextureImage(diffuseTextureImage);
        material->setAmbient(QColor("#000088"));
        material->setSpecular(QColor("#FFFF00"));
        material->setShininess(30.0);
        material->setTextureScale(2.0f);
        diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png")));
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setScale(0.75f);
        transform->setTranslation(QVector3D(2.0f, 1.0f, -1.0f));
        Qt3DExtras::QCuboidMesh *mesh = new Qt3DExtras::QCuboidMesh;
        mesh->setXExtent(1.2f);
        mesh->setYExtent(1.1f);
        mesh->setZExtent(0.9f);
        mesh->setYZMeshResolution(QSize(2, 2));
        mesh->setYZMeshResolution(QSize(2, 3));
        mesh->setYZMeshResolution(QSize(3, 2));
        createAndAddEntity(QStringLiteral("Cube with DiffuseMap"), mesh, material, transform);
    }
    // Cone with PhongAlpha
    {
        Qt3DExtras::QPhongAlphaMaterial *material = new Qt3DExtras::QPhongAlphaMaterial();
        material->setAlpha(0.6f);
        material->setAmbient(QColor("#550000"));
        material->setDiffuse(QColor("#00FFFF"));
        material->setSpecular(QColor("#FFFF00"));
        material->setShininess(20.0f);
        material->setSourceRgbArg(Qt3DRender::QBlendEquationArguments::Source1Color);
        material->setSourceAlphaArg(Qt3DRender::QBlendEquationArguments::Source1Alpha);
        material->setDestinationRgbArg(Qt3DRender::QBlendEquationArguments::DestinationColor);
        material->setDestinationAlphaArg(Qt3DRender::QBlendEquationArguments::DestinationAlpha);
        material->setBlendFunctionArg(Qt3DRender::QBlendEquation::ReverseSubtract);
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(2.0f, 1.0f, 1.0f));
        transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -20.0f));
        Qt3DExtras::QConeMesh *mesh = new Qt3DExtras::QConeMesh;
        mesh->setRings(2);
        mesh->setSlices(16);
        mesh->setTopRadius(0.5f);
        mesh->setBottomRadius(1.5f);
        mesh->setLength(0.9f);
        createAndAddEntity(QStringLiteral("Cone with PhongAlpha"), mesh, material, transform);
    }
    // Cylinder with Phong
    {
        Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
        material->setAmbient(QColor("#220022"));
        material->setDiffuse(QColor("#6633AA"));
        material->setSpecular(QColor("#66AA33"));
        material->setShininess(50.0f);
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 1.0f, 1.0f));
        transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -45.0f));
        Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh;
        mesh->setRadius(0.5f);
        mesh->setRings(3);
        mesh->setLength(1.2f);
        mesh->setSlices(16);
        createAndAddEntity(QStringLiteral("Cylinder with Phong"), mesh, material, transform);
    }
    // Plane with DiffuseSpecularMap
    {
        Qt3DExtras::QDiffuseSpecularMapMaterial *material =
                new Qt3DExtras::QDiffuseSpecularMapMaterial();
        Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage();
        material->diffuse()->addTextureImage(diffuseTextureImage);
        diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png")));
        Qt3DRender::QTextureImage *specularTextureImage = new Qt3DRender::QTextureImage();
        material->specular()->addTextureImage(specularTextureImage);
        specularTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_specular.png")));

        material->setAmbient(QColor("#0000FF"));
        material->setTextureScale(3.0f);
        material->setShininess(15.0f);

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(-1.0f, 1.0f, 1.0f));
        transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 45.0f));

        Qt3DExtras::QPlaneMesh *mesh = new Qt3DExtras::QPlaneMesh;
        mesh->setMeshResolution(QSize(3, 3));
        mesh->setHeight(1.5f);
        mesh->setWidth(1.2f);
        createAndAddEntity(QStringLiteral("Plane with DiffuseSpecularMap"),
                           mesh, material, transform);
    }
    // Sphere with NormalDiffuseMap
    {
        Qt3DExtras::QNormalDiffuseMapMaterial *material =
                new Qt3DExtras::QNormalDiffuseMapMaterial();

        Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage();
        material->normal()->addTextureImage(normalTextureImage);
        normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png")));

        Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage();
        material->diffuse()->addTextureImage(diffuseTextureImage);
        diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png")));

        material->setAmbient(QColor("#000044"));
        material->setSpecular(QColor("#0000CC"));
        material->setShininess(9.0f);
        material->setTextureScale(4.0f);

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 1.0f, -10.0f));

        Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh;
        mesh->setRadius(2.0f);
        mesh->setRings(16);
        mesh->setSlices(16);
        mesh->setGenerateTangents(true);
        createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseMap"),
                           mesh, material, transform);
    }
    // Sphere with NormalDiffuseMapAlpha
    {
        Qt3DExtras::QNormalDiffuseMapAlphaMaterial *material =
                new Qt3DExtras::QNormalDiffuseMapAlphaMaterial();

        Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage();
        material->normal()->addTextureImage(normalTextureImage);
        normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png")));

        Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage();
        material->diffuse()->addTextureImage(diffuseTextureImage);
        diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_with_alpha.png")));

        material->setAmbient(QColor("#000044"));
        material->setSpecular(QColor("#0000CC"));
        material->setShininess(9.0f);
        material->setTextureScale(4.0f);

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(4.0f, 1.0f, -10.0f));

        Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh;
        mesh->setRadius(2.0f);
        mesh->setRings(16);
        mesh->setSlices(16);
        mesh->setGenerateTangents(true);
        createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseMapAlpha"),
                           mesh, material, transform);
    }
    // Sphere with NormalDiffuseSpecularMap
    {
        Qt3DExtras::QNormalDiffuseSpecularMapMaterial *material =
                new Qt3DExtras::QNormalDiffuseSpecularMapMaterial();

        Qt3DRender::QTextureImage *normalTextureImage = new Qt3DRender::QTextureImage();
        material->normal()->addTextureImage(normalTextureImage);
        normalTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_normal.png")));

        Qt3DRender::QTextureImage *diffuseTextureImage = new Qt3DRender::QTextureImage();
        material->diffuse()->addTextureImage(diffuseTextureImage);
        diffuseTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png")));

        Qt3DRender::QTextureImage *specularTextureImage = new Qt3DRender::QTextureImage();
        material->specular()->addTextureImage(specularTextureImage);
        specularTextureImage->setSource(QUrl(QStringLiteral("qrc:/qtlogo_specular.png")));

        material->setAmbient(QColor("#000044"));
        material->setShininess(9.0f);
        material->setTextureScale(4.0f);

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(-4.0f, 1.0f, -10.0f));

        Qt3DExtras::QSphereMesh *mesh = new Qt3DExtras::QSphereMesh;
        mesh->setRadius(2.0f);
        mesh->setRings(16);
        mesh->setSlices(16);
        mesh->setGenerateTangents(true);
        createAndAddEntity(QStringLiteral("Sphere with NormalDiffuseSpecularMap"),
                           mesh, material, transform);
    }
    // Torus with Gooch
    {
        Qt3DExtras::QGoochMaterial *material = new Qt3DExtras::QGoochMaterial();

        material->setDiffuse(QColor("#333333"));
        material->setSpecular(QColor("#550055"));
        material->setCool(QColor("#0055AA"));
        material->setWarm(QColor("#FF3300"));
        material->setAlpha(0.2f);
        material->setBeta(0.4f);
        material->setShininess(22.0f);

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 4.0f, -10.0f));

        Qt3DExtras::QTorusMesh *mesh = new Qt3DExtras::QTorusMesh;
        mesh->setRadius(1.0f);
        mesh->setMinorRadius(0.5f);
        mesh->setRings(16);
        mesh->setSlices(16);
        createAndAddEntity(QStringLiteral("Torus with Gooch"), mesh, material, transform);
    }
    // Custom cube with per-vertex colors
    {
        Qt3DExtras::QPerVertexColorMaterial *material = new Qt3DExtras::QPerVertexColorMaterial();
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(4.0f, 3.0f, -15.0f));
        transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 1.0f, 1.0f, 270.0f));

        Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube();
        Qt3DCore::QBuffer *colorDataBuffer =
            new Qt3DCore::QBuffer(boxMesh->geometry());
        QByteArray colorBufferData;
        colorBufferData.resize(8 * 4 * sizeof(float));

        float *cPtr = reinterpret_cast<float *>(colorBufferData.data());
        for (int i = 0; i < 8; i++) {
            cPtr[i * 4] = float(i) / 8.0f;
            cPtr[i * 4 + 1] = float(8 - i) / 8.0f;
            cPtr[i * 4 + 2] = float((i + 4) % 8) / 8.0f;
            cPtr[i * 4 + 3] = 1.0f;
        }

        colorDataBuffer->setData(colorBufferData);

        addColorAttributeToGeometry(boxMesh->geometry(), colorDataBuffer, 8);

        createAndAddEntity(QStringLiteral("Custom cube with per-vertex colors"),
                           boxMesh, material, transform);
    }
    // Child cylinder with Phong
    {
        Qt3DCore::QEntity *parentEntity = findChildEntity(m_sceneRoot1,
                                                          QStringLiteral("Cylinder with Phong"));
        Qt3DExtras::QPhongMaterial *material = new Qt3DExtras::QPhongMaterial();
        material->setAmbient(QColor("#333333"));
        material->setDiffuse(QColor("#88FF00"));
        material->setSpecular(QColor("#000088"));
        material->setShininess(150.0f);
        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 4.0f, 0.0f));
        Qt3DExtras::QCylinderMesh *mesh = new Qt3DExtras::QCylinderMesh;
        mesh->setRadius(0.25f);
        mesh->setRings(3);
        mesh->setLength(1.5f);
        mesh->setSlices(16);
        createAndAddEntity(QStringLiteral("Child with Phong"),
                           mesh, material, transform, parentEntity);
    }
    // Cube with custom material
    {
        Qt3DRender::QMaterial *material = new Qt3DRender::QMaterial;
        material->setEffect(createOnTopEffect());
        material->addParameter(new Qt3DRender::QParameter(QStringLiteral("globalOffset"),
                                                          QVector3D(-3.0f, 0.0f, 3.0f)));
        material->addParameter(new Qt3DRender::QParameter(QStringLiteral("extraYOffset"), 3));
        material->effect()->addParameter(new Qt3DRender::QParameter(QStringLiteral("handleColor"),
                                                                    QColor(Qt::magenta)));
        material->effect()->addParameter(new Qt3DRender::QParameter(QStringLiteral("reverseOffset"),
                                                                    QVariant::fromValue(true)));

        Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
        transform->setTranslation(QVector3D(0.0f, 2.0f, -40.0f));
        transform->setRotation(Qt3DCore::QTransform::fromAxisAndAngle(1.0f, 2.0f, 3.0f, 90.0f));
        Qt3DRender::QGeometryRenderer *boxMesh = createCustomCube();
        Qt3DCore::QBuffer *offsetBuffer =
            new Qt3DCore::QBuffer(boxMesh->geometry());
        QByteArray offsetBufferData;
        offsetBufferData.resize(8 * 3 * sizeof(float));

        float *oPtr = reinterpret_cast<float *>(offsetBufferData.data());
        for (int i = 0; i < 8; i++) {
            oPtr[i * 3] = float(i) / 4.0f;
            oPtr[i * 3 + 1] = float(8 - i) / 4.0f + 2.0f;
            oPtr[i * 3 + 2] = float((i + 4) % 8) / 4.0f;
        }

        offsetBuffer->setData(offsetBufferData);

        Qt3DCore::QAttribute *customAttribute = new Qt3DCore::QAttribute();
        customAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute);
        customAttribute->setBuffer(offsetBuffer);
        customAttribute->setVertexBaseType(Qt3DCore::QAttribute::Float);
        customAttribute->setVertexSize(3);
        customAttribute->setByteOffset(0);
        customAttribute->setByteStride(0);
        customAttribute->setCount(8);
        customAttribute->setName(QStringLiteral("vertexOffset"));

        boxMesh->geometry()->addAttribute(customAttribute);

        createAndAddEntity(QStringLiteral("Custom cube with on-top material"),
                           boxMesh, material, transform);
    }

#ifdef VISUAL_CHECK
    m_view1->setGeometry(30, 30, 400, 400);
    m_view1->setRootEntity(m_sceneRoot1);
    m_view1->show();

    m_view2->setGeometry(450, 30, 400, 400);
    m_view2->setRootEntity(m_sceneRoot2);
    m_view2->show();

    QTest::qWaitForWindowExposed(m_view1);
    QTest::qWaitForWindowExposed(m_view2);
#endif
}

void tst_gltfPlugins::addPositionAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                                     Qt3DCore::QBuffer *buffer, int count)
{
    Qt3DCore::QAttribute *posAttribute = new Qt3DCore::QAttribute();
    posAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute);
    posAttribute->setBuffer(buffer);
    posAttribute->setVertexBaseType(Qt3DCore::QAttribute::Float);
    posAttribute->setVertexSize(3);
    posAttribute->setByteOffset(0);
    posAttribute->setByteStride(0);
    posAttribute->setCount(count);
    posAttribute->setName(Qt3DCore::QAttribute::defaultPositionAttributeName());

    geometry->addAttribute(posAttribute);
}

void tst_gltfPlugins::addIndexAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                                  Qt3DCore::QBuffer *buffer, int count)
{
    Qt3DCore::QAttribute *indexAttribute = new Qt3DCore::QAttribute();
    indexAttribute->setAttributeType(Qt3DCore::QAttribute::IndexAttribute);
    indexAttribute->setBuffer(buffer);
    indexAttribute->setVertexBaseType(Qt3DCore::QAttribute::UnsignedShort);
    indexAttribute->setVertexSize(1);
    indexAttribute->setByteOffset(0);
    indexAttribute->setByteStride(0);
    indexAttribute->setCount(count);

    geometry->addAttribute(indexAttribute);
}

void tst_gltfPlugins::addColorAttributeToGeometry(Qt3DCore::QGeometry *geometry,
                                                  Qt3DCore::QBuffer *buffer, int count)
{
    Qt3DCore::QAttribute *colorAttribute = new Qt3DCore::QAttribute();
    colorAttribute->setAttributeType(Qt3DCore::QAttribute::VertexAttribute);
    colorAttribute->setBuffer(buffer);
    colorAttribute->setVertexBaseType(Qt3DCore::QAttribute::Float);
    colorAttribute->setVertexSize(4);
    colorAttribute->setByteOffset(0);
    colorAttribute->setByteStride(0);
    colorAttribute->setCount(count);
    colorAttribute->setName(Qt3DCore::QAttribute::defaultColorAttributeName());

    geometry->addAttribute(colorAttribute);
}

Qt3DCore::QEntity *tst_gltfPlugins::findChildEntity(Qt3DCore::QEntity *entity, const QString &name)
{
    for (auto child : entity->children()) {
        if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(child)) {
            if (childEntity->objectName() == name)
                return childEntity;
            if (auto foundEntity = findChildEntity(childEntity, name))
                return foundEntity;
        }
    }
    return nullptr;
}

Qt3DCore::QTransform *tst_gltfPlugins::transformComponent(Qt3DCore::QEntity *entity)
{
    for (auto component : entity->components()) {
        if (auto castedComponent = qobject_cast<Qt3DCore::QTransform *>(component))
            return castedComponent;
    }
    return nullptr;
}

Qt3DRender::QAbstractLight *tst_gltfPlugins::lightComponent(Qt3DCore::QEntity *entity)
{
    for (auto component : entity->components()) {
        if (auto castedComponent = qobject_cast<Qt3DRender::QAbstractLight *>(component))
            return castedComponent;
    }
    return nullptr;
}

Qt3DRender::QCameraLens *tst_gltfPlugins::cameraComponent(Qt3DCore::QEntity *entity)
{
    for (auto component : entity->components()) {
        if (auto castedComponent = qobject_cast<Qt3DRender::QCameraLens *>(component))
            return castedComponent;
    }
    return nullptr;
}

Qt3DRender::QGeometryRenderer *tst_gltfPlugins::meshComponent(Qt3DCore::QEntity *entity)
{
    for (auto component : entity->components()) {
        if (auto castedComponent = qobject_cast<Qt3DRender::QGeometryRenderer *>(component))
            return castedComponent;
    }
    return nullptr;
}

Qt3DRender::QMaterial *tst_gltfPlugins::materialComponent(Qt3DCore::QEntity *entity)
{
    for (auto component : entity->components()) {
        if (auto castedComponent = qobject_cast<Qt3DRender::QMaterial *>(component))
            return castedComponent;
    }
    return nullptr;
}

void tst_gltfPlugins::compareComponents(Qt3DCore::QComponent *c1, Qt3DCore::QComponent *c2)
{
    // Make sure component classes are the same and the non-pointer properties are the same
    QCOMPARE((c1 == nullptr), (c2 == nullptr));
    if (c1) {
        // Transform names are lost in export, as the transform is just part of the node item
        if (!qobject_cast<Qt3DCore::QTransform *>(c1))
            QCOMPARE(c1->objectName(), c2->objectName());
        QCOMPARE(c1->metaObject()->className(), c2->metaObject()->className());
        // Meshes are all imported as generic meshes
        if (auto mesh1 = qobject_cast<Qt3DRender::QGeometryRenderer *>(c1)) {
            auto mesh2 = qobject_cast<Qt3DRender::QGeometryRenderer *>(c2);
            QVERIFY(mesh2 != nullptr);
            auto geometry1 = mesh1->geometry();
            if (!geometry1 && mesh1->view())
                geometry1 = mesh1->view()->geometry();
            auto geometry2 = mesh2->geometry();
            if (!geometry2 && mesh2->view())
                geometry2 = mesh2->view()->geometry();
            // Check that attributes match.
            compareAttributes(
                        findAttribute(Qt3DCore::QAttribute::defaultPositionAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry1),
                        findAttribute(Qt3DCore::QAttribute::defaultPositionAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry2));
            compareAttributes(
                        findAttribute(Qt3DCore::QAttribute::defaultNormalAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry1),
                        findAttribute(Qt3DCore::QAttribute::defaultNormalAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry2));
            compareAttributes(
                        findAttribute(Qt3DCore::QAttribute::defaultTangentAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry1),
                        findAttribute(Qt3DCore::QAttribute::defaultTangentAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry2));
            compareAttributes(
                        findAttribute(Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry1),
                        findAttribute(Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry2));
            compareAttributes(
                        findAttribute(Qt3DCore::QAttribute::defaultColorAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry1),
                        findAttribute(Qt3DCore::QAttribute::defaultColorAttributeName(),
                                      Qt3DCore::QAttribute::VertexAttribute,
                                      geometry2));
            compareAttributes(
                        findAttribute(QStringLiteral(""),
                                      Qt3DCore::QAttribute::IndexAttribute,
                                      geometry1),
                        findAttribute(QStringLiteral(""),
                                      Qt3DCore::QAttribute::IndexAttribute,
                                      geometry2));
        } else {
            int count = c1->metaObject()->propertyCount();
            for (int i = 0; i < count; i++) {
                auto property = c1->metaObject()->property(i);
                auto v1 = c1->property(property.name());
                auto v2 = c2->property(property.name());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                const auto v1TypeId = v1.metaType().id();
#else
                const auto v1TypeId = v1.type();
#endif
                if (v1TypeId == QMetaType::Bool) {
                    QCOMPARE(v1.toBool(), v2.toBool());
                } else if (v1TypeId == QMetaType::QColor) {
                    QCOMPARE(v1.value<QColor>(), v2.value<QColor>());
                } else if (v1TypeId == QMetaType::QVector3D) {
                    QCOMPARE(v1.value<QVector3D>(), v2.value<QVector3D>());
                } else if (v1TypeId == QMetaType::QMatrix4x4) {
                    QCOMPARE(v1.value<QMatrix4x4>(), v2.value<QMatrix4x4>());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                } else if (QMetaType::canConvert(v1.metaType(), QMetaType(QMetaType::Float))) {
#else
                } else if (v1.canConvert(QMetaType::Float)) {
#endif
                    QVERIFY(qFuzzyCompare(v1.toFloat(), v2.toFloat()));
                }
            }
            if (QString::fromLatin1(c1->metaObject()->className())
                    .endsWith(QStringLiteral("Qt3DRender::QMaterial"))) {
                auto m1 = qobject_cast<Qt3DRender::QMaterial *>(c1);
                auto m2 = qobject_cast<Qt3DRender::QMaterial *>(c2);
                QVERIFY(m1);
                QVERIFY(m2);
                auto e1 = m1->effect();
                auto e2 = m2->effect();
                QVERIFY(e1);
                QVERIFY(e2);
                QCOMPARE(e1->objectName(), e2->objectName());
                QCOMPARE(e1->techniques().size(), e2->techniques().size());

                compareParameters(m1->parameters(), m2->parameters());
                compareParameters(e1->parameters(), e2->parameters());

                for (auto t1 : e1->techniques()) {
                    bool techMatch = false;
                    for (auto t2 : e2->techniques()) {
                        if (t1->objectName() == t2->objectName()) {
                            techMatch = true;
                            compareParameters(t1->parameters(), t2->parameters());
                            compareFilterKeys(t1->filterKeys(), t2->filterKeys());
                            compareRenderPasses(t1->renderPasses(), t2->renderPasses());
                            QCOMPARE(t1->graphicsApiFilter()->api(),
                                     t2->graphicsApiFilter()->api());
                            QCOMPARE(t1->graphicsApiFilter()->profile(),
                                     t2->graphicsApiFilter()->profile());
                            QCOMPARE(t1->graphicsApiFilter()->minorVersion(),
                                     t2->graphicsApiFilter()->minorVersion());
                            QCOMPARE(t1->graphicsApiFilter()->majorVersion(),
                                     t2->graphicsApiFilter()->majorVersion());
                            QCOMPARE(t1->graphicsApiFilter()->extensions(),
                                     t2->graphicsApiFilter()->extensions());
                            QCOMPARE(t1->graphicsApiFilter()->vendor(),
                                     t2->graphicsApiFilter()->vendor());
                        }
                    }
                    QVERIFY(techMatch);
                }
            }
        }
    }
}

Qt3DCore::QAttribute *tst_gltfPlugins::findAttribute(const QString &name,
                                                       Qt3DCore::QAttribute::AttributeType type,
                                                       Qt3DCore::QGeometry *geometry)
{
    for (auto att : geometry->attributes()) {
        if ((type == Qt3DCore::QAttribute::IndexAttribute && type == att->attributeType())
                || name == att->name()) {
            return att;
        }
    }
    return nullptr;
}

void tst_gltfPlugins::compareAttributes(Qt3DCore::QAttribute *a1, Qt3DCore::QAttribute *a2)
{
    QCOMPARE(a1 == nullptr, a2 == nullptr);
    if (a1) {
        QCOMPARE(a1->attributeType(), a2->attributeType());
        QCOMPARE(a1->vertexBaseType(), a2->vertexBaseType());
        QCOMPARE(a1->vertexSize(), a2->vertexSize());
        QCOMPARE(a1->count(), a2->count());
    }
}

void tst_gltfPlugins::compareParameters(const QList<Qt3DRender::QParameter *> &params1,
                                        const QList<Qt3DRender::QParameter *> &params2)
{
    QCOMPARE(params1.size(), params2.size());
    for (auto p1 : params1) {
        bool pMatch = false;
        for (auto p2 : params2) {
            if (p1->name() == p2->name()) {
                pMatch = true;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
                if (p1->value().metaType().id() == QMetaType::QColor) {
#else
                if (p1->value().type() == QMetaType::QColor) {
#endif
                    // Colors are imported as QVector4Ds
                    QColor color = p1->value().value<QColor>();
                    QVector4D vec = p2->value().value<QVector4D>();
                    QCOMPARE(color.redF(), vec.x());
                    QCOMPARE(color.greenF(), vec.y());
                    QCOMPARE(color.blueF(), vec.z());
                    QCOMPARE(color.alphaF(), vec.w());
                } else if (p1->value().canConvert<Qt3DRender::QAbstractTexture *>()) {
                    QUrl u1 = getTextureUrl(p1->value().value<Qt3DRender::QAbstractTexture *>());
                    QUrl u2 = getTextureUrl(p2->value().value<Qt3DRender::QAbstractTexture *>());
                    QCOMPARE(u1.fileName(), u2.fileName());
                } else {
                    QCOMPARE(p1->value(), p2->value());
                }
            }
        }
        QVERIFY(pMatch);
    }
}

void tst_gltfPlugins::compareRenderPasses(const QList<Qt3DRender::QRenderPass *> &passes1,
                                          const QList<Qt3DRender::QRenderPass *> &passes2)
{
    QCOMPARE(passes1.size(), passes2.size());
    for (auto pass1 : passes1) {
        bool passMatch = false;
        for (auto pass2 : passes2) {
            if (pass1->objectName() == pass2->objectName()) {
                passMatch = true;
                compareFilterKeys(pass1->filterKeys(), pass2->filterKeys());
                compareParameters(pass1->parameters(), pass2->parameters());

                const QList<Qt3DRender::QRenderState *> states1 = pass1->renderStates();
                const QList<Qt3DRender::QRenderState *> states2 = pass2->renderStates();
                QCOMPARE(states1.size(), states2.size());
                for (auto state1 : states1) {
                    bool stateMatch = false;
                    for (auto state2 : states2) {
                        if (state1->metaObject()->className()
                                == state2->metaObject()->className()) {
                            stateMatch = true;
                        }
                    }
                    QVERIFY(stateMatch);
                }

                QCOMPARE(pass1->shaderProgram()->vertexShaderCode(),
                         pass2->shaderProgram()->vertexShaderCode());
                QCOMPARE(pass1->shaderProgram()->fragmentShaderCode(),
                         pass2->shaderProgram()->fragmentShaderCode());
            }
        }
        QVERIFY(passMatch);
    }
}

void tst_gltfPlugins::compareFilterKeys(const QList<Qt3DRender::QFilterKey *> &keys1,
                                        const QList<Qt3DRender::QFilterKey *> &keys2)
{
    QCOMPARE(keys1.size(), keys2.size());
    for (auto k1 : keys1) {
        bool kMatch = false;
        for (auto k2 : keys2) {
            if (k1->name() == k2->name()) {
                kMatch = true;
                QCOMPARE(k1->value(), k2->value());
            }
        }
        QVERIFY(kMatch);
    }
}

QUrl tst_gltfPlugins::getTextureUrl(Qt3DRender::QAbstractTexture *tex)
{
    QUrl url;
    if (tex->textureImages().size()) {
        Qt3DRender::QTextureImage *img =
                qobject_cast<Qt3DRender::QTextureImage *>(
                    tex->textureImages().at(0));
        if (img)
            url = img->source();
    }
    return url;
}

Qt3DRender::QGeometryRenderer *tst_gltfPlugins::createCustomCube()
{
    Qt3DRender::QGeometryRenderer *boxMesh = new Qt3DRender::QGeometryRenderer;
    Qt3DCore::QGeometry *boxGeometry = new Qt3DCore::QGeometry(boxMesh);
    Qt3DCore::QBuffer *boxDataBuffer =
            new Qt3DCore::QBuffer(boxGeometry);
    Qt3DCore::QBuffer *indexDataBuffer =
            new Qt3DCore::QBuffer(boxGeometry);
    QByteArray vertexBufferData;
    QByteArray indexBufferData;

    vertexBufferData.resize(8 * 3 * sizeof(float));
    indexBufferData.resize(12 * 3 * sizeof(ushort));

    float dimension = 1.0f;

    float *vPtr = reinterpret_cast<float *>(vertexBufferData.data());
    vPtr[0]  = -dimension; vPtr[1]  = -dimension; vPtr[2]  = -dimension;
    vPtr[3]  = dimension;  vPtr[4]  = -dimension; vPtr[5]  = -dimension;
    vPtr[6]  = dimension;  vPtr[7]  = -dimension; vPtr[8]  = dimension;
    vPtr[9]  = -dimension; vPtr[10] = -dimension; vPtr[11] = dimension;
    vPtr[12] = -dimension; vPtr[13] = dimension;  vPtr[14] = -dimension;
    vPtr[15] = dimension;  vPtr[16] = dimension;  vPtr[17] = -dimension;
    vPtr[18] = dimension;  vPtr[19] = dimension;  vPtr[20] = dimension;
    vPtr[21] = -dimension; vPtr[22] = dimension;  vPtr[23] = dimension;

    ushort *iPtr = reinterpret_cast<ushort *>(indexBufferData.data());
    iPtr[0]  = 2; iPtr[1]  = 0; iPtr[2]  = 1;
    iPtr[3]  = 2; iPtr[4]  = 3; iPtr[5]  = 0;
    iPtr[6]  = 1; iPtr[7]  = 6; iPtr[8]  = 2;
    iPtr[9]  = 1; iPtr[10] = 5; iPtr[11] = 6;
    iPtr[12] = 2; iPtr[13] = 7; iPtr[14] = 3;
    iPtr[15] = 2; iPtr[16] = 6; iPtr[17] = 7;
    iPtr[18] = 6; iPtr[19] = 5; iPtr[20] = 4;
    iPtr[21] = 6; iPtr[22] = 4; iPtr[23] = 7;
    iPtr[24] = 7; iPtr[25] = 0; iPtr[26] = 3;
    iPtr[27] = 7; iPtr[28] = 4; iPtr[29] = 0;
    iPtr[30] = 4; iPtr[31] = 1; iPtr[32] = 0;
    iPtr[33] = 4; iPtr[34] = 5; iPtr[35] = 1;

    boxDataBuffer->setData(vertexBufferData);
    indexDataBuffer->setData(indexBufferData);

    addPositionAttributeToGeometry(boxGeometry, boxDataBuffer, 8);
    addIndexAttributeToGeometry(boxGeometry, indexDataBuffer, 36);

    boxMesh->setInstanceCount(1);
    boxMesh->setIndexOffset(0);
    boxMesh->setFirstInstance(0);
    boxMesh->setVertexCount(36);
    boxMesh->setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
    boxMesh->setGeometry(boxGeometry);

    return boxMesh;
}

Qt3DRender::QEffect *tst_gltfPlugins::createOnTopEffect()
{
    Qt3DRender::QEffect *effect = new Qt3DRender::QEffect;

    Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique();
    technique->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile);
    technique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
    technique->graphicsApiFilter()->setMajorVersion(2);
    technique->graphicsApiFilter()->setMinorVersion(1);

    Qt3DRender::QTechnique *techniqueCore = new Qt3DRender::QTechnique();
    techniqueCore->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile);
    techniqueCore->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
    techniqueCore->graphicsApiFilter()->setMajorVersion(3);
    techniqueCore->graphicsApiFilter()->setMinorVersion(1);

    Qt3DRender::QTechnique *techniqueES2 = new Qt3DRender::QTechnique();
    techniqueES2->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGLES);
    techniqueES2->graphicsApiFilter()->setMajorVersion(2);
    techniqueES2->graphicsApiFilter()->setMinorVersion(0);
    techniqueES2->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile);

    Qt3DRender::QFilterKey *filterkey1 = new Qt3DRender::QFilterKey(effect);
    Qt3DRender::QFilterKey *filterkey2 = new Qt3DRender::QFilterKey();
    filterkey1->setName(QStringLiteral("renderingStyle"));
    filterkey1->setValue(QStringLiteral("forward"));
    filterkey2->setName(QStringLiteral("dummyKey"));
    filterkey2->setValue(QStringLiteral("dummyValue"));

    Qt3DRender::QParameter *parameter1 = new Qt3DRender::QParameter(QStringLiteral("handleColor"),
                                                                    QColor(Qt::yellow));
    Qt3DRender::QParameter *parameter2 = new Qt3DRender::QParameter(QStringLiteral("customAlpha"),
                                                                    1.0f);
    Qt3DRender::QParameter *parameter3 = new Qt3DRender::QParameter(QStringLiteral("handleColor"),
                                                                    QColor(Qt::blue));
    Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D;
    Qt3DRender::QParameter *parameter4 =
            new Qt3DRender::QParameter(QStringLiteral("customTexture"), texture);
    Qt3DRender::QTextureImage *ti = new Qt3DRender::QTextureImage();
    parameter4->value().value<Qt3DRender::QAbstractTexture *>()->addTextureImage(ti);
    ti->setSource(QUrl(QStringLiteral("qrc:/qtlogo.png")));

    technique->addFilterKey(filterkey1);
    technique->addFilterKey(filterkey2);
    techniqueES2->addFilterKey(filterkey1);
    techniqueES2->addFilterKey(filterkey2);
    techniqueCore->addFilterKey(filterkey1);

    technique->addParameter(parameter1);
    technique->addParameter(parameter2);
    technique->addParameter(parameter4);
    techniqueES2->addParameter(parameter1);
    techniqueES2->addParameter(parameter2);

    Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram();
    Qt3DRender::QShaderProgram *shaderES2 = new Qt3DRender::QShaderProgram();
    shader->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(
                                    QUrl(QStringLiteral("qrc:/ontopmaterial.vert"))));
    shader->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(
                                      QUrl(QStringLiteral("qrc:/ontopmaterial.frag"))));
    shaderES2->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(
                                       QUrl(QStringLiteral("qrc:/ontopmaterialES2.vert"))));
    shaderES2->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(
                                         QUrl(QStringLiteral("qrc:/ontopmaterialES2.frag"))));
    shader->setObjectName(QStringLiteral("Basic shader"));
    shaderES2->setObjectName(QStringLiteral("ES2 shader"));

    Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass();
    Qt3DRender::QRenderPass *renderPassES2 = new Qt3DRender::QRenderPass();
    renderPass->setShaderProgram(shader);
    renderPassES2->setShaderProgram(shaderES2);
    renderPass->addFilterKey(filterkey2);
    renderPass->addParameter(parameter3);
    Qt3DRender::QColorMask *cmask = new Qt3DRender::QColorMask;
    cmask->setRedMasked(false);
    renderPass->addRenderState(cmask);
    Qt3DRender::QBlendEquation *be = new Qt3DRender::QBlendEquation;
    be->setBlendFunction(Qt3DRender::QBlendEquation::Subtract);
    renderPass->addRenderState(be);
    technique->addRenderPass(renderPassES2);
    techniqueES2->addRenderPass(renderPassES2);
    techniqueCore->addRenderPass(renderPass);
    technique->setObjectName(QStringLiteral("Basic technique"));
    techniqueES2->setObjectName(QStringLiteral("ES2 technique"));
    techniqueCore->setObjectName(QStringLiteral("Core technique"));
    renderPass->setObjectName(QStringLiteral("Basic pass"));
    renderPassES2->setObjectName(QStringLiteral("ES2 pass"));

    effect->addTechnique(technique);
    effect->addTechnique(techniqueES2);
    effect->addTechnique(techniqueCore);
    effect->setObjectName(QStringLiteral("OnTopEffect"));

    return effect;
}

Qt3DCore::QEntity *tst_gltfPlugins::findCameraChild(Qt3DCore::QEntity *entity,
                                                    Qt3DRender::QCameraLens::ProjectionType type)
{
    for (auto child : entity->children()) {
        if (auto childEntity = qobject_cast<Qt3DCore::QEntity *>(child)) {
            for (auto component : childEntity->components()) {
                if (auto cameraLens = qobject_cast<Qt3DRender::QCameraLens *>(component)) {
                    if (cameraLens->projectionType() == type)
                        return childEntity;
                }
            }
            if (auto cameraEntity = findCameraChild(childEntity, type))
                return cameraEntity;
        }
    }
    return nullptr;
}

void tst_gltfPlugins::exportAndImport_data()
{
    QTest::addColumn<bool>("compactJson");

    QTest::newRow("No options") << false;
#ifndef VISUAL_CHECK
    QTest::newRow("Compact json") << true;
#endif
}

void tst_gltfPlugins::exportAndImport()
{
    QSKIP("Crashes in JSON parser - see QTBUG-88588");
    QFETCH(bool, compactJson);

    createTestScene();

#ifdef PRESERVE_EXPORT
    m_exportDir->setAutoRemove(false);
    qDebug() << "Export Directory:" << m_exportDir->path();
#endif

    const QString sceneName = QStringLiteral("MyGLTFScene");
    const QString exportDir = m_exportDir->path();

    // Export the created scene using GLTF export plugin
    QStringList keys = Qt3DRender::QSceneExportFactory::keys();
    for (const QString &key : keys) {
        Qt3DRender::QSceneExporter *exporter =
                Qt3DRender::QSceneExportFactory::create(key, QStringList());
        if (exporter != nullptr && key == QStringLiteral("gltfexport")) {
            QVariantHash options;
            options.insert(QStringLiteral("compactJson"), QVariant(compactJson));
            exporter->exportScene(m_sceneRoot1, exportDir, sceneName, options);
            break;
        }
    }

    QCoreApplication::processEvents();

    // Import the exported scene using GLTF import plugin
    Qt3DCore::QEntity *importedScene = nullptr;
    keys = Qt3DRender::QSceneImportFactory::keys();
    for (auto key : keys) {
        Qt3DRender::QSceneImporter *importer =
                Qt3DRender::QSceneImportFactory::create(key, QStringList());
        if (importer != nullptr && key == QStringLiteral("gltf")) {
            QString sceneSource = exportDir;
            if (!sceneSource.endsWith(QLatin1Char('/')))
                sceneSource.append(QLatin1Char('/'));
            sceneSource += sceneName;
            sceneSource += QLatin1Char('/');
            sceneSource += sceneName;
            sceneSource += QStringLiteral(".qgltf");
            importer->setSource(QUrl::fromLocalFile(sceneSource));
            importedScene = importer->scene();
            break;
        }
    }

    importedScene->setParent(m_sceneRoot2);

    // Compare contents of the original scene and the exported one.
    for (auto it = m_entityMap.begin(), end = m_entityMap.end(); it != end; ++it) {
        QString name = it.key();
        Qt3DCore::QEntity *exportedEntity = it.value();
        Qt3DCore::QEntity *importedEntity = findChildEntity(importedScene, name);
        QVERIFY(importedEntity != nullptr);
        if (importedEntity) {
            qDebug() << importedEntity->objectName();
            compareComponents(transformComponent(exportedEntity),
                              transformComponent(importedEntity));
            compareComponents(lightComponent(exportedEntity),
                              lightComponent(importedEntity));
            compareComponents(cameraComponent(exportedEntity),
                              cameraComponent(importedEntity));
            compareComponents(meshComponent(exportedEntity),
                              meshComponent(importedEntity));
            compareComponents(materialComponent(exportedEntity),
                              materialComponent(importedEntity));
            Qt3DRender::QCamera *exportedCamera =
                    qobject_cast<Qt3DRender::QCamera *>(exportedEntity);
            if (exportedCamera) {
                Qt3DRender::QCamera *importedCamera =
                        qobject_cast<Qt3DRender::QCamera *>(importedEntity);
                QVERIFY(importedCamera != nullptr);
                QCOMPARE(exportedCamera->position(), importedCamera->position());
                QCOMPARE(exportedCamera->upVector(), importedCamera->upVector());
                QCOMPARE(exportedCamera->viewCenter(), importedCamera->viewCenter());
            }
        }
    }


#ifdef VISUAL_CHECK
    qDebug() << "Dumping original entity tree:";
    walkEntity(m_sceneRoot1, 0);
    qDebug() << "Dumping imported entity tree:";
    walkEntity(importedScene, 0);

    // Find the camera to actually show the scene
    m_view2->defaultFrameGraph()->setCamera(
                findCameraChild(m_sceneRoot2, Qt3DRender::QCameraLens::OrthographicProjection));
    QTest::qWait(VISUAL_CHECK);

    m_view1->defaultFrameGraph()->setCamera(
                findCameraChild(m_sceneRoot1, Qt3DRender::QCameraLens::PerspectiveProjection));
    m_view2->defaultFrameGraph()->setCamera(
                findCameraChild(m_sceneRoot2, Qt3DRender::QCameraLens::PerspectiveProjection));
    QTest::qWait(VISUAL_CHECK);
#endif
}

QTEST_MAIN(tst_gltfPlugins)

#include "tst_gltfplugins.moc"
