| /**************************************************************************** |
| ** |
| ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "assimpimporter.h" |
| |
| #include <Qt3DCore/qentity.h> |
| #include <Qt3DCore/qtransform.h> |
| #include <Qt3DExtras/qdiffusemapmaterial.h> |
| #include <Qt3DExtras/qdiffusespecularmapmaterial.h> |
| #include <Qt3DExtras/qphongmaterial.h> |
| #include <Qt3DRender/qattribute.h> |
| #include <Qt3DRender/qbuffer.h> |
| #include <Qt3DRender/qcameralens.h> |
| #include <Qt3DRender/qeffect.h> |
| #include <Qt3DRender/qgeometry.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/qmorphphongmaterial.h> |
| #include <Qt3DExtras/qdiffusemapmaterial.h> |
| #include <Qt3DExtras/qdiffusespecularmapmaterial.h> |
| #include <Qt3DExtras/qphongmaterial.h> |
| #include <Qt3DAnimation/qkeyframeanimation.h> |
| #include <Qt3DAnimation/qmorphinganimation.h> |
| #include <QtCore/QFileInfo> |
| #include <QtGui/QColor> |
| |
| #include <qmath.h> |
| |
| #include <Qt3DCore/private/qabstractnodefactory_p.h> |
| #include <Qt3DRender/private/renderlogging_p.h> |
| #include <Qt3DRender/private/qurlhelper_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace Qt3DCore; |
| using namespace Qt3DExtras; |
| |
| namespace Qt3DRender { |
| |
| /*! |
| \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"); |
| 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) Q_DECL_NOTHROW |
| { |
| 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 hasDiffuseTexture = (assimpMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS); |
| const bool hasSpecularTexture = (assimpMaterial->GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS); |
| |
| 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(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(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 : qAsConst(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 = 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 : qAsConst(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(QVector<T *> &animations, QVector<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); |
| |
| QVector<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 : qAsConst(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); |
| |
| QVector<Qt3DAnimation::QKeyframeAnimation *> animations; |
| findAnimationsForNode<Qt3DAnimation::QKeyframeAnimation>(m_scene->m_animations, |
| animations, |
| aiStringToQString(node->mName)); |
| |
| for (Qt3DAnimation::QKeyframeAnimation *anim : qAsConst(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); |
| Qt3DRender::QBuffer *vertexBuffer = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer"); |
| vertexBuffer->setParent(meshGeometry); |
| vertexBuffer->setType(Qt3DRender::QBuffer::VertexBuffer); |
| Qt3DRender::QBuffer *indexBuffer = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer"); |
| indexBuffer->setParent(meshGeometry); |
| indexBuffer->setType(Qt3DRender::QBuffer::IndexBuffer); |
| |
| 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 (ushort 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); |
| QVector<QString> names; |
| QVector<Qt3DAnimation::QMorphTarget *> targets; |
| uint voff = 0; |
| uint noff = 0; |
| uint tanoff = 0; |
| uint texoff = 0; |
| uint coloff = 0; |
| uint offset = 0; |
| if (animesh->mVertices) { |
| names.push_back(VERTICES_ATTRIBUTE_NAME); |
| offset += 3; |
| } |
| if (animesh->mNormals) { |
| names.push_back(NORMAL_ATTRIBUTE_NAME); |
| noff = offset; |
| offset += 3; |
| } |
| if (animesh->mTangents) { |
| names.push_back(TANGENT_ATTRIBUTE_NAME); |
| tanoff = offset; |
| offset += 3; |
| } |
| if (animesh->mTextureCoords[0]) { |
| names.push_back(TEXTCOORD_ATTRIBUTE_NAME); |
| texoff = offset; |
| offset += 2; |
| } |
| if (animesh->mColors[0]) { |
| names.push_back(COLOR_ATTRIBUTE_NAME); |
| coloff = offset; |
| } |
| |
| ushort clumpSize = (animesh->mVertices ? 3 : 0) |
| + (animesh->mNormals ? 3 : 0) |
| + (animesh->mTangents ? 3 : 0) |
| + (animesh->mColors[0] ? 4 : 0) |
| + (animesh->mTextureCoords[0] ? 2 : 0); |
| |
| |
| for (uint i = 0; i < mesh->mNumAnimMeshes; i++) { |
| aiAnimMesh *animesh = mesh->mAnimMeshes[i]; |
| Qt3DAnimation::QMorphTarget *target = new Qt3DAnimation::QMorphTarget(geometryRenderer); |
| targets.push_back(target); |
| QVector<QAttribute *> attributes; |
| 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; |
| } |
| } |
| |
| Qt3DRender::QBuffer *targetBuffer |
| = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer"); |
| targetBuffer->setData(targetBufferArray); |
| targetBuffer->setParent(meshGeometry); |
| |
| if (animesh->mVertices) { |
| attributes.push_back(createAttribute(targetBuffer, VERTICES_ATTRIBUTE_NAME, |
| QAttribute::Float, 3, |
| animesh->mNumVertices, voff * sizeof(float), |
| clumpSize * sizeof(float), meshGeometry)); |
| } |
| if (animesh->mNormals) { |
| attributes.push_back(createAttribute(targetBuffer, NORMAL_ATTRIBUTE_NAME, |
| QAttribute::Float, 3, |
| animesh->mNumVertices, noff * sizeof(float), |
| clumpSize * sizeof(float), meshGeometry)); |
| } |
| if (animesh->mTangents) { |
| attributes.push_back(createAttribute(targetBuffer, TANGENT_ATTRIBUTE_NAME, |
| QAttribute::Float, 3, |
| animesh->mNumVertices, tanoff * sizeof(float), |
| clumpSize * sizeof(float), meshGeometry)); |
| } |
| if (animesh->mTextureCoords[0]) { |
| attributes.push_back(createAttribute(targetBuffer, TEXTCOORD_ATTRIBUTE_NAME, |
| QAttribute::Float, 2, |
| animesh->mNumVertices, texoff * sizeof(float), |
| clumpSize * sizeof(float), meshGeometry)); |
| } |
| if (animesh->mColors[0]) { |
| attributes.push_back(createAttribute(targetBuffer, COLOR_ATTRIBUTE_NAME, |
| QAttribute::Float, 4, |
| animesh->mNumVertices, coloff * 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 QVector<float> ×, float time) { |
| for (int i = 0; i < times.size(); i++) { |
| if (qFuzzyCompare(times[i], time)) |
| return i; |
| } |
| return -1; |
| } |
| |
| void insertAtTime(QVector<float> &positions, QVector<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(); |
| QVector<float> positions; |
| QVector<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; |
| QVector<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; |
| |
| QVector<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; |
| setParameterValue(m_scene->m_textureToParameterName[textureType[i]], |
| 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 "assimpimporter.moc" |