Revision control

++ // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "assimpimporter.h"
++
++ #include <Qt3DCore/qentity.h>
++ #include <Qt3DCore/qtransform.h>
++ #include <Qt3DCore/qattribute.h>
++ #include <Qt3DCore/qbuffer.h>
++ #include <Qt3DCore/qgeometry.h>
++ #include <Qt3DRender/qcameralens.h>
++ #include <Qt3DRender/qeffect.h>
++ #include <Qt3DRender/qgeometryrenderer.h>
++ #include <Qt3DRender/qmaterial.h>
++ #include <Qt3DRender/qmesh.h>
++ #include <Qt3DRender/qparameter.h>
++ #include <Qt3DRender/qtexture.h>
++ #include <Qt3DRender/qtextureimagedatagenerator.h>
++ #include <Qt3DExtras/qdiffusemapmaterial.h>
++ #include <Qt3DExtras/qdiffusespecularmapmaterial.h>
++ #include <Qt3DExtras/qphongmaterial.h>
++ #include <Qt3DExtras/qmorphphongmaterial.h>
++ #include <Qt3DExtras/qnormaldiffusemapmaterial.h>
++ #include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h>
++ #include <Qt3DAnimation/qkeyframeanimation.h>
++ #include <Qt3DAnimation/qmorphinganimation.h>
++ #include <QtCore/QFileInfo>
++
++ #include <QtGui/QColor>
++ #include <QtGui/qquaternion.h>
++
++ #include <qmath.h>
++
++ #include <Qt3DCore/private/qabstractnodefactory_p.h>
++ #include <Qt3DCore/private/qurlhelper_p.h>
++ #include <Qt3DRender/private/renderlogging_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++
++ namespace Qt3DRender {
++
++ using namespace Qt3DCore;
++ using namespace Qt3DExtras;
++
++ /*!
++ \class Qt3DRender::AssimpImporter
++ \inmodule Qt3DRender
++ \since 5.5
++ \internal
++
++ \brief Provides a generic way of loading various 3D assets
++ format into a Qt3D scene.
++
++ It should be noted that Assimp aiString is explicitly defined to be UTF-8.
++ */
++
++ Q_LOGGING_CATEGORY(AssimpImporterLog, "Qt3D.AssimpImporter", QtWarningMsg)
++
++ namespace {
++
++ const QString ASSIMP_MATERIAL_DIFFUSE_COLOR = QLatin1String("kd");
++ const QString ASSIMP_MATERIAL_SPECULAR_COLOR = QLatin1String("ks");
++ const QString ASSIMP_MATERIAL_AMBIENT_COLOR = QLatin1String("ka");
++ const QString ASSIMP_MATERIAL_EMISSIVE_COLOR = QLatin1String("emissive");
++ const QString ASSIMP_MATERIAL_TRANSPARENT_COLOR = QLatin1String("transparent");
++ const QString ASSIMP_MATERIAL_REFLECTIVE_COLOR = QLatin1String("reflective");
++
++ const QString ASSIMP_MATERIAL_DIFFUSE_TEXTURE = QLatin1String("diffuseTexture");
++ const QString ASSIMP_MATERIAL_AMBIENT_TEXTURE = QLatin1String("ambientTex");
++ const QString ASSIMP_MATERIAL_SPECULAR_TEXTURE = QLatin1String("specularTexture");
++ const QString ASSIMP_MATERIAL_EMISSIVE_TEXTURE = QLatin1String("emissiveTex");
++ const QString ASSIMP_MATERIAL_NORMALS_TEXTURE = QLatin1String("normalsTex");
++ // Keep the old "normalsTex" parameter name to keep backwards compatibility, add "normalTexture" as a new one
++ const QString ASSIMP_MATERIAL_NORMALS_TEXTURE2 = QLatin1String("normalTexture");
++ const QString ASSIMP_MATERIAL_OPACITY_TEXTURE = QLatin1String("opacityTex");
++ const QString ASSIMP_MATERIAL_REFLECTION_TEXTURE = QLatin1String("reflectionTex");
++ const QString ASSIMP_MATERIAL_HEIGHT_TEXTURE = QLatin1String("heightTex");
++ const QString ASSIMP_MATERIAL_LIGHTMAP_TEXTURE = QLatin1String("opacityTex");
++ const QString ASSIMP_MATERIAL_DISPLACEMENT_TEXTURE = QLatin1String("displacementTex");
++ const QString ASSIMP_MATERIAL_SHININESS_TEXTURE = QLatin1String("shininessTex");
++
++ const QString ASSIMP_MATERIAL_IS_TWOSIDED = QLatin1String("twosided");
++ const QString ASSIMP_MATERIAL_IS_WIREFRAME = QLatin1String("wireframe");
++
++ const QString ASSIMP_MATERIAL_OPACITY = QLatin1String("opacity");
++ const QString ASSIMP_MATERIAL_SHININESS = QLatin1String("shininess");
++ const QString ASSIMP_MATERIAL_SHININESS_STRENGTH = QLatin1String("shininess_strength");
++ const QString ASSIMP_MATERIAL_REFRACTI = QLatin1String("refracti");
++ const QString ASSIMP_MATERIAL_REFLECTIVITY = QLatin1String("reflectivity");
++
++ const QString ASSIMP_MATERIAL_NAME = QLatin1String("name");
++
++ const QString VERTICES_ATTRIBUTE_NAME = QAttribute::defaultPositionAttributeName();
++ const QString NORMAL_ATTRIBUTE_NAME = QAttribute::defaultNormalAttributeName();
++ const QString TANGENT_ATTRIBUTE_NAME = QAttribute::defaultTangentAttributeName();
++ const QString TEXTCOORD_ATTRIBUTE_NAME = QAttribute::defaultTextureCoordinateAttributeName();
++ const QString COLOR_ATTRIBUTE_NAME = QAttribute::defaultColorAttributeName();
++
++ /*
++ * Returns a QMatrix4x4 from \a matrix;
++ */
++ QMatrix4x4 aiMatrix4x4ToQMatrix4x4(const aiMatrix4x4 &matrix) noexcept
++ {
++ return QMatrix4x4(matrix.a1, matrix.a2, matrix.a3, matrix.a4,
++ matrix.b1, matrix.b2, matrix.b3, matrix.b4,
++ matrix.c1, matrix.c2, matrix.c3, matrix.c4,
++ matrix.d1, matrix.d2, matrix.d3, matrix.d4);
++ }
++
++ /*
++ * Returns a QString from \a str;
++ */
++ inline QString aiStringToQString(const aiString &str)
++ {
++ return QString::fromUtf8(str.data, int(str.length));
++ }
++
++ QMaterial *createBestApproachingMaterial(const aiMaterial *assimpMaterial)
++ {
++ aiString path; // unused but necessary
++ const bool hasNormalTexture = (assimpMaterial->GetTexture(aiTextureType_NORMALS, 0, &path) == AI_SUCCESS);
++ const bool hasDiffuseTexture = (assimpMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS);
++ const bool hasSpecularTexture = (assimpMaterial->GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS);
++
++ if (hasNormalTexture && hasDiffuseTexture && hasSpecularTexture)
++ return QAbstractNodeFactory::createNode<QNormalDiffuseSpecularMapMaterial>("QNormalDiffuseSpecularMapMaterial");
++ if (hasNormalTexture && hasDiffuseTexture)
++ return QAbstractNodeFactory::createNode<QNormalDiffuseMapMaterial>("QNormalDiffuseMapMaterial");
++ if (hasDiffuseTexture && hasSpecularTexture)
++ return QAbstractNodeFactory::createNode<QDiffuseSpecularMapMaterial>("QDiffuseSpecularMapMaterial");
++ if (hasDiffuseTexture)
++ return QAbstractNodeFactory::createNode<QDiffuseMapMaterial>("QDiffuseMapMaterial");
++ return QAbstractNodeFactory::createNode<QPhongMaterial>("QPhongMaterial");
++ }
++
++ QString texturePath(const aiString &path)
++ {
++ QString p = aiStringToQString(path);
++ p.replace(QLatin1String("\\"), QLatin1String("/"));
++ if (p.startsWith('/'))
++ p.remove(0, 1);
++ return p;
++ }
++
++ /*
++ * Returns the Qt3DRender::QParameter with named \a name if contained by the material
++ * \a material. If the material doesn't contain the named parameter, a new
++ * Qt3DRender::QParameter is created and inserted into the material.
++ */
++ QParameter *findNamedParameter(const QString &name, QMaterial *material)
++ {
++ // Does the material contain the parameter ?
++ const auto params = material->parameters();
++ for (QParameter *p : params) {
++ if (p->name() == name)
++ return p;
++ }
++
++ // Does the material's effect contain the parameter ?
++ if (material->effect()) {
++ const QEffect *e = material->effect();
++ const auto params = e->parameters();
++ for (QParameter *p : params) {
++ if (p->name() == name)
++ return p;
++ }
++ }
++
++ // Create and add parameter to material
++ QParameter *p = QAbstractNodeFactory::createNode<QParameter>("QParameter");
++ p->setParent(material);
++ p->setName(name);
++ material->addParameter(p);
++ return p;
++ }
++
++ void setParameterValue(const QString &name, QMaterial *material, const QVariant &value)
++ {
++ QParameter *p = findNamedParameter(name, material);
++ p->setValue(value);
++ }
++
++ QAttribute *createAttribute(Qt3DCore::QBuffer *buffer,
++ const QString &name,
++ QAttribute::VertexBaseType vertexBaseType,
++ uint vertexSize,
++ uint count,
++ uint byteOffset = 0,
++ uint byteStride = 0,
++ QNode *parent = nullptr)
++ {
++ QAttribute *attribute = QAbstractNodeFactory::createNode<QAttribute>("QAttribute");
++ attribute->setBuffer(buffer);
++ attribute->setName(name);
++ attribute->setVertexBaseType(vertexBaseType);
++ attribute->setVertexSize(vertexSize);
++ attribute->setCount(count);
++ attribute->setByteOffset(byteOffset);
++ attribute->setByteStride(byteStride);
++ attribute->setParent(parent);
++ return attribute;
++ }
++
++ QAttribute *createIndexAttribute(Qt3DCore::QBuffer *buffer,
++ QAttribute::VertexBaseType vertexBaseType,
++ uint vertexSize,
++ uint count,
++ uint byteOffset = 0,
++ uint byteStride = 0,
++ QNode *parent = nullptr)
++ {
++ QAttribute *attribute = QAbstractNodeFactory::createNode<QAttribute>("QAttribute");
++ attribute->setBuffer(buffer);
++ attribute->setVertexBaseType(vertexBaseType);
++ attribute->setVertexSize(vertexSize);
++ attribute->setCount(count);
++ attribute->setByteOffset(byteOffset);
++ attribute->setByteStride(byteStride);
++ attribute->setParent(parent);
++ return attribute;
++ }
++
++ QTextureWrapMode::WrapMode wrapModeFromaiTextureMapMode(int mode)
++ {
++ switch (mode) {
++ case aiTextureMapMode_Wrap:
++ return QTextureWrapMode::Repeat;
++ case aiTextureMapMode_Mirror:
++ return QTextureWrapMode::MirroredRepeat;
++ case aiTextureMapMode_Decal:
++ return QTextureWrapMode::ClampToBorder;
++ case aiTextureMapMode_Clamp:
++ default:
++ return QTextureWrapMode::ClampToEdge;
++ }
++ }
++
++ } // anonymous
++
++ QStringList AssimpImporter::assimpSupportedFormatsList = AssimpImporter::assimpSupportedFormats();
++
++ /*!
++ * Returns a QStringlist with the suffixes of the various supported asset formats.
++ */
++ QStringList AssimpImporter::assimpSupportedFormats()
++ {
++ QStringList formats;
++
++ formats.reserve(60);
++ formats.append(QStringLiteral("3d"));
++ formats.append(QStringLiteral("3ds"));
++ formats.append(QStringLiteral("ac"));
++ formats.append(QStringLiteral("ac3d"));
++ formats.append(QStringLiteral("acc"));
++ formats.append(QStringLiteral("ase"));
++ formats.append(QStringLiteral("ask"));
++ formats.append(QStringLiteral("assbin"));
++ formats.append(QStringLiteral("b3d"));
++ formats.append(QStringLiteral("blend"));
++ formats.append(QStringLiteral("bvh"));
++ formats.append(QStringLiteral("cob"));
++ formats.append(QStringLiteral("csm"));
++ formats.append(QStringLiteral("dae"));
++ formats.append(QStringLiteral("dxf"));
++ formats.append(QStringLiteral("enff"));
++ formats.append(QStringLiteral("fbx"));
++ formats.append(QStringLiteral("hmp"));
++ formats.append(QStringLiteral("irr"));
++ formats.append(QStringLiteral("irrmesh"));
++ formats.append(QStringLiteral("lwo"));
++ formats.append(QStringLiteral("lws"));
++ formats.append(QStringLiteral("lxo"));
++ formats.append(QStringLiteral("md2"));
++ formats.append(QStringLiteral("md3"));
++ formats.append(QStringLiteral("md5anim"));
++ formats.append(QStringLiteral("md5camera"));
++ formats.append(QStringLiteral("md5mesh"));
++ formats.append(QStringLiteral("mdc"));
++ formats.append(QStringLiteral("mdl"));
++ formats.append(QStringLiteral("mesh.xml"));
++ formats.append(QStringLiteral("mot"));
++ formats.append(QStringLiteral("ms3d"));
++ formats.append(QStringLiteral("ndo"));
++ formats.append(QStringLiteral("nff"));
++ formats.append(QStringLiteral("obj"));
++ formats.append(QStringLiteral("off"));
++ formats.append(QStringLiteral("ogex"));
++ formats.append(QStringLiteral("pk3"));
++ formats.append(QStringLiteral("ply"));
++ formats.append(QStringLiteral("prj"));
++ formats.append(QStringLiteral("q3o"));
++ formats.append(QStringLiteral("q3s"));
++ formats.append(QStringLiteral("raw"));
++ formats.append(QStringLiteral("scn"));
++ formats.append(QStringLiteral("sib"));
++ formats.append(QStringLiteral("smd"));
++ formats.append(QStringLiteral("stl"));
++ formats.append(QStringLiteral("ter"));
++ formats.append(QStringLiteral("uc"));
++ formats.append(QStringLiteral("vta"));
++ formats.append(QStringLiteral("x"));
++ formats.append(QStringLiteral("xml"));
++
++ return formats;
++ }
++
++ class AssimpRawTextureImage : public QAbstractTextureImage
++ {
++ Q_OBJECT
++ public:
++ explicit AssimpRawTextureImage(QNode *parent = 0);
++
++ QTextureImageDataGeneratorPtr dataGenerator() const final;
++
++ void setData(const QByteArray &data);
++
++ private:
++ QByteArray m_data;
++
++ class AssimpRawTextureImageFunctor : public QTextureImageDataGenerator
++ {
++ public:
++ explicit AssimpRawTextureImageFunctor(const QByteArray &data);
++
++ QTextureImageDataPtr operator()() final;
++ bool operator ==(const QTextureImageDataGenerator &other) const final;
++
++ QT3D_FUNCTOR(AssimpRawTextureImageFunctor)
++ private:
++ QByteArray m_data;
++ };
++ };
++
++ /*!
++ * Constructor. Initializes a new instance of AssimpImporter.
++ */
++ AssimpImporter::AssimpImporter() : QSceneImporter(),
++ m_sceneParsed(false),
++ m_scene(nullptr)
++ {
++ }
++
++ /*!
++ * Destructor. Cleans the parser properly before destroying it.
++ */
++ AssimpImporter::~AssimpImporter()
++ {
++ cleanup();
++ }
++
++ /*!
++ * Returns \c true if the extensions are supported
++ * by the Assimp Assets importer.
++ */
++ bool AssimpImporter::areAssimpExtensions(const QStringList &extensions)
++ {
++ for (const auto &ext : std::as_const(extensions))
++ if (AssimpImporter::assimpSupportedFormatsList.contains(ext.toLower()))
++ return true;
++ return false;
++ }
++
++ /*!
++ * Sets the \a source used by the parser to load the asset file.
++ * If the file is valid, this will trigger parsing of the file.
++ */
++ void AssimpImporter::setSource(const QUrl &source)
++ {
++ const QString path = Qt3DCore::QUrlHelper::urlToLocalFileOrQrc(source);
++ QFileInfo file(path);
++ m_sceneDir = file.absoluteDir();
++ if (!file.exists()) {
++ qCWarning(AssimpImporterLog) << "File missing " << path;
++ return ;
++ }
++ readSceneFile(path);
++ }
++
++ /*!
++ * Sets the \a basePath used by the parser to load the asset file.
++ * If the file specified in \a data is valid, this will trigger parsing of the file.
++ */
++ void AssimpImporter::setData(const QByteArray &data, const QString &basePath)
++ {
++ readSceneData(data, basePath);
++ }
++
++ /*!
++ * Returns \c true if the extension in QStringList \a extensions is supported by
++ * the assimp parser.
++ */
++ bool AssimpImporter::areFileTypesSupported(const QStringList &extensions) const
++ {
++ return AssimpImporter::areAssimpExtensions(extensions);
++ }
++
++ /*!
++ * Returns a Entity node which is the root node of the scene
++ * node specified by \a id. If \a id is empty, the scene is assumed to be
++ * the root node of the scene.
++ *
++ * Returns \c nullptr if \a id was specified but no node matching it was found.
++ */
++ Qt3DCore::QEntity *AssimpImporter::scene(const QString &id)
++ {
++ // m_aiScene shouldn't be null.
++ // If it is either, the file failed to be imported or
++ // setFilePath was not called
++ if (m_scene == nullptr || m_scene->m_aiScene == nullptr)
++ return nullptr;
++
++ aiNode *rootNode = m_scene->m_aiScene->mRootNode;
++ // if id specified, tries to find node
++ if (!id.isEmpty() &&
++ !(rootNode = rootNode->FindNode(id.toUtf8().constData()))) {
++ qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Couldn't find requested scene node";
++ return nullptr;
++ }
++
++ // Builds the Qt3D scene using the Assimp aiScene
++ // and the various dicts filled previously by parse
++ Qt3DCore::QEntity *n = node(rootNode);
++ if (m_scene->m_animations.size() > 0) {
++ qWarning() << "No target found for " << m_scene->m_animations.size() << " animations!";
++
++ for (Qt3DAnimation::QKeyframeAnimation *anim : std::as_const(m_scene->m_animations))
++ delete anim;
++ m_scene->m_animations.clear();
++ }
++ return n;
++ }
++
++ /*!
++ * Returns a Node from the scene identified by \a id.
++ * Returns \c nullptr if the node was not found.
++ */
++ Qt3DCore::QEntity *AssimpImporter::node(const QString &id)
++ {
++ if (m_scene == nullptr || m_scene->m_aiScene == nullptr)
++ return nullptr;
++ parse();
++ aiNode *n = m_scene->m_aiScene->mRootNode->FindNode(id.toUtf8().constData());
++ return node(n);
++ }
++
++ template <typename T>
++ void findAnimationsForNode(QList<T *> &animations, QList<T *> &result, const QString &name)
++ {
++ for (T *anim : animations) {
++ if (anim->targetName() == name) {
++ result.push_back(anim);
++ animations.removeAll(anim);
++ }
++ }
++ }
++
++ /*!
++ * Returns a Node from an Assimp aiNode \a node.
++ */
++ Qt3DCore::QEntity *AssimpImporter::node(aiNode *node)
++ {
++ if (node == nullptr)
++ return nullptr;
++ QEntity *entityNode = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
++ entityNode->setObjectName(aiStringToQString(node->mName));
++
++ // Add Meshes to the node
++ for (uint i = 0; i < node->mNumMeshes; i++) {
++ uint meshIndex = node->mMeshes[i];
++ QGeometryRenderer *mesh = loadMesh(meshIndex);
++
++ // mesh material
++ QMaterial *material;
++
++ QList<Qt3DAnimation::QMorphingAnimation *> morphingAnimations
++ = mesh->findChildren<Qt3DAnimation::QMorphingAnimation *>();
++ if (morphingAnimations.size() > 0) {
++ material = new Qt3DExtras::QMorphPhongMaterial(entityNode);
++
++ QList<Qt3DAnimation::QMorphingAnimation *> animations;
++ findAnimationsForNode<Qt3DAnimation::QMorphingAnimation>(m_scene->m_morphAnimations,
++ animations,
++ aiStringToQString(node->mName));
++ const auto morphTargetList = morphingAnimations.at(0)->morphTargetList();
++ for (Qt3DAnimation::QMorphingAnimation *anim : std::as_const(animations)) {
++ anim->setParent(entityNode);
++ anim->setTarget(mesh);
++ anim->setMorphTargets(morphTargetList);
++ }
++
++ for (int j = 0; j < animations.size(); ++j) {
++ QObject::connect(animations[j], &Qt3DAnimation::QMorphingAnimation::interpolatorChanged,
++ (Qt3DExtras::QMorphPhongMaterial *)material,
++ &Qt3DExtras::QMorphPhongMaterial::setInterpolator);
++ }
++ morphingAnimations[0]->deleteLater();
++ } else {
++ uint materialIndex = m_scene->m_aiScene->mMeshes[meshIndex]->mMaterialIndex;
++ material = loadMaterial(materialIndex);
++ }
++
++ if (node->mNumMeshes == 1) {
++ if (material)
++ entityNode->addComponent(material);
++ // mesh
++ entityNode->addComponent(mesh);
++ } else {
++ QEntity *childEntity = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
++ childEntity->setObjectName(entityNode->objectName() + QLatin1String("_Child") + QString::number(i));
++ if (material)
++ childEntity->addComponent(material);
++ childEntity->addComponent(mesh);
++ childEntity->setParent(entityNode);
++
++ Qt3DCore::QTransform *transform
++ = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
++ childEntity->addComponent(transform);
++ }
++ }
++
++ // Add Children to Node
++ for (uint i = 0; i < node->mNumChildren; i++) {
++ // this-> is necessary here otherwise
++ // it conflicts with the variable node
++ QEntity *child = this->node(node->mChildren[i]);
++ // Are we sure each child are unique ???
++ if (child != nullptr)
++ child->setParent(entityNode);
++ }
++
++ // Add Transformations
++ const QMatrix4x4 qTransformMatrix = aiMatrix4x4ToQMatrix4x4(node->mTransformation);
++ Qt3DCore::QTransform *transform = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
++ transform->setMatrix(qTransformMatrix);
++ entityNode->addComponent(transform);
++
++ QList<Qt3DAnimation::QKeyframeAnimation *> animations;
++ findAnimationsForNode<Qt3DAnimation::QKeyframeAnimation>(m_scene->m_animations,
++ animations,
++ aiStringToQString(node->mName));
++
++ for (Qt3DAnimation::QKeyframeAnimation *anim : std::as_const(animations)) {
++ anim->setTarget(transform);
++ anim->setParent(entityNode);
++ }
++
++ // Add Camera
++ auto camera = loadCamera(node);
++ if (camera)
++ camera->setParent(entityNode);
++
++ // TO DO : Add lights ....
++
++ return entityNode;
++ }
++
++ /*!
++ * Reads the scene file pointed by \a path and launches the parsing of
++ * the scene using Assimp, after having cleaned up previously saved values
++ * from eventual previous parsings.
++ */
++ void AssimpImporter::readSceneFile(const QString &path)
++ {
++ cleanup();
++
++ m_scene = new SceneImporter();
++
++ // SET THIS TO REMOVE POINTS AND LINES -> HAVE ONLY TRIANGLES
++ m_scene->m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE|aiPrimitiveType_POINT);
++ // SET CUSTOM FILE HANDLER TO HANDLE FILE READING THROUGH QT (RESOURCES, SOCKET ...)
++ m_scene->m_importer->SetIOHandler(new AssimpHelper::AssimpIOSystem());
++
++ // type and aiProcess_Triangulate discompose polygons with more than 3 points in triangles
++ // aiProcess_SortByPType makes sure that meshes data are triangles
++ m_scene->m_aiScene = m_scene->m_importer->ReadFile(path.toUtf8().constData(),
++ aiProcess_SortByPType|
++ aiProcess_Triangulate|
++ aiProcess_GenSmoothNormals|
++ aiProcess_FlipUVs);
++ if (m_scene->m_aiScene == nullptr) {
++ qCWarning(AssimpImporterLog) << "Assimp scene import failed" << m_scene->m_importer->GetErrorString();
++ QSceneImporter::logError(QString::fromUtf8(m_scene->m_importer->GetErrorString()));
++ return ;
++ }
++ parse();
++ }
++
++ /*!
++ * Reads the scene file pointed by \a path and launches the parsing of
++ * the scene using Assimp, after having cleaned up previously saved values
++ * from eventual previous parsings.
++ */
++ void AssimpImporter::readSceneData(const QByteArray& data, const QString &basePath)
++ {
++ Q_UNUSED(basePath);
++ cleanup();
++
++ m_scene = new SceneImporter();
++
++ // SET THIS TO REMOVE POINTS AND LINES -> HAVE ONLY TRIANGLES
++ m_scene->m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE|aiPrimitiveType_POINT);
++ // SET CUSTOM FILE HANDLER TO HANDLE FILE READING THROUGH QT (RESOURCES, SOCKET ...)
++ m_scene->m_importer->SetIOHandler(new AssimpHelper::AssimpIOSystem());
++
++ // type and aiProcess_Triangulate discompose polygons with more than 3 points in triangles
++ // aiProcess_SortByPType makes sure that meshes data are triangles
++ m_scene->m_aiScene = m_scene->m_importer->ReadFileFromMemory(data.data(), data.size(),
++ aiProcess_SortByPType|
++ aiProcess_Triangulate|
++ aiProcess_GenSmoothNormals|
++ aiProcess_FlipUVs);
++ if (m_scene->m_aiScene == nullptr) {
++ qCWarning(AssimpImporterLog) << "Assimp scene import failed";
++ return ;
++ }
++ parse();
++ }
++
++ /*!
++ * Cleans the various dictionaries holding the scene's information.
++ */
++ void AssimpImporter::cleanup()
++ {
++ m_sceneParsed = false;
++ delete m_scene;
++ m_scene = nullptr;
++ }
++
++ /*!
++ * Parses the aiScene provided py Assimp and converts Assimp
++ * values to Qt3D values.
++ */
++ void AssimpImporter::parse()
++ {
++ if (!m_sceneParsed) {
++ // Set parsed flags
++ m_sceneParsed = !m_sceneParsed;
++
++ for (uint i = 0; i < m_scene->m_aiScene->mNumAnimations; i++)
++ loadAnimation(i);
++ }
++ }
++
++ /*!
++ * Converts the provided Assimp aiMaterial identified by \a materialIndex to a
++ * Qt3D material
++ * \sa Material
++ */
++ QMaterial *AssimpImporter::loadMaterial(uint materialIndex)
++ {
++ // Generates default material based on what the assimp material contains
++ aiMaterial *assimpMaterial = m_scene->m_aiScene->mMaterials[materialIndex];
++ QMaterial *material = createBestApproachingMaterial(assimpMaterial);
++ // Material Name
++ copyMaterialName(material, assimpMaterial);
++ copyMaterialColorProperties(material, assimpMaterial);
++ copyMaterialBoolProperties(material, assimpMaterial);
++ copyMaterialFloatProperties(material, assimpMaterial);
++
++ // Add textures to materials dict
++ copyMaterialTextures(material, assimpMaterial);
++
++ return material;
++ }
++
++ /*!
++ * Converts the Assimp aiMesh mesh identified by \a meshIndex to a QGeometryRenderer
++ * \sa QGeometryRenderer
++ */
++ QGeometryRenderer *AssimpImporter::loadMesh(uint meshIndex)
++ {
++ aiMesh *mesh = m_scene->m_aiScene->mMeshes[meshIndex];
++
++ QGeometryRenderer *geometryRenderer = QAbstractNodeFactory::createNode<QGeometryRenderer>("QGeometryRenderer");
++ QGeometry *meshGeometry = QAbstractNodeFactory::createNode<QGeometry>("QGeometry");
++ meshGeometry->setParent(geometryRenderer);
++ Qt3DCore::QBuffer *vertexBuffer = QAbstractNodeFactory::createNode<Qt3DCore::QBuffer>("QBuffer");
++ vertexBuffer->setParent(meshGeometry);
++ Qt3DCore::QBuffer *indexBuffer = QAbstractNodeFactory::createNode<Qt3DCore::QBuffer>("QBuffer");
++ indexBuffer->setParent(meshGeometry);
++
++ geometryRenderer->setGeometry(meshGeometry);
++
++ // Primitive are always triangles with the current Assimp's configuration
++
++ // Vertices and Normals always present with the current Assimp's configuration
++ aiVector3D *vertices = mesh->mVertices;
++ aiVector3D *normals = mesh->mNormals;
++ aiColor4D *colors = mesh->mColors[0];
++ // Tangents and TextureCoord not always present
++ bool hasTangent = mesh->HasTangentsAndBitangents();
++ bool hasTexture = mesh->HasTextureCoords(0);
++ bool hasColor = (colors != NULL); // NULL defined by Assimp
++ aiVector3D *tangents = hasTangent ? mesh->mTangents : nullptr;
++ aiVector3D *textureCoord = hasTexture ? mesh->mTextureCoords[0] : nullptr;
++
++ // Add values in raw float array
++ ushort chunkSize = 6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0) + (hasColor ? 4 : 0);
++ QByteArray bufferArray;
++ bufferArray.resize(chunkSize * mesh->mNumVertices * sizeof(float));
++ float *vbufferContent = reinterpret_cast<float*>(bufferArray.data());
++ for (uint i = 0; i < mesh->mNumVertices; i++) {
++ uint idx = i * chunkSize;
++ // position
++ vbufferContent[idx] = vertices[i].x;
++ vbufferContent[idx + 1] = vertices[i].y;
++ vbufferContent[idx + 2] = vertices[i].z;
++ // normals
++ vbufferContent[idx + 3] = normals[i].x;
++ vbufferContent[idx + 4] = normals[i].y;
++ vbufferContent[idx + 5] = normals[i].z;
++
++ if (hasTangent) {
++ vbufferContent[idx + 6] = tangents[i].x;
++ vbufferContent[idx + 7] = tangents[i].y;
++ vbufferContent[idx + 8] = tangents[i].z;
++ }
++ if (hasTexture) {
++ char offset = (hasTangent ? 9 : 6);
++ vbufferContent[idx + offset] = textureCoord[i].x;
++ vbufferContent[idx + offset + 1] = textureCoord[i].y;
++ }
++ if (hasColor) {
++ char offset = 6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0);
++ vbufferContent[idx + offset] = colors[i].r;
++ vbufferContent[idx + offset + 1] = colors[i].g;
++ vbufferContent[idx + offset + 2] = colors[i].b;
++ vbufferContent[idx + offset + 3] = colors[i].a;
++ }
++ }
++
++ vertexBuffer->setData(bufferArray);
++
++ // Add vertex attributes to the mesh with the right array
++ QAttribute *positionAttribute = createAttribute(vertexBuffer, VERTICES_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ mesh->mNumVertices,
++ 0,
++ chunkSize * sizeof(float));
++
++ QAttribute *normalAttribute = createAttribute(vertexBuffer, NORMAL_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ mesh->mNumVertices,
++ 3 * sizeof(float),
++ chunkSize * sizeof(float));
++
++ meshGeometry->addAttribute(positionAttribute);
++ meshGeometry->addAttribute(normalAttribute);
++
++ if (hasTangent) {
++ QAttribute *tangentsAttribute = createAttribute(vertexBuffer, TANGENT_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ mesh->mNumVertices,
++ 6 * sizeof(float),
++ chunkSize * sizeof(float));
++ meshGeometry->addAttribute(tangentsAttribute);
++ }
++
++ if (hasTexture) {
++ QAttribute *textureCoordAttribute = createAttribute(vertexBuffer, TEXTCOORD_ATTRIBUTE_NAME,
++ QAttribute::Float, 2,
++ mesh->mNumVertices,
++ (hasTangent ? 9 : 6) * sizeof(float),
++ chunkSize * sizeof(float));
++ meshGeometry->addAttribute(textureCoordAttribute);
++ }
++
++ if (hasColor) {
++ QAttribute *colorAttribute = createAttribute(vertexBuffer, COLOR_ATTRIBUTE_NAME,
++ QAttribute::Float, 4,
++ mesh->mNumVertices,
++ (6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0)) * sizeof(float),
++ chunkSize * sizeof(float));
++ meshGeometry->addAttribute(colorAttribute);
++ }
++
++ QAttribute::VertexBaseType indiceType;
++ QByteArray ibufferContent;
++ uint indices = mesh->mNumFaces * 3;
++ // If there are less than 65535 indices, indices can then fit in ushort
++ // which saves video memory
++ if (indices >= USHRT_MAX) {
++ indiceType = QAttribute::UnsignedInt;
++ ibufferContent.resize(indices * sizeof(quint32));
++ for (uint i = 0; i < mesh->mNumFaces; i++) {
++ aiFace face = mesh->mFaces[i];
++ Q_ASSERT(face.mNumIndices == 3);
++ memcpy(&reinterpret_cast<quint32*>(ibufferContent.data())[i * 3], face.mIndices, 3 * sizeof(uint));
++ }
++ }
++ else {
++ indiceType = QAttribute::UnsignedShort;
++ ibufferContent.resize(indices * sizeof(quint16));
++ for (uint i = 0; i < mesh->mNumFaces; i++) {
++ aiFace face = mesh->mFaces[i];
++ Q_ASSERT(face.mNumIndices == 3);
++ for (uint j = 0; j < face.mNumIndices; j++)
++ reinterpret_cast<quint16*>(ibufferContent.data())[i * 3 + j] = face.mIndices[j];
++ }
++ }
++
++ indexBuffer->setData(ibufferContent);
++
++ // Add indices attributes
++ QAttribute *indexAttribute = createIndexAttribute(indexBuffer, indiceType, 1, indices);
++ indexAttribute->setAttributeType(QAttribute::IndexAttribute);
++
++ meshGeometry->addAttribute(indexAttribute);
++
++ if (mesh->mNumAnimMeshes > 0) {
++
++ aiAnimMesh *animesh = mesh->mAnimMeshes[0];
++
++ if (animesh->mNumVertices != mesh->mNumVertices)
++ return geometryRenderer;
++
++ Qt3DAnimation::QMorphingAnimation *morphingAnimation
++ = new Qt3DAnimation::QMorphingAnimation(geometryRenderer);
++
++ QList<Qt3DAnimation::QMorphTarget *> targets;
++
++ for (uint i = 0; i < mesh->mNumAnimMeshes; i++) {
++ aiAnimMesh *animesh = mesh->mAnimMeshes[i];
++ Qt3DAnimation::QMorphTarget *target = new Qt3DAnimation::QMorphTarget(geometryRenderer);
++ targets.push_back(target);
++ QList<QAttribute *> attributes;
++ ushort clumpSize = (animesh->mVertices ? 3 : 0)
++ + (animesh->mNormals ? 3 : 0)
++ + (animesh->mTangents ? 3 : 0)
++ + (animesh->mColors[0] ? 4 : 0)
++ + (animesh->mTextureCoords[0] ? 2 : 0);
++ QByteArray targetBufferArray;
++ targetBufferArray.resize(clumpSize * mesh->mNumVertices * sizeof(float));
++ float *dst = reinterpret_cast<float *>(targetBufferArray.data());
++
++ for (uint j = 0; j < mesh->mNumVertices; j++) {
++ if (animesh->mVertices) {
++ *dst++ = animesh->mVertices[j].x;
++ *dst++ = animesh->mVertices[j].y;
++ *dst++ = animesh->mVertices[j].z;
++ }
++ if (animesh->mNormals) {
++ *dst++ = animesh->mNormals[j].x;
++ *dst++ = animesh->mNormals[j].y;
++ *dst++ = animesh->mNormals[j].z;
++ }
++ if (animesh->mTangents) {
++ *dst++ = animesh->mTangents[j].x;
++ *dst++ = animesh->mTangents[j].y;
++ *dst++ = animesh->mTangents[j].z;
++ }
++ if (animesh->mTextureCoords[0]) {
++ *dst++ = animesh->mTextureCoords[0][j].x;
++ *dst++ = animesh->mTextureCoords[0][j].y;
++ }
++ if (animesh->mColors[0]) {
++ *dst++ = animesh->mColors[0][j].r;
++ *dst++ = animesh->mColors[0][j].g;
++ *dst++ = animesh->mColors[0][j].b;
++ *dst++ = animesh->mColors[0][j].a;
++ }
++ }
++
++ Qt3DCore::QBuffer *targetBuffer
++ = QAbstractNodeFactory::createNode<Qt3DCore::QBuffer>("QBuffer");
++ targetBuffer->setData(targetBufferArray);
++ targetBuffer->setParent(meshGeometry);
++
++ uint offset = 0;
++ if (animesh->mVertices) {
++ attributes.push_back(createAttribute(targetBuffer, VERTICES_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ animesh->mNumVertices, offset * sizeof(float),
++ clumpSize * sizeof(float), meshGeometry));
++ offset += 3;
++ }
++ if (animesh->mNormals) {
++ attributes.push_back(createAttribute(targetBuffer, NORMAL_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ animesh->mNumVertices, offset * sizeof(float),
++ clumpSize * sizeof(float), meshGeometry));
++ offset += 3;
++ }
++ if (animesh->mTangents) {
++ attributes.push_back(createAttribute(targetBuffer, TANGENT_ATTRIBUTE_NAME,
++ QAttribute::Float, 3,
++ animesh->mNumVertices, offset * sizeof(float),
++ clumpSize * sizeof(float), meshGeometry));
++ offset += 3;
++ }
++ if (animesh->mTextureCoords[0]) {
++ attributes.push_back(createAttribute(targetBuffer, TEXTCOORD_ATTRIBUTE_NAME,
++ QAttribute::Float, 2,
++ animesh->mNumVertices, offset * sizeof(float),
++ clumpSize * sizeof(float), meshGeometry));
++ offset += 2;
++ }
++ if (animesh->mColors[0]) {
++ attributes.push_back(createAttribute(targetBuffer, COLOR_ATTRIBUTE_NAME,
++ QAttribute::Float, 4,
++ animesh->mNumVertices, offset * sizeof(float),
++ clumpSize * sizeof(float), meshGeometry));
++ }
++ target->setAttributes(attributes);
++ }
++ morphingAnimation->setMorphTargets(targets);
++ morphingAnimation->setTargetName(aiStringToQString(mesh->mName));
++ morphingAnimation->setTarget(geometryRenderer);
++ }
++
++ qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Mesh " << aiStringToQString(mesh->mName)
++ << " Vertices " << mesh->mNumVertices << " Faces "
++ << mesh->mNumFaces << " Indices " << indices;
++
++ return geometryRenderer;
++ }
++
++ /*!
++ * Converts the provided Assimp aiTexture at \a textureIndex to a Texture
++ * \sa Texture
++ */
++ QAbstractTexture *AssimpImporter::loadEmbeddedTexture(uint textureIndex)
++ {
++ aiTexture *assimpTexture = m_scene->m_aiScene->mTextures[textureIndex];
++ QAbstractTexture *texture = QAbstractNodeFactory::createNode<QTexture2D>("QTexture2D");
++ AssimpRawTextureImage *imageData = new AssimpRawTextureImage();
++
++ bool isCompressed = assimpTexture->mHeight == 0;
++ uint textureSize = assimpTexture->mWidth *
++ (isCompressed ? 1 : assimpTexture->mHeight);
++ // Set texture to RGBA8888
++ QByteArray textureContent;
++ textureContent.reserve(textureSize * 4);
++ for (uint i = 0; i < textureSize; i++) {
++ uint idx = i * 4;
++ aiTexel texel = assimpTexture->pcData[i];
++ textureContent[idx] = texel.r;
++ textureContent[idx + 1] = texel.g;
++ textureContent[idx + 2] = texel.b;
++ textureContent[idx + 3] = texel.a;
++ }
++ imageData->setData(textureContent);
++ texture->addTextureImage(imageData);
++
++ return texture;
++ }
++
++ /*!
++ * Loads the light in the current scene located at \a lightIndex.
++ */
++ QAbstractLight *AssimpImporter::loadLight(uint lightIndex)
++ {
++ aiLight *light = m_scene->m_aiScene->mLights[lightIndex];
++ // TODO: Implement me!
++ Q_UNUSED(light);
++ return nullptr;
++ }
++
++ /*!
++ * Converts the provided Assimp aiCamera in a node to a camera entity
++ */
++ Qt3DCore::QEntity *AssimpImporter::loadCamera(aiNode *node)
++ {
++ aiCamera *assimpCamera = nullptr;
++
++ for (uint i = 0; i < m_scene->m_aiScene->mNumCameras; ++i) {
++ auto camera = m_scene->m_aiScene->mCameras[i];
++ if (camera->mName == node->mName) {
++ assimpCamera = camera;
++ break;
++ }
++ }
++
++ if (assimpCamera == nullptr)
++ return nullptr;
++
++ QEntity *camera = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
++ QCameraLens *lens = QAbstractNodeFactory::createNode<QCameraLens>("QCameraLens");
++
++ lens->setObjectName(aiStringToQString(assimpCamera->mName));
++ lens->setPerspectiveProjection(qRadiansToDegrees(assimpCamera->mHorizontalFOV),
++ qMax(assimpCamera->mAspect, 1.0f),
++ assimpCamera->mClipPlaneNear,
++ assimpCamera->mClipPlaneFar);
++ camera->addComponent(lens);
++
++ QMatrix4x4 m;
++ m.lookAt(QVector3D(assimpCamera->mPosition.x, assimpCamera->mPosition.y, assimpCamera->mPosition.z),
++ QVector3D(assimpCamera->mLookAt.x, assimpCamera->mLookAt.y, assimpCamera->mLookAt.z),
++ QVector3D(assimpCamera->mUp.x, assimpCamera->mUp.y, assimpCamera->mUp.z));
++ Qt3DCore::QTransform *transform = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
++ transform->setMatrix(m);
++ camera->addComponent(transform);
++
++ return camera;
++ }
++
++ int findTimeIndex(const QList<float> &times, float time) {
++ for (int i = 0; i < times.size(); i++) {
++ if (qFuzzyCompare(times[i], time))
++ return i;
++ }
++ return -1;
++ }
++
++ void insertAtTime(QList<float> &positions, QList<Qt3DCore::QTransform *> &tranforms,
++ Qt3DCore::QTransform *t, float time)
++ {
++ if (positions.size() == 0) {
++ positions.push_back(time);
++ tranforms.push_back(t);
++ } else if (time < positions.first()) {
++ positions.push_front(time);
++ tranforms.push_front(t);
++ } else if (time > positions.last()) {
++ positions.push_back(time);
++ tranforms.push_back(t);
++ } else {
++ qWarning() << "Insert new key in the middle of the keyframe not implemented.";
++ }
++ }
++
++ // OPTIONAL
++ void AssimpImporter::loadAnimation(uint animationIndex)
++ {
++ aiAnimation *assimpAnim = m_scene->m_aiScene->mAnimations[animationIndex];
++ qCDebug(AssimpImporterLog) << "load Animation: "<< aiStringToQString(assimpAnim->mName);
++ double tickScale = 1.0;
++ if (!qFuzzyIsNull(assimpAnim->mTicksPerSecond))
++ tickScale = 1.0 / assimpAnim->mTicksPerSecond;
++
++ /* keyframe animations */
++ for (uint i = 0; i < assimpAnim->mNumChannels; ++i) {
++ aiNodeAnim *nodeAnim = assimpAnim->mChannels[i];
++ aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(nodeAnim->mNodeName);
++
++ Qt3DAnimation::QKeyframeAnimation *kfa = new Qt3DAnimation::QKeyframeAnimation();
++ QList<float> positions;
++ QList<Qt3DCore::QTransform *> transforms;
++ if ((nodeAnim->mNumPositionKeys > 1)
++ || !(nodeAnim->mNumPositionKeys == 1 && nodeAnim->mPositionKeys[0].mValue.x == 0
++ && nodeAnim->mPositionKeys[0].mValue.y == 0
++ && nodeAnim->mPositionKeys[0].mValue.z == 0)) {
++ for (uint j = 0; j < nodeAnim->mNumPositionKeys; j++) {
++ positions.push_back(nodeAnim->mPositionKeys[j].mTime);
++ Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
++ t->setTranslation(QVector3D(nodeAnim->mPositionKeys[j].mValue.x,
++ nodeAnim->mPositionKeys[j].mValue.y,
++ nodeAnim->mPositionKeys[j].mValue.z));
++ transforms.push_back(t);
++ }
++ }
++ if ((nodeAnim->mNumRotationKeys > 1) ||
++ !(nodeAnim->mNumRotationKeys == 1 && nodeAnim->mRotationKeys[0].mValue.x == 0
++ && nodeAnim->mRotationKeys[0].mValue.y == 0
++ && nodeAnim->mRotationKeys[0].mValue.z == 0
++ && nodeAnim->mRotationKeys[0].mValue.w == 1)) {
++ for (uint j = 0; j < nodeAnim->mNumRotationKeys; j++) {
++ int index = findTimeIndex(positions, nodeAnim->mRotationKeys[j].mTime);
++ if (index >= 0) {
++ Qt3DCore::QTransform *t = transforms[index];
++ t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w,
++ nodeAnim->mRotationKeys[j].mValue.x,
++ nodeAnim->mRotationKeys[j].mValue.y,
++ nodeAnim->mRotationKeys[j].mValue.z));
++ } else {
++ Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
++ t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w,
++ nodeAnim->mRotationKeys[j].mValue.x,
++ nodeAnim->mRotationKeys[j].mValue.y,
++ nodeAnim->mRotationKeys[j].mValue.z));
++ insertAtTime(positions, transforms, t, nodeAnim->mRotationKeys[j].mTime);
++ }
++ }
++ }
++ if ((nodeAnim->mNumScalingKeys > 1)
++ || !(nodeAnim->mNumScalingKeys == 1 && nodeAnim->mScalingKeys[0].mValue.x == 1
++ && nodeAnim->mScalingKeys[0].mValue.y == 1
++ && nodeAnim->mScalingKeys[0].mValue.z == 1)) {
++ for (uint j = 0; j < nodeAnim->mNumScalingKeys; j++) {
++ int index = findTimeIndex(positions, nodeAnim->mScalingKeys[j].mTime);
++ if (index >= 0) {
++ Qt3DCore::QTransform *t = transforms[index];
++ t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x,
++ nodeAnim->mScalingKeys[j].mValue.y,
++ nodeAnim->mScalingKeys[j].mValue.z));
++ } else {
++ Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
++ t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x,
++ nodeAnim->mScalingKeys[j].mValue.y,
++ nodeAnim->mScalingKeys[j].mValue.z));
++ insertAtTime(positions, transforms, t, nodeAnim->mScalingKeys[j].mTime);
++ }
++ }
++ }
++ for (int j = 0; j < positions.size(); ++j)
++ positions[j] = positions[j] * tickScale;
++ kfa->setFramePositions(positions);
++ kfa->setKeyframes(transforms);
++ kfa->setAnimationName(QString(assimpAnim->mName.C_Str()));
++ kfa->setTargetName(QString(targetNode->mName.C_Str()));
++ m_scene->m_animations.push_back(kfa);
++ }
++ /* mesh morph animations */
++ for (uint i = 0; i < assimpAnim->mNumMorphMeshChannels; ++i) {
++ aiMeshMorphAnim *morphAnim = assimpAnim->mMorphMeshChannels[i];
++ aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(morphAnim->mName);
++ aiMesh *mesh = m_scene->m_aiScene->mMeshes[targetNode->mMeshes[0]];
++
++ Qt3DAnimation::QMorphingAnimation *morphingAnimation = new Qt3DAnimation::QMorphingAnimation;
++ QList<float> positions;
++ positions.resize(morphAnim->mNumKeys);
++ // set so that weights array is allocated to correct size in morphingAnimation
++ morphingAnimation->setTargetPositions(positions);
++ for (unsigned int j = 0; j < morphAnim->mNumKeys; ++j) {
++ aiMeshMorphKey &key = morphAnim->mKeys[j];
++ positions[j] = key.mTime * tickScale;
++
++ QList<float> weights;
++ weights.resize(key.mNumValuesAndWeights);
++ for (int k = 0; k < weights.size(); k++) {
++ const unsigned int value = key.mValues[k];
++ if (value < key.mNumValuesAndWeights)
++ weights[value] = key.mWeights[k];
++ }
++ morphingAnimation->setWeights(j, weights);
++ }
++
++ morphingAnimation->setTargetPositions(positions);
++ morphingAnimation->setAnimationName(QString(assimpAnim->mName.C_Str()));
++ morphingAnimation->setTargetName(QString(targetNode->mName.C_Str()));
++ morphingAnimation->setMethod((mesh->mMethod == aiMorphingMethod_MORPH_NORMALIZED)
++ ? Qt3DAnimation::QMorphingAnimation::Normalized
++ : Qt3DAnimation::QMorphingAnimation::Relative);
++ m_scene->m_morphAnimations.push_back(morphingAnimation);
++ }
++ }
++
++ /*!
++ * Sets the object name of \a material to the name of \a assimpMaterial.
++ */
++ void AssimpImporter::copyMaterialName(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ aiString name;
++ if (assimpMaterial->Get(AI_MATKEY_NAME, name) == aiReturn_SUCCESS) {
++ // May not be necessary
++ // Kept for debug purposes at the moment
++ material->setObjectName(aiStringToQString(name));
++ qCDebug(AssimpImporterLog) << Q_FUNC_INFO << "Assimp Material " << material->objectName();
++ }
++ }
++
++ /*!
++ * Fills \a material color properties with \a assimpMaterial color properties.
++ */
++ void AssimpImporter::copyMaterialColorProperties(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ aiColor3D color;
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_DIFFUSE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_SPECULAR, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_SPECULAR_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_AMBIENT, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_AMBIENT_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_EMISSIVE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_TRANSPARENT, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_TRANSPARENT_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ if (assimpMaterial->Get(AI_MATKEY_COLOR_REFLECTIVE, color) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_REFLECTIVE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
++ }
++
++ /*!
++ * Retrieves a \a material bool property.
++ */
++ void AssimpImporter::copyMaterialBoolProperties(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ int value;
++ if (assimpMaterial->Get(AI_MATKEY_TWOSIDED, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_IS_TWOSIDED, material, (value == 0) ? false : true);
++ if (assimpMaterial->Get(AI_MATKEY_ENABLE_WIREFRAME, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_IS_WIREFRAME, material, (value == 0) ? false : true);
++ }
++
++ void AssimpImporter::copyMaterialShadingModel(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ Q_UNUSED(material);
++ Q_UNUSED(assimpMaterial);
++ // TODO
++ // Match each shading function with a default shader
++
++ // AssimpIO::assimpMaterialAttributesMap[AI_MATKEY_SHADING_MODEL] = &AssimpIO::getMaterialShadingModel;
++ // AssimpIO::assimpMaterialAttributesMap[AI_MATKEY_BLEND_FUNC] = &AssimpIO::getMaterialBlendingFunction;
++ }
++
++ void AssimpImporter::copyMaterialBlendingFunction(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ Q_UNUSED(material);
++ Q_UNUSED(assimpMaterial);
++ // TO DO
++ }
++
++ /*!
++ *
++ */
++ void AssimpImporter::copyMaterialTextures(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ static const aiTextureType textureType[] = {aiTextureType_AMBIENT,
++ aiTextureType_DIFFUSE,
++ aiTextureType_DISPLACEMENT,
++ aiTextureType_EMISSIVE,
++ aiTextureType_HEIGHT,
++ aiTextureType_LIGHTMAP,
++ aiTextureType_NORMALS,
++ aiTextureType_OPACITY,
++ aiTextureType_REFLECTION,
++ aiTextureType_SHININESS,
++ aiTextureType_SPECULAR};
++
++ if (m_scene->m_textureToParameterName.isEmpty()) {
++ m_scene->m_textureToParameterName.insert(aiTextureType_AMBIENT, ASSIMP_MATERIAL_AMBIENT_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_DIFFUSE, ASSIMP_MATERIAL_DIFFUSE_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_DISPLACEMENT, ASSIMP_MATERIAL_DISPLACEMENT_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_EMISSIVE, ASSIMP_MATERIAL_EMISSIVE_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_HEIGHT, ASSIMP_MATERIAL_HEIGHT_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_LIGHTMAP, ASSIMP_MATERIAL_LIGHTMAP_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_NORMALS, ASSIMP_MATERIAL_NORMALS_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_OPACITY, ASSIMP_MATERIAL_OPACITY_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_REFLECTION, ASSIMP_MATERIAL_REFLECTION_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_SHININESS, ASSIMP_MATERIAL_SHININESS_TEXTURE);
++ m_scene->m_textureToParameterName.insert(aiTextureType_SPECULAR, ASSIMP_MATERIAL_SPECULAR_TEXTURE);
++ }
++
++ for (unsigned int i = 0; i < sizeof(textureType)/sizeof(textureType[0]); i++) {
++ aiString path;
++ if (assimpMaterial->GetTexture(textureType[i], 0, &path) == AI_SUCCESS) {
++ const QString fullPath = m_sceneDir.absoluteFilePath(texturePath(path));
++ // Load texture if not already loaded
++ QAbstractTexture *tex = QAbstractNodeFactory::createNode<QTexture2D>("QTexture2D");
++ QTextureImage *texImage = QAbstractNodeFactory::createNode<QTextureImage>("QTextureImage");
++ texImage->setSource(QUrl::fromLocalFile(fullPath));
++ texImage->setMirrored(false);
++ tex->addTextureImage(texImage);
++
++ // Set proper wrapping mode
++ QTextureWrapMode wrapMode(QTextureWrapMode::Repeat);
++ int xMode = 0;
++ int yMode = 0;
++
++ if (assimpMaterial->Get(AI_MATKEY_MAPPINGMODE_U(textureType[i], 0), xMode) == aiReturn_SUCCESS)
++ wrapMode.setX(wrapModeFromaiTextureMapMode(xMode));
++ if (assimpMaterial->Get(AI_MATKEY_MAPPINGMODE_V(textureType[i], 0), yMode) == aiReturn_SUCCESS)
++ wrapMode.setY(wrapModeFromaiTextureMapMode(yMode));
++
++ tex->setWrapMode(wrapMode);
++
++ qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Loaded Texture " << fullPath;
++ const QString parameterName = m_scene->m_textureToParameterName[textureType[i]];
++ setParameterValue(parameterName, material, QVariant::fromValue(tex));
++
++ if (parameterName == ASSIMP_MATERIAL_NORMALS_TEXTURE) {
++ setParameterValue(ASSIMP_MATERIAL_NORMALS_TEXTURE2, material, QVariant::fromValue(tex));
++ }
++ }
++ }
++ }
++
++ /*!
++ * Retrieves a \a material float property.
++ */
++ void AssimpImporter::copyMaterialFloatProperties(QMaterial *material, aiMaterial *assimpMaterial)
++ {
++ float value = 0;
++ if (assimpMaterial->Get(AI_MATKEY_OPACITY, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_OPACITY, material, value);
++ if (assimpMaterial->Get(AI_MATKEY_SHININESS, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_SHININESS, material, value);
++ if (assimpMaterial->Get(AI_MATKEY_SHININESS_STRENGTH, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_SHININESS_STRENGTH, material, value);
++ if (assimpMaterial->Get(AI_MATKEY_REFRACTI, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_REFRACTI, material, value);
++ if (assimpMaterial->Get(AI_MATKEY_REFLECTIVITY, value) == aiReturn_SUCCESS)
++ setParameterValue(ASSIMP_MATERIAL_REFLECTIVITY, material, value);
++ }
++
++ AssimpRawTextureImage::AssimpRawTextureImage(QNode *parent)
++ : QAbstractTextureImage(parent)
++ {
++ }
++
++ QTextureImageDataGeneratorPtr AssimpRawTextureImage::dataGenerator() const
++ {
++ return QTextureImageDataGeneratorPtr(new AssimpRawTextureImageFunctor(m_data));
++ }
++
++ void AssimpRawTextureImage::setData(const QByteArray &data)
++ {
++ if (data != m_data) {
++ m_data = data;
++ notifyDataGeneratorChanged();
++ }
++ }
++
++ AssimpRawTextureImage::AssimpRawTextureImageFunctor::AssimpRawTextureImageFunctor(const QByteArray &data)
++ : QTextureImageDataGenerator()
++ , m_data(data)
++ {
++ }
++
++ QTextureImageDataPtr AssimpRawTextureImage::AssimpRawTextureImageFunctor::operator()()
++ {
++ QTextureImageDataPtr dataPtr = QTextureImageDataPtr::create();
++ // Note: we assume 4 components per pixel and not compressed for now
++ dataPtr->setData(m_data, 4, false);
++ return dataPtr;
++ }
++
++ bool AssimpRawTextureImage::AssimpRawTextureImageFunctor::operator ==(const QTextureImageDataGenerator &other) const
++ {
++ const AssimpRawTextureImageFunctor *otherFunctor = functor_cast<AssimpRawTextureImageFunctor>(&other);
++ return (otherFunctor != nullptr && otherFunctor->m_data == m_data);
++ }
++
++ AssimpImporter::SceneImporter::SceneImporter()
++ : m_importer(new Assimp::Importer())
++ , m_aiScene(nullptr)
++ {
++ // The Assimp::Importer manages the lifetime of the aiScene object
++ }
++
++
++
++ AssimpImporter::SceneImporter::~SceneImporter()
++ {
++ delete m_importer;
++ }
++
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE
++
++ #include "moc_assimpimporter.cpp"
++
++ #include "assimpimporter.moc"