1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "assimpimporter.h"
41
42#include <Qt3DCore/qentity.h>
43#include <Qt3DCore/qtransform.h>
44#include <Qt3DExtras/qdiffusemapmaterial.h>
45#include <Qt3DExtras/qdiffusespecularmapmaterial.h>
46#include <Qt3DExtras/qphongmaterial.h>
47#include <Qt3DRender/qattribute.h>
48#include <Qt3DRender/qbuffer.h>
49#include <Qt3DRender/qcameralens.h>
50#include <Qt3DRender/qeffect.h>
51#include <Qt3DRender/qgeometry.h>
52#include <Qt3DRender/qgeometryrenderer.h>
53#include <Qt3DRender/qmaterial.h>
54#include <Qt3DRender/qmesh.h>
55#include <Qt3DRender/qparameter.h>
56#include <Qt3DRender/qtexture.h>
57#include <Qt3DRender/qtextureimagedatagenerator.h>
58#include <Qt3DExtras/qmorphphongmaterial.h>
59#include <Qt3DExtras/qdiffusemapmaterial.h>
60#include <Qt3DExtras/qdiffusespecularmapmaterial.h>
61#include <Qt3DExtras/qphongmaterial.h>
62#include <Qt3DAnimation/qkeyframeanimation.h>
63#include <Qt3DAnimation/qmorphinganimation.h>
64#include <QtCore/QFileInfo>
65#include <QtGui/QColor>
66
67#include <qmath.h>
68
69#include <Qt3DCore/private/qabstractnodefactory_p.h>
70#include <Qt3DRender/private/renderlogging_p.h>
71#include <Qt3DRender/private/qurlhelper_p.h>
72
73QT_BEGIN_NAMESPACE
74
75using namespace Qt3DCore;
76using namespace Qt3DExtras;
77
78namespace Qt3DRender {
79
80/*!
81 \class Qt3DRender::AssimpImporter
82 \inmodule Qt3DRender
83 \since 5.5
84 \internal
85
86 \brief Provides a generic way of loading various 3D assets
87 format into a Qt3D scene.
88
89 It should be noted that Assimp aiString is explicitly defined to be UTF-8.
90*/
91
92Q_LOGGING_CATEGORY(AssimpImporterLog, "Qt3D.AssimpImporter", QtWarningMsg)
93
94namespace {
95
96const QString ASSIMP_MATERIAL_DIFFUSE_COLOR = QLatin1String("kd");
97const QString ASSIMP_MATERIAL_SPECULAR_COLOR = QLatin1String("ks");
98const QString ASSIMP_MATERIAL_AMBIENT_COLOR = QLatin1String("ka");
99const QString ASSIMP_MATERIAL_EMISSIVE_COLOR = QLatin1String("emissive");
100const QString ASSIMP_MATERIAL_TRANSPARENT_COLOR = QLatin1String("transparent");
101const QString ASSIMP_MATERIAL_REFLECTIVE_COLOR = QLatin1String("reflective");
102
103const QString ASSIMP_MATERIAL_DIFFUSE_TEXTURE = QLatin1String("diffuseTexture");
104const QString ASSIMP_MATERIAL_AMBIENT_TEXTURE = QLatin1String("ambientTex");
105const QString ASSIMP_MATERIAL_SPECULAR_TEXTURE = QLatin1String("specularTexture");
106const QString ASSIMP_MATERIAL_EMISSIVE_TEXTURE = QLatin1String("emissiveTex");
107const QString ASSIMP_MATERIAL_NORMALS_TEXTURE = QLatin1String("normalsTex");
108const QString ASSIMP_MATERIAL_OPACITY_TEXTURE = QLatin1String("opacityTex");
109const QString ASSIMP_MATERIAL_REFLECTION_TEXTURE = QLatin1String("reflectionTex");
110const QString ASSIMP_MATERIAL_HEIGHT_TEXTURE = QLatin1String("heightTex");
111const QString ASSIMP_MATERIAL_LIGHTMAP_TEXTURE = QLatin1String("opacityTex");
112const QString ASSIMP_MATERIAL_DISPLACEMENT_TEXTURE = QLatin1String("displacementTex");
113const QString ASSIMP_MATERIAL_SHININESS_TEXTURE = QLatin1String("shininessTex");
114
115const QString ASSIMP_MATERIAL_IS_TWOSIDED = QLatin1String("twosided");
116const QString ASSIMP_MATERIAL_IS_WIREFRAME = QLatin1String("wireframe");
117
118const QString ASSIMP_MATERIAL_OPACITY = QLatin1String("opacity");
119const QString ASSIMP_MATERIAL_SHININESS = QLatin1String("shininess");
120const QString ASSIMP_MATERIAL_SHININESS_STRENGTH = QLatin1String("shininess_strength");
121const QString ASSIMP_MATERIAL_REFRACTI = QLatin1String("refracti");
122const QString ASSIMP_MATERIAL_REFLECTIVITY = QLatin1String("reflectivity");
123
124const QString ASSIMP_MATERIAL_NAME = QLatin1String("name");
125
126const QString VERTICES_ATTRIBUTE_NAME = QAttribute::defaultPositionAttributeName();
127const QString NORMAL_ATTRIBUTE_NAME = QAttribute::defaultNormalAttributeName();
128const QString TANGENT_ATTRIBUTE_NAME = QAttribute::defaultTangentAttributeName();
129const QString TEXTCOORD_ATTRIBUTE_NAME = QAttribute::defaultTextureCoordinateAttributeName();
130const QString COLOR_ATTRIBUTE_NAME = QAttribute::defaultColorAttributeName();
131
132/*
133 * Returns a QMatrix4x4 from \a matrix;
134 */
135QMatrix4x4 aiMatrix4x4ToQMatrix4x4(const aiMatrix4x4 &matrix) Q_DECL_NOTHROW
136{
137 return QMatrix4x4(matrix.a1, matrix.a2, matrix.a3, matrix.a4,
138 matrix.b1, matrix.b2, matrix.b3, matrix.b4,
139 matrix.c1, matrix.c2, matrix.c3, matrix.c4,
140 matrix.d1, matrix.d2, matrix.d3, matrix.d4);
141}
142
143/*
144 * Returns a QString from \a str;
145 */
146inline QString aiStringToQString(const aiString &str)
147{
148 return QString::fromUtf8(str.data, int(str.length));
149}
150
151QMaterial *createBestApproachingMaterial(const aiMaterial *assimpMaterial)
152{
153 aiString path; // unused but necessary
154 const bool hasDiffuseTexture = (assimpMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS);
155 const bool hasSpecularTexture = (assimpMaterial->GetTexture(aiTextureType_SPECULAR, 0, &path) == AI_SUCCESS);
156
157 if (hasDiffuseTexture && hasSpecularTexture)
158 return QAbstractNodeFactory::createNode<QDiffuseSpecularMapMaterial>("QDiffuseSpecularMapMaterial");
159 if (hasDiffuseTexture)
160 return QAbstractNodeFactory::createNode<QDiffuseMapMaterial>("QDiffuseMapMaterial");
161 return QAbstractNodeFactory::createNode<QPhongMaterial>("QPhongMaterial");
162}
163
164QString texturePath(const aiString &path)
165{
166 QString p = aiStringToQString(path);
167 p.replace(QLatin1String("\\"), QLatin1String("/"));
168 if (p.startsWith('/'))
169 p.remove(0, 1);
170 return p;
171}
172
173/*
174 * Returns the Qt3DRender::QParameter with named \a name if contained by the material
175 * \a material. If the material doesn't contain the named parameter, a new
176 * Qt3DRender::QParameter is created and inserted into the material.
177 */
178QParameter *findNamedParameter(const QString &name, QMaterial *material)
179{
180 // Does the material contain the parameter ?
181 const auto params = material->parameters();
182 for (QParameter *p : params) {
183 if (p->name() == name)
184 return p;
185 }
186
187 // Does the material's effect contain the parameter ?
188 if (material->effect()) {
189 const QEffect *e = material->effect();
190 const auto params = e->parameters();
191 for (QParameter *p : params) {
192 if (p->name() == name)
193 return p;
194 }
195 }
196
197 // Create and add parameter to material
198 QParameter *p = QAbstractNodeFactory::createNode<QParameter>("QParameter");
199 p->setParent(material);
200 p->setName(name);
201 material->addParameter(p);
202 return p;
203}
204
205void setParameterValue(const QString &name, QMaterial *material, const QVariant &value)
206{
207 QParameter *p = findNamedParameter(name, material);
208 p->setValue(value);
209}
210
211QAttribute *createAttribute(QBuffer *buffer,
212 const QString &name,
213 QAttribute::VertexBaseType vertexBaseType,
214 uint vertexSize,
215 uint count,
216 uint byteOffset = 0,
217 uint byteStride = 0,
218 QNode *parent = nullptr)
219{
220 QAttribute *attribute = QAbstractNodeFactory::createNode<QAttribute>("QAttribute");
221 attribute->setBuffer(buffer);
222 attribute->setName(name);
223 attribute->setVertexBaseType(vertexBaseType);
224 attribute->setVertexSize(vertexSize);
225 attribute->setCount(count);
226 attribute->setByteOffset(byteOffset);
227 attribute->setByteStride(byteStride);
228 attribute->setParent(parent);
229 return attribute;
230}
231
232QAttribute *createIndexAttribute(QBuffer *buffer,
233 QAttribute::VertexBaseType vertexBaseType,
234 uint vertexSize,
235 uint count,
236 uint byteOffset = 0,
237 uint byteStride = 0,
238 QNode *parent = nullptr)
239{
240 QAttribute *attribute = QAbstractNodeFactory::createNode<QAttribute>("QAttribute");
241 attribute->setBuffer(buffer);
242 attribute->setVertexBaseType(vertexBaseType);
243 attribute->setVertexSize(vertexSize);
244 attribute->setCount(count);
245 attribute->setByteOffset(byteOffset);
246 attribute->setByteStride(byteStride);
247 attribute->setParent(parent);
248 return attribute;
249}
250
251QTextureWrapMode::WrapMode wrapModeFromaiTextureMapMode(int mode)
252{
253 switch (mode) {
254 case aiTextureMapMode_Wrap:
255 return QTextureWrapMode::Repeat;
256 case aiTextureMapMode_Mirror:
257 return QTextureWrapMode::MirroredRepeat;
258 case aiTextureMapMode_Decal:
259 return QTextureWrapMode::ClampToBorder;
260 case aiTextureMapMode_Clamp:
261 default:
262 return QTextureWrapMode::ClampToEdge;
263 }
264}
265
266} // anonymous
267
268QStringList AssimpImporter::assimpSupportedFormatsList = AssimpImporter::assimpSupportedFormats();
269
270/*!
271 * Returns a QStringlist with the suffixes of the various supported asset formats.
272 */
273QStringList AssimpImporter::assimpSupportedFormats()
274{
275 QStringList formats;
276
277 formats.reserve(60);
278 formats.append(QStringLiteral("3d"));
279 formats.append(QStringLiteral("3ds"));
280 formats.append(QStringLiteral("ac"));
281 formats.append(QStringLiteral("ac3d"));
282 formats.append(QStringLiteral("acc"));
283 formats.append(QStringLiteral("ase"));
284 formats.append(QStringLiteral("ask"));
285 formats.append(QStringLiteral("assbin"));
286 formats.append(QStringLiteral("b3d"));
287 formats.append(QStringLiteral("blend"));
288 formats.append(QStringLiteral("bvh"));
289 formats.append(QStringLiteral("cob"));
290 formats.append(QStringLiteral("csm"));
291 formats.append(QStringLiteral("dae"));
292 formats.append(QStringLiteral("dxf"));
293 formats.append(QStringLiteral("enff"));
294 formats.append(QStringLiteral("fbx"));
295 formats.append(QStringLiteral("hmp"));
296 formats.append(QStringLiteral("irr"));
297 formats.append(QStringLiteral("irrmesh"));
298 formats.append(QStringLiteral("lwo"));
299 formats.append(QStringLiteral("lws"));
300 formats.append(QStringLiteral("lxo"));
301 formats.append(QStringLiteral("md2"));
302 formats.append(QStringLiteral("md3"));
303 formats.append(QStringLiteral("md5anim"));
304 formats.append(QStringLiteral("md5camera"));
305 formats.append(QStringLiteral("md5mesh"));
306 formats.append(QStringLiteral("mdc"));
307 formats.append(QStringLiteral("mdl"));
308 formats.append(QStringLiteral("mesh.xml"));
309 formats.append(QStringLiteral("mot"));
310 formats.append(QStringLiteral("ms3d"));
311 formats.append(QStringLiteral("ndo"));
312 formats.append(QStringLiteral("nff"));
313 formats.append(QStringLiteral("obj"));
314 formats.append(QStringLiteral("off"));
315 formats.append(QStringLiteral("ogex"));
316 formats.append(QStringLiteral("pk3"));
317 formats.append(QStringLiteral("ply"));
318 formats.append(QStringLiteral("prj"));
319 formats.append(QStringLiteral("q3o"));
320 formats.append(QStringLiteral("q3s"));
321 formats.append(QStringLiteral("raw"));
322 formats.append(QStringLiteral("scn"));
323 formats.append(QStringLiteral("sib"));
324 formats.append(QStringLiteral("smd"));
325 formats.append(QStringLiteral("stl"));
326 formats.append(QStringLiteral("ter"));
327 formats.append(QStringLiteral("uc"));
328 formats.append(QStringLiteral("vta"));
329 formats.append(QStringLiteral("x"));
330 formats.append(QStringLiteral("xml"));
331
332 return formats;
333}
334
335class AssimpRawTextureImage : public QAbstractTextureImage
336{
337 Q_OBJECT
338public:
339 explicit AssimpRawTextureImage(QNode *parent = 0);
340
341 QTextureImageDataGeneratorPtr dataGenerator() const final;
342
343 void setData(const QByteArray &data);
344
345private:
346 QByteArray m_data;
347
348 class AssimpRawTextureImageFunctor : public QTextureImageDataGenerator
349 {
350 public:
351 explicit AssimpRawTextureImageFunctor(const QByteArray &data);
352
353 QTextureImageDataPtr operator()() final;
354 bool operator ==(const QTextureImageDataGenerator &other) const final;
355
356 QT3D_FUNCTOR(AssimpRawTextureImageFunctor)
357 private:
358 QByteArray m_data;
359 };
360};
361
362/*!
363 * Constructor. Initializes a new instance of AssimpImporter.
364 */
365AssimpImporter::AssimpImporter() : QSceneImporter(),
366 m_sceneParsed(false),
367 m_scene(nullptr)
368{
369}
370
371/*!
372 * Destructor. Cleans the parser properly before destroying it.
373 */
374AssimpImporter::~AssimpImporter()
375{
376 cleanup();
377}
378
379/*!
380 * Returns \c true if the extensions are supported
381 * by the Assimp Assets importer.
382 */
383bool AssimpImporter::areAssimpExtensions(const QStringList &extensions)
384{
385 for (const auto &ext : qAsConst(extensions))
386 if (AssimpImporter::assimpSupportedFormatsList.contains(ext.toLower()))
387 return true;
388 return false;
389}
390
391/*!
392 * Sets the \a source used by the parser to load the asset file.
393 * If the file is valid, this will trigger parsing of the file.
394 */
395void AssimpImporter::setSource(const QUrl &source)
396{
397 const QString path = QUrlHelper::urlToLocalFileOrQrc(source);
398 QFileInfo file(path);
399 m_sceneDir = file.absoluteDir();
400 if (!file.exists()) {
401 qCWarning(AssimpImporterLog) << "File missing " << path;
402 return ;
403 }
404 readSceneFile(path);
405}
406
407/*!
408 * Sets the \a basePath used by the parser to load the asset file.
409 * If the file specified in \a data is valid, this will trigger parsing of the file.
410 */
411void AssimpImporter::setData(const QByteArray &data, const QString &basePath)
412{
413 readSceneData(data, basePath);
414}
415
416/*!
417 * Returns \c true if the extension in QStringList \a extensions is supported by
418 * the assimp parser.
419 */
420bool AssimpImporter::areFileTypesSupported(const QStringList &extensions) const
421{
422 return AssimpImporter::areAssimpExtensions(extensions);
423}
424
425/*!
426 * Returns a Entity node which is the root node of the scene
427 * node specified by \a id. If \a id is empty, the scene is assumed to be
428 * the root node of the scene.
429 *
430 * Returns \c nullptr if \a id was specified but no node matching it was found.
431 */
432Qt3DCore::QEntity *AssimpImporter::scene(const QString &id)
433{
434 // m_aiScene shouldn't be null.
435 // If it is either, the file failed to be imported or
436 // setFilePath was not called
437 if (m_scene == nullptr || m_scene->m_aiScene == nullptr)
438 return nullptr;
439
440 aiNode *rootNode = m_scene->m_aiScene->mRootNode;
441 // if id specified, tries to find node
442 if (!id.isEmpty() &&
443 !(rootNode = rootNode->FindNode(id.toUtf8().constData()))) {
444 qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Couldn't find requested scene node";
445 return nullptr;
446 }
447
448 // Builds the Qt3D scene using the Assimp aiScene
449 // and the various dicts filled previously by parse
450 Qt3DCore::QEntity *n = node(rootNode);
451 if (m_scene->m_animations.size() > 0) {
452 qWarning() << "No target found for " << m_scene->m_animations.size() << " animations!";
453
454 for (Qt3DAnimation::QKeyframeAnimation *anim : qAsConst(m_scene->m_animations))
455 delete anim;
456 m_scene->m_animations.clear();
457 }
458 return n;
459}
460
461/*!
462 * Returns a Node from the scene identified by \a id.
463 * Returns \c nullptr if the node was not found.
464 */
465Qt3DCore::QEntity *AssimpImporter::node(const QString &id)
466{
467 if (m_scene == nullptr || m_scene->m_aiScene == nullptr)
468 return nullptr;
469 parse();
470 aiNode *n = m_scene->m_aiScene->mRootNode->FindNode(id.toUtf8().constData());
471 return node(n);
472}
473
474template <typename T>
475void findAnimationsForNode(QVector<T *> &animations, QVector<T *> &result, const QString &name)
476{
477 for (T *anim : animations) {
478 if (anim->targetName() == name) {
479 result.push_back(anim);
480 animations.removeAll(anim);
481 }
482 }
483}
484
485/*!
486 * Returns a Node from an Assimp aiNode \a node.
487 */
488Qt3DCore::QEntity *AssimpImporter::node(aiNode *node)
489{
490 if (node == nullptr)
491 return nullptr;
492 QEntity *entityNode = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
493 entityNode->setObjectName(aiStringToQString(node->mName));
494
495 // Add Meshes to the node
496 for (uint i = 0; i < node->mNumMeshes; i++) {
497 uint meshIndex = node->mMeshes[i];
498 QGeometryRenderer *mesh = loadMesh(meshIndex);
499
500 // mesh material
501 QMaterial *material;
502
503 QList<Qt3DAnimation::QMorphingAnimation *> morphingAnimations
504 = mesh->findChildren<Qt3DAnimation::QMorphingAnimation *>();
505 if (morphingAnimations.size() > 0) {
506 material = new Qt3DExtras::QMorphPhongMaterial(entityNode);
507
508 QVector<Qt3DAnimation::QMorphingAnimation *> animations;
509 findAnimationsForNode<Qt3DAnimation::QMorphingAnimation>(m_scene->m_morphAnimations,
510 animations,
511 aiStringToQString(node->mName));
512 const auto morphTargetList = morphingAnimations.at(0)->morphTargetList();
513 for (Qt3DAnimation::QMorphingAnimation *anim : qAsConst(animations)) {
514 anim->setParent(entityNode);
515 anim->setTarget(mesh);
516 anim->setMorphTargets(morphTargetList);
517 }
518
519 for (int j = 0; j < animations.size(); ++j) {
520 QObject::connect(animations[j], &Qt3DAnimation::QMorphingAnimation::interpolatorChanged,
521 (Qt3DExtras::QMorphPhongMaterial *)material,
522 &Qt3DExtras::QMorphPhongMaterial::setInterpolator);
523 }
524 morphingAnimations[0]->deleteLater();
525 } else {
526 uint materialIndex = m_scene->m_aiScene->mMeshes[meshIndex]->mMaterialIndex;
527 material = loadMaterial(materialIndex);
528 }
529
530 if (node->mNumMeshes == 1) {
531 if (material)
532 entityNode->addComponent(material);
533 // mesh
534 entityNode->addComponent(mesh);
535 } else {
536 QEntity *childEntity = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
537 childEntity->setObjectName(entityNode->objectName() + QLatin1String("_Child") + QString::number(i));
538 if (material)
539 childEntity->addComponent(material);
540 childEntity->addComponent(mesh);
541 childEntity->setParent(entityNode);
542
543 Qt3DCore::QTransform *transform
544 = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
545 childEntity->addComponent(transform);
546 }
547 }
548
549 // Add Children to Node
550 for (uint i = 0; i < node->mNumChildren; i++) {
551 // this-> is necessary here otherwise
552 // it conflicts with the variable node
553 QEntity *child = this->node(node->mChildren[i]);
554 // Are we sure each child are unique ???
555 if (child != nullptr)
556 child->setParent(entityNode);
557 }
558
559 // Add Transformations
560 const QMatrix4x4 qTransformMatrix = aiMatrix4x4ToQMatrix4x4(node->mTransformation);
561 Qt3DCore::QTransform *transform = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
562 transform->setMatrix(qTransformMatrix);
563 entityNode->addComponent(transform);
564
565 QVector<Qt3DAnimation::QKeyframeAnimation *> animations;
566 findAnimationsForNode<Qt3DAnimation::QKeyframeAnimation>(m_scene->m_animations,
567 animations,
568 aiStringToQString(node->mName));
569
570 for (Qt3DAnimation::QKeyframeAnimation *anim : qAsConst(animations)) {
571 anim->setTarget(transform);
572 anim->setParent(entityNode);
573 }
574
575 // Add Camera
576 auto camera = loadCamera(node);
577 if (camera)
578 camera->setParent(entityNode);
579
580 // TO DO : Add lights ....
581
582 return entityNode;
583}
584
585/*!
586 * Reads the scene file pointed by \a path and launches the parsing of
587 * the scene using Assimp, after having cleaned up previously saved values
588 * from eventual previous parsings.
589 */
590void AssimpImporter::readSceneFile(const QString &path)
591{
592 cleanup();
593
594 m_scene = new SceneImporter();
595
596 // SET THIS TO REMOVE POINTS AND LINES -> HAVE ONLY TRIANGLES
597 m_scene->m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE|aiPrimitiveType_POINT);
598 // SET CUSTOM FILE HANDLER TO HANDLE FILE READING THROUGH QT (RESOURCES, SOCKET ...)
599 m_scene->m_importer->SetIOHandler(new AssimpHelper::AssimpIOSystem());
600
601 // type and aiProcess_Triangulate discompose polygons with more than 3 points in triangles
602 // aiProcess_SortByPType makes sure that meshes data are triangles
603 m_scene->m_aiScene = m_scene->m_importer->ReadFile(path.toUtf8().constData(),
604 aiProcess_SortByPType|
605 aiProcess_Triangulate|
606 aiProcess_GenSmoothNormals|
607 aiProcess_FlipUVs);
608 if (m_scene->m_aiScene == nullptr) {
609 qCWarning(AssimpImporterLog) << "Assimp scene import failed" << m_scene->m_importer->GetErrorString();
610 QSceneImporter::logError(QString::fromUtf8(m_scene->m_importer->GetErrorString()));
611 return ;
612 }
613 parse();
614}
615
616/*!
617 * Reads the scene file pointed by \a path and launches the parsing of
618 * the scene using Assimp, after having cleaned up previously saved values
619 * from eventual previous parsings.
620 */
621void AssimpImporter::readSceneData(const QByteArray& data, const QString &basePath)
622{
623 Q_UNUSED(basePath);
624 cleanup();
625
626 m_scene = new SceneImporter();
627
628 // SET THIS TO REMOVE POINTS AND LINES -> HAVE ONLY TRIANGLES
629 m_scene->m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE|aiPrimitiveType_POINT);
630 // SET CUSTOM FILE HANDLER TO HANDLE FILE READING THROUGH QT (RESOURCES, SOCKET ...)
631 m_scene->m_importer->SetIOHandler(new AssimpHelper::AssimpIOSystem());
632
633 // type and aiProcess_Triangulate discompose polygons with more than 3 points in triangles
634 // aiProcess_SortByPType makes sure that meshes data are triangles
635 m_scene->m_aiScene = m_scene->m_importer->ReadFileFromMemory(data.data(), data.size(),
636 aiProcess_SortByPType|
637 aiProcess_Triangulate|
638 aiProcess_GenSmoothNormals|
639 aiProcess_FlipUVs);
640 if (m_scene->m_aiScene == nullptr) {
641 qCWarning(AssimpImporterLog) << "Assimp scene import failed";
642 return ;
643 }
644 parse();
645}
646
647/*!
648 * Cleans the various dictionaries holding the scene's information.
649 */
650void AssimpImporter::cleanup()
651{
652 m_sceneParsed = false;
653 delete m_scene;
654 m_scene = nullptr;
655}
656
657/*!
658 * Parses the aiScene provided py Assimp and converts Assimp
659 * values to Qt3D values.
660 */
661void AssimpImporter::parse()
662{
663 if (!m_sceneParsed) {
664 // Set parsed flags
665 m_sceneParsed = !m_sceneParsed;
666
667 for (uint i = 0; i < m_scene->m_aiScene->mNumAnimations; i++)
668 loadAnimation(i);
669 }
670}
671
672/*!
673 * Converts the provided Assimp aiMaterial identified by \a materialIndex to a
674 * Qt3D material
675 * \sa Material
676 */
677QMaterial *AssimpImporter::loadMaterial(uint materialIndex)
678{
679 // Generates default material based on what the assimp material contains
680 aiMaterial *assimpMaterial = m_scene->m_aiScene->mMaterials[materialIndex];
681 QMaterial *material = createBestApproachingMaterial(assimpMaterial);
682 // Material Name
683 copyMaterialName(material, assimpMaterial);
684 copyMaterialColorProperties(material, assimpMaterial);
685 copyMaterialBoolProperties(material, assimpMaterial);
686 copyMaterialFloatProperties(material, assimpMaterial);
687
688 // Add textures to materials dict
689 copyMaterialTextures(material, assimpMaterial);
690
691 return material;
692}
693
694/*!
695 * Converts the Assimp aiMesh mesh identified by \a meshIndex to a QGeometryRenderer
696 * \sa QGeometryRenderer
697 */
698QGeometryRenderer *AssimpImporter::loadMesh(uint meshIndex)
699{
700 aiMesh *mesh = m_scene->m_aiScene->mMeshes[meshIndex];
701
702 QGeometryRenderer *geometryRenderer = QAbstractNodeFactory::createNode<QGeometryRenderer>("QGeometryRenderer");
703 QGeometry *meshGeometry = QAbstractNodeFactory::createNode<QGeometry>("QGeometry");
704 meshGeometry->setParent(geometryRenderer);
705 Qt3DRender::QBuffer *vertexBuffer = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer");
706 vertexBuffer->setParent(meshGeometry);
707 vertexBuffer->setType(Qt3DRender::QBuffer::VertexBuffer);
708 Qt3DRender::QBuffer *indexBuffer = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer");
709 indexBuffer->setParent(meshGeometry);
710 indexBuffer->setType(Qt3DRender::QBuffer::IndexBuffer);
711
712 geometryRenderer->setGeometry(meshGeometry);
713
714 // Primitive are always triangles with the current Assimp's configuration
715
716 // Vertices and Normals always present with the current Assimp's configuration
717 aiVector3D *vertices = mesh->mVertices;
718 aiVector3D *normals = mesh->mNormals;
719 aiColor4D *colors = mesh->mColors[0];
720 // Tangents and TextureCoord not always present
721 bool hasTangent = mesh->HasTangentsAndBitangents();
722 bool hasTexture = mesh->HasTextureCoords(0);
723 bool hasColor = (colors != NULL); // NULL defined by Assimp
724 aiVector3D *tangents = hasTangent ? mesh->mTangents : nullptr;
725 aiVector3D *textureCoord = hasTexture ? mesh->mTextureCoords[0] : nullptr;
726
727 // Add values in raw float array
728 ushort chunkSize = 6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0) + (hasColor ? 4 : 0);
729 QByteArray bufferArray;
730 bufferArray.resize(chunkSize * mesh->mNumVertices * sizeof(float));
731 float *vbufferContent = reinterpret_cast<float*>(bufferArray.data());
732 for (uint i = 0; i < mesh->mNumVertices; i++) {
733 uint idx = i * chunkSize;
734 // position
735 vbufferContent[idx] = vertices[i].x;
736 vbufferContent[idx + 1] = vertices[i].y;
737 vbufferContent[idx + 2] = vertices[i].z;
738 // normals
739 vbufferContent[idx + 3] = normals[i].x;
740 vbufferContent[idx + 4] = normals[i].y;
741 vbufferContent[idx + 5] = normals[i].z;
742
743 if (hasTangent) {
744 vbufferContent[idx + 6] = tangents[i].x;
745 vbufferContent[idx + 7] = tangents[i].y;
746 vbufferContent[idx + 8] = tangents[i].z;
747 }
748 if (hasTexture) {
749 char offset = (hasTangent ? 9 : 6);
750 vbufferContent[idx + offset] = textureCoord[i].x;
751 vbufferContent[idx + offset + 1] = textureCoord[i].y;
752 }
753 if (hasColor) {
754 char offset = 6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0);
755 vbufferContent[idx + offset] = colors[i].r;
756 vbufferContent[idx + offset + 1] = colors[i].g;
757 vbufferContent[idx + offset + 2] = colors[i].b;
758 vbufferContent[idx + offset + 3] = colors[i].a;
759 }
760 }
761
762 vertexBuffer->setData(bufferArray);
763
764 // Add vertex attributes to the mesh with the right array
765 QAttribute *positionAttribute = createAttribute(vertexBuffer, VERTICES_ATTRIBUTE_NAME,
766 QAttribute::Float, 3,
767 mesh->mNumVertices,
768 0,
769 chunkSize * sizeof(float));
770
771 QAttribute *normalAttribute = createAttribute(vertexBuffer, NORMAL_ATTRIBUTE_NAME,
772 QAttribute::Float, 3,
773 mesh->mNumVertices,
774 3 * sizeof(float),
775 chunkSize * sizeof(float));
776
777 meshGeometry->addAttribute(positionAttribute);
778 meshGeometry->addAttribute(normalAttribute);
779
780 if (hasTangent) {
781 QAttribute *tangentsAttribute = createAttribute(vertexBuffer, TANGENT_ATTRIBUTE_NAME,
782 QAttribute::Float, 3,
783 mesh->mNumVertices,
784 6 * sizeof(float),
785 chunkSize * sizeof(float));
786 meshGeometry->addAttribute(tangentsAttribute);
787 }
788
789 if (hasTexture) {
790 QAttribute *textureCoordAttribute = createAttribute(vertexBuffer, TEXTCOORD_ATTRIBUTE_NAME,
791 QAttribute::Float, 2,
792 mesh->mNumVertices,
793 (hasTangent ? 9 : 6) * sizeof(float),
794 chunkSize * sizeof(float));
795 meshGeometry->addAttribute(textureCoordAttribute);
796 }
797
798 if (hasColor) {
799 QAttribute *colorAttribute = createAttribute(vertexBuffer, COLOR_ATTRIBUTE_NAME,
800 QAttribute::Float, 4,
801 mesh->mNumVertices,
802 (6 + (hasTangent ? 3 : 0) + (hasTexture ? 2 : 0)) * sizeof(float),
803 chunkSize * sizeof(float));
804 meshGeometry->addAttribute(colorAttribute);
805 }
806
807 QAttribute::VertexBaseType indiceType;
808 QByteArray ibufferContent;
809 uint indices = mesh->mNumFaces * 3;
810 // If there are less than 65535 indices, indices can then fit in ushort
811 // which saves video memory
812 if (indices >= USHRT_MAX) {
813 indiceType = QAttribute::UnsignedInt;
814 ibufferContent.resize(indices * sizeof(quint32));
815 for (uint i = 0; i < mesh->mNumFaces; i++) {
816 aiFace face = mesh->mFaces[i];
817 Q_ASSERT(face.mNumIndices == 3);
818 memcpy(&reinterpret_cast<quint32*>(ibufferContent.data())[i * 3], face.mIndices, 3 * sizeof(uint));
819 }
820 }
821 else {
822 indiceType = QAttribute::UnsignedShort;
823 ibufferContent.resize(indices * sizeof(quint16));
824 for (uint i = 0; i < mesh->mNumFaces; i++) {
825 aiFace face = mesh->mFaces[i];
826 Q_ASSERT(face.mNumIndices == 3);
827 for (ushort j = 0; j < face.mNumIndices; j++)
828 reinterpret_cast<quint16*>(ibufferContent.data())[i * 3 + j] = face.mIndices[j];
829 }
830 }
831
832 indexBuffer->setData(ibufferContent);
833
834 // Add indices attributes
835 QAttribute *indexAttribute = createIndexAttribute(indexBuffer, indiceType, 1, indices);
836 indexAttribute->setAttributeType(QAttribute::IndexAttribute);
837
838 meshGeometry->addAttribute(indexAttribute);
839
840 if (mesh->mNumAnimMeshes > 0) {
841
842 aiAnimMesh *animesh = mesh->mAnimMeshes[0];
843
844 if (animesh->mNumVertices != mesh->mNumVertices)
845 return geometryRenderer;
846
847 Qt3DAnimation::QMorphingAnimation *morphingAnimation
848 = new Qt3DAnimation::QMorphingAnimation(geometryRenderer);
849 QVector<QString> names;
850 QVector<Qt3DAnimation::QMorphTarget *> targets;
851 uint voff = 0;
852 uint noff = 0;
853 uint tanoff = 0;
854 uint texoff = 0;
855 uint coloff = 0;
856 uint offset = 0;
857 if (animesh->mVertices) {
858 names.push_back(VERTICES_ATTRIBUTE_NAME);
859 offset += 3;
860 }
861 if (animesh->mNormals) {
862 names.push_back(NORMAL_ATTRIBUTE_NAME);
863 noff = offset;
864 offset += 3;
865 }
866 if (animesh->mTangents) {
867 names.push_back(TANGENT_ATTRIBUTE_NAME);
868 tanoff = offset;
869 offset += 3;
870 }
871 if (animesh->mTextureCoords[0]) {
872 names.push_back(TEXTCOORD_ATTRIBUTE_NAME);
873 texoff = offset;
874 offset += 2;
875 }
876 if (animesh->mColors[0]) {
877 names.push_back(COLOR_ATTRIBUTE_NAME);
878 coloff = offset;
879 }
880
881 ushort clumpSize = (animesh->mVertices ? 3 : 0)
882 + (animesh->mNormals ? 3 : 0)
883 + (animesh->mTangents ? 3 : 0)
884 + (animesh->mColors[0] ? 4 : 0)
885 + (animesh->mTextureCoords[0] ? 2 : 0);
886
887
888 for (uint i = 0; i < mesh->mNumAnimMeshes; i++) {
889 aiAnimMesh *animesh = mesh->mAnimMeshes[i];
890 Qt3DAnimation::QMorphTarget *target = new Qt3DAnimation::QMorphTarget(geometryRenderer);
891 targets.push_back(target);
892 QVector<QAttribute *> attributes;
893 QByteArray targetBufferArray;
894 targetBufferArray.resize(clumpSize * mesh->mNumVertices * sizeof(float));
895 float *dst = reinterpret_cast<float *>(targetBufferArray.data());
896
897 for (uint j = 0; j < mesh->mNumVertices; j++) {
898 if (animesh->mVertices) {
899 *dst++ = animesh->mVertices[j].x;
900 *dst++ = animesh->mVertices[j].y;
901 *dst++ = animesh->mVertices[j].z;
902 }
903 if (animesh->mNormals) {
904 *dst++ = animesh->mNormals[j].x;
905 *dst++ = animesh->mNormals[j].y;
906 *dst++ = animesh->mNormals[j].z;
907 }
908 if (animesh->mTangents) {
909 *dst++ = animesh->mTangents[j].x;
910 *dst++ = animesh->mTangents[j].y;
911 *dst++ = animesh->mTangents[j].z;
912 }
913 if (animesh->mTextureCoords[0]) {
914 *dst++ = animesh->mTextureCoords[0][j].x;
915 *dst++ = animesh->mTextureCoords[0][j].y;
916 }
917 if (animesh->mColors[0]) {
918 *dst++ = animesh->mColors[0][j].r;
919 *dst++ = animesh->mColors[0][j].g;
920 *dst++ = animesh->mColors[0][j].b;
921 *dst++ = animesh->mColors[0][j].a;
922 }
923 }
924
925 Qt3DRender::QBuffer *targetBuffer
926 = QAbstractNodeFactory::createNode<Qt3DRender::QBuffer>("QBuffer");
927 targetBuffer->setData(targetBufferArray);
928 targetBuffer->setParent(meshGeometry);
929
930 if (animesh->mVertices) {
931 attributes.push_back(createAttribute(targetBuffer, VERTICES_ATTRIBUTE_NAME,
932 QAttribute::Float, 3,
933 animesh->mNumVertices, voff * sizeof(float),
934 clumpSize * sizeof(float), meshGeometry));
935 }
936 if (animesh->mNormals) {
937 attributes.push_back(createAttribute(targetBuffer, NORMAL_ATTRIBUTE_NAME,
938 QAttribute::Float, 3,
939 animesh->mNumVertices, noff * sizeof(float),
940 clumpSize * sizeof(float), meshGeometry));
941 }
942 if (animesh->mTangents) {
943 attributes.push_back(createAttribute(targetBuffer, TANGENT_ATTRIBUTE_NAME,
944 QAttribute::Float, 3,
945 animesh->mNumVertices, tanoff * sizeof(float),
946 clumpSize * sizeof(float), meshGeometry));
947 }
948 if (animesh->mTextureCoords[0]) {
949 attributes.push_back(createAttribute(targetBuffer, TEXTCOORD_ATTRIBUTE_NAME,
950 QAttribute::Float, 2,
951 animesh->mNumVertices, texoff * sizeof(float),
952 clumpSize * sizeof(float), meshGeometry));
953 }
954 if (animesh->mColors[0]) {
955 attributes.push_back(createAttribute(targetBuffer, COLOR_ATTRIBUTE_NAME,
956 QAttribute::Float, 4,
957 animesh->mNumVertices, coloff * sizeof(float),
958 clumpSize * sizeof(float), meshGeometry));
959 }
960 target->setAttributes(attributes);
961 }
962 morphingAnimation->setMorphTargets(targets);
963 morphingAnimation->setTargetName(aiStringToQString(mesh->mName));
964 morphingAnimation->setTarget(geometryRenderer);
965 }
966
967 qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Mesh " << aiStringToQString(mesh->mName)
968 << " Vertices " << mesh->mNumVertices << " Faces "
969 << mesh->mNumFaces << " Indices " << indices;
970
971 return geometryRenderer;
972}
973
974/*!
975 * Converts the provided Assimp aiTexture at \a textureIndex to a Texture
976 * \sa Texture
977 */
978QAbstractTexture *AssimpImporter::loadEmbeddedTexture(uint textureIndex)
979{
980 aiTexture *assimpTexture = m_scene->m_aiScene->mTextures[textureIndex];
981 QAbstractTexture *texture = QAbstractNodeFactory::createNode<QTexture2D>("QTexture2D");
982 AssimpRawTextureImage *imageData = new AssimpRawTextureImage();
983
984 bool isCompressed = assimpTexture->mHeight == 0;
985 uint textureSize = assimpTexture->mWidth *
986 (isCompressed ? 1 : assimpTexture->mHeight);
987 // Set texture to RGBA8888
988 QByteArray textureContent;
989 textureContent.reserve(textureSize * 4);
990 for (uint i = 0; i < textureSize; i++) {
991 uint idx = i * 4;
992 aiTexel texel = assimpTexture->pcData[i];
993 textureContent[idx] = texel.r;
994 textureContent[idx + 1] = texel.g;
995 textureContent[idx + 2] = texel.b;
996 textureContent[idx + 3] = texel.a;
997 }
998 imageData->setData(textureContent);
999 texture->addTextureImage(imageData);
1000
1001 return texture;
1002}
1003
1004/*!
1005 * Loads the light in the current scene located at \a lightIndex.
1006 */
1007QAbstractLight *AssimpImporter::loadLight(uint lightIndex)
1008{
1009 aiLight *light = m_scene->m_aiScene->mLights[lightIndex];
1010 // TODO: Implement me!
1011 Q_UNUSED(light);
1012 return nullptr;
1013}
1014
1015/*!
1016 * Converts the provided Assimp aiCamera in a node to a camera entity
1017 */
1018Qt3DCore::QEntity *AssimpImporter::loadCamera(aiNode *node)
1019{
1020 aiCamera *assimpCamera = nullptr;
1021
1022 for (uint i = 0; i < m_scene->m_aiScene->mNumCameras; ++i) {
1023 auto camera = m_scene->m_aiScene->mCameras[i];
1024 if (camera->mName == node->mName) {
1025 assimpCamera = camera;
1026 break;
1027 }
1028 }
1029
1030 if (assimpCamera == nullptr)
1031 return nullptr;
1032
1033 QEntity *camera = QAbstractNodeFactory::createNode<Qt3DCore::QEntity>("QEntity");
1034 QCameraLens *lens = QAbstractNodeFactory::createNode<QCameraLens>("QCameraLens");
1035
1036 lens->setObjectName(aiStringToQString(assimpCamera->mName));
1037 lens->setPerspectiveProjection(qRadiansToDegrees(assimpCamera->mHorizontalFOV),
1038 qMax(assimpCamera->mAspect, 1.0f),
1039 assimpCamera->mClipPlaneNear,
1040 assimpCamera->mClipPlaneFar);
1041 camera->addComponent(lens);
1042
1043 QMatrix4x4 m;
1044 m.lookAt(QVector3D(assimpCamera->mPosition.x, assimpCamera->mPosition.y, assimpCamera->mPosition.z),
1045 QVector3D(assimpCamera->mLookAt.x, assimpCamera->mLookAt.y, assimpCamera->mLookAt.z),
1046 QVector3D(assimpCamera->mUp.x, assimpCamera->mUp.y, assimpCamera->mUp.z));
1047 Qt3DCore::QTransform *transform = QAbstractNodeFactory::createNode<Qt3DCore::QTransform>("QTransform");
1048 transform->setMatrix(m);
1049 camera->addComponent(transform);
1050
1051 return camera;
1052}
1053
1054int findTimeIndex(const QVector<float> &times, float time) {
1055 for (int i = 0; i < times.size(); i++) {
1056 if (qFuzzyCompare(times[i], time))
1057 return i;
1058 }
1059 return -1;
1060}
1061
1062void insertAtTime(QVector<float> &positions, QVector<Qt3DCore::QTransform *> &tranforms,
1063 Qt3DCore::QTransform *t, float time)
1064{
1065 if (positions.size() == 0) {
1066 positions.push_back(time);
1067 tranforms.push_back(t);
1068 } else if (time < positions.first()) {
1069 positions.push_front(time);
1070 tranforms.push_front(t);
1071 } else if (time > positions.last()) {
1072 positions.push_back(time);
1073 tranforms.push_back(t);
1074 } else {
1075 qWarning() << "Insert new key in the middle of the keyframe not implemented.";
1076 }
1077}
1078
1079// OPTIONAL
1080void AssimpImporter::loadAnimation(uint animationIndex)
1081{
1082 aiAnimation *assimpAnim = m_scene->m_aiScene->mAnimations[animationIndex];
1083 qCDebug(AssimpImporterLog) << "load Animation: "<< aiStringToQString(assimpAnim->mName);
1084 double tickScale = 1.0;
1085 if (!qFuzzyIsNull(assimpAnim->mTicksPerSecond))
1086 tickScale = 1.0 / assimpAnim->mTicksPerSecond;
1087
1088 /* keyframe animations */
1089 for (uint i = 0; i < assimpAnim->mNumChannels; ++i) {
1090 aiNodeAnim *nodeAnim = assimpAnim->mChannels[i];
1091 aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(nodeAnim->mNodeName);
1092
1093 Qt3DAnimation::QKeyframeAnimation *kfa = new Qt3DAnimation::QKeyframeAnimation();
1094 QVector<float> positions;
1095 QVector<Qt3DCore::QTransform*> transforms;
1096 if ((nodeAnim->mNumPositionKeys > 1)
1097 || !(nodeAnim->mNumPositionKeys == 1 && nodeAnim->mPositionKeys[0].mValue.x == 0
1098 && nodeAnim->mPositionKeys[0].mValue.y == 0
1099 && nodeAnim->mPositionKeys[0].mValue.z == 0)) {
1100 for (uint j = 0; j < nodeAnim->mNumPositionKeys; j++) {
1101 positions.push_back(nodeAnim->mPositionKeys[j].mTime);
1102 Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
1103 t->setTranslation(QVector3D(nodeAnim->mPositionKeys[j].mValue.x,
1104 nodeAnim->mPositionKeys[j].mValue.y,
1105 nodeAnim->mPositionKeys[j].mValue.z));
1106 transforms.push_back(t);
1107 }
1108 }
1109 if ((nodeAnim->mNumRotationKeys > 1) ||
1110 !(nodeAnim->mNumRotationKeys == 1 && nodeAnim->mRotationKeys[0].mValue.x == 0
1111 && nodeAnim->mRotationKeys[0].mValue.y == 0
1112 && nodeAnim->mRotationKeys[0].mValue.z == 0
1113 && nodeAnim->mRotationKeys[0].mValue.w == 1)) {
1114 for (uint j = 0; j < nodeAnim->mNumRotationKeys; j++) {
1115 int index = findTimeIndex(positions, nodeAnim->mRotationKeys[j].mTime);
1116 if (index >= 0) {
1117 Qt3DCore::QTransform *t = transforms[index];
1118 t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w,
1119 nodeAnim->mRotationKeys[j].mValue.x,
1120 nodeAnim->mRotationKeys[j].mValue.y,
1121 nodeAnim->mRotationKeys[j].mValue.z));
1122 } else {
1123 Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
1124 t->setRotation(QQuaternion(nodeAnim->mRotationKeys[j].mValue.w,
1125 nodeAnim->mRotationKeys[j].mValue.x,
1126 nodeAnim->mRotationKeys[j].mValue.y,
1127 nodeAnim->mRotationKeys[j].mValue.z));
1128 insertAtTime(positions, transforms, t, nodeAnim->mRotationKeys[j].mTime);
1129 }
1130 }
1131 }
1132 if ((nodeAnim->mNumScalingKeys > 1)
1133 || !(nodeAnim->mNumScalingKeys == 1 && nodeAnim->mScalingKeys[0].mValue.x == 1
1134 && nodeAnim->mScalingKeys[0].mValue.y == 1
1135 && nodeAnim->mScalingKeys[0].mValue.z == 1)) {
1136 for (uint j = 0; j < nodeAnim->mNumScalingKeys; j++) {
1137 int index = findTimeIndex(positions, nodeAnim->mScalingKeys[j].mTime);
1138 if (index >= 0) {
1139 Qt3DCore::QTransform *t = transforms[index];
1140 t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x,
1141 nodeAnim->mScalingKeys[j].mValue.y,
1142 nodeAnim->mScalingKeys[j].mValue.z));
1143 } else {
1144 Qt3DCore::QTransform *t = new Qt3DCore::QTransform();
1145 t->setScale3D(QVector3D(nodeAnim->mScalingKeys[j].mValue.x,
1146 nodeAnim->mScalingKeys[j].mValue.y,
1147 nodeAnim->mScalingKeys[j].mValue.z));
1148 insertAtTime(positions, transforms, t, nodeAnim->mScalingKeys[j].mTime);
1149 }
1150 }
1151 }
1152 for (int j = 0; j < positions.size(); ++j)
1153 positions[j] = positions[j] * tickScale;
1154 kfa->setFramePositions(positions);
1155 kfa->setKeyframes(transforms);
1156 kfa->setAnimationName(QString(assimpAnim->mName.C_Str()));
1157 kfa->setTargetName(QString(targetNode->mName.C_Str()));
1158 m_scene->m_animations.push_back(kfa);
1159 }
1160 /* mesh morph animations */
1161 for (uint i = 0; i < assimpAnim->mNumMorphMeshChannels; ++i) {
1162 aiMeshMorphAnim *morphAnim = assimpAnim->mMorphMeshChannels[i];
1163 aiNode *targetNode = m_scene->m_aiScene->mRootNode->FindNode(morphAnim->mName);
1164 aiMesh *mesh = m_scene->m_aiScene->mMeshes[targetNode->mMeshes[0]];
1165
1166 Qt3DAnimation::QMorphingAnimation *morphingAnimation = new Qt3DAnimation::QMorphingAnimation;
1167 QVector<float> positions;
1168 positions.resize(morphAnim->mNumKeys);
1169 // set so that weights array is allocated to correct size in morphingAnimation
1170 morphingAnimation->setTargetPositions(positions);
1171 for (unsigned int j = 0; j < morphAnim->mNumKeys; ++j) {
1172 aiMeshMorphKey &key = morphAnim->mKeys[j];
1173 positions[j] = key.mTime * tickScale;
1174
1175 QVector<float> weights;
1176 weights.resize(key.mNumValuesAndWeights);
1177 for (int k = 0; k < weights.size(); k++) {
1178 const unsigned int value = key.mValues[k];
1179 if (value < key.mNumValuesAndWeights)
1180 weights[value] = key.mWeights[k];
1181 }
1182 morphingAnimation->setWeights(j, weights);
1183 }
1184
1185 morphingAnimation->setTargetPositions(positions);
1186 morphingAnimation->setAnimationName(QString(assimpAnim->mName.C_Str()));
1187 morphingAnimation->setTargetName(QString(targetNode->mName.C_Str()));
1188 morphingAnimation->setMethod((mesh->mMethod == aiMorphingMethod_MORPH_NORMALIZED)
1189 ? Qt3DAnimation::QMorphingAnimation::Normalized
1190 : Qt3DAnimation::QMorphingAnimation::Relative);
1191 m_scene->m_morphAnimations.push_back(morphingAnimation);
1192 }
1193}
1194
1195/*!
1196 * Sets the object name of \a material to the name of \a assimpMaterial.
1197 */
1198void AssimpImporter::copyMaterialName(QMaterial *material, aiMaterial *assimpMaterial)
1199{
1200 aiString name;
1201 if (assimpMaterial->Get(AI_MATKEY_NAME, name) == aiReturn_SUCCESS) {
1202 // May not be necessary
1203 // Kept for debug purposes at the moment
1204 material->setObjectName(aiStringToQString(name));
1205 qCDebug(AssimpImporterLog) << Q_FUNC_INFO << "Assimp Material " << material->objectName();
1206 }
1207}
1208
1209/*!
1210 * Fills \a material color properties with \a assimpMaterial color properties.
1211 */
1212void AssimpImporter::copyMaterialColorProperties(QMaterial *material, aiMaterial *assimpMaterial)
1213{
1214 aiColor3D color;
1215 if (assimpMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS)
1216 setParameterValue(ASSIMP_MATERIAL_DIFFUSE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1217 if (assimpMaterial->Get(AI_MATKEY_COLOR_SPECULAR, color) == aiReturn_SUCCESS)
1218 setParameterValue(ASSIMP_MATERIAL_SPECULAR_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1219 if (assimpMaterial->Get(AI_MATKEY_COLOR_AMBIENT, color) == aiReturn_SUCCESS)
1220 setParameterValue(ASSIMP_MATERIAL_AMBIENT_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1221 if (assimpMaterial->Get(AI_MATKEY_COLOR_EMISSIVE, color) == aiReturn_SUCCESS)
1222 setParameterValue(ASSIMP_MATERIAL_EMISSIVE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1223 if (assimpMaterial->Get(AI_MATKEY_COLOR_TRANSPARENT, color) == aiReturn_SUCCESS)
1224 setParameterValue(ASSIMP_MATERIAL_TRANSPARENT_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1225 if (assimpMaterial->Get(AI_MATKEY_COLOR_REFLECTIVE, color) == aiReturn_SUCCESS)
1226 setParameterValue(ASSIMP_MATERIAL_REFLECTIVE_COLOR, material, QColor::fromRgbF(color.r, color.g, color.b));
1227}
1228
1229/*!
1230 * Retrieves a \a material bool property.
1231 */
1232void AssimpImporter::copyMaterialBoolProperties(QMaterial *material, aiMaterial *assimpMaterial)
1233{
1234 int value;
1235 if (assimpMaterial->Get(AI_MATKEY_TWOSIDED, value) == aiReturn_SUCCESS)
1236 setParameterValue(ASSIMP_MATERIAL_IS_TWOSIDED, material, (value == 0) ? false : true);
1237 if (assimpMaterial->Get(AI_MATKEY_ENABLE_WIREFRAME, value) == aiReturn_SUCCESS)
1238 setParameterValue(ASSIMP_MATERIAL_IS_WIREFRAME, material, (value == 0) ? false : true);
1239}
1240
1241void AssimpImporter::copyMaterialShadingModel(QMaterial *material, aiMaterial *assimpMaterial)
1242{
1243 Q_UNUSED(material);
1244 Q_UNUSED(assimpMaterial);
1245 // TODO
1246 // Match each shading function with a default shader
1247
1248 // AssimpIO::assimpMaterialAttributesMap[AI_MATKEY_SHADING_MODEL] = &AssimpIO::getMaterialShadingModel;
1249 // AssimpIO::assimpMaterialAttributesMap[AI_MATKEY_BLEND_FUNC] = &AssimpIO::getMaterialBlendingFunction;
1250}
1251
1252void AssimpImporter::copyMaterialBlendingFunction(QMaterial *material, aiMaterial *assimpMaterial)
1253{
1254 Q_UNUSED(material);
1255 Q_UNUSED(assimpMaterial);
1256 // TO DO
1257}
1258
1259/*!
1260 *
1261 */
1262void AssimpImporter::copyMaterialTextures(QMaterial *material, aiMaterial *assimpMaterial)
1263{
1264 static const aiTextureType textureType[] = {aiTextureType_AMBIENT,
1265 aiTextureType_DIFFUSE,
1266 aiTextureType_DISPLACEMENT,
1267 aiTextureType_EMISSIVE,
1268 aiTextureType_HEIGHT,
1269 aiTextureType_LIGHTMAP,
1270 aiTextureType_NORMALS,
1271 aiTextureType_OPACITY,
1272 aiTextureType_REFLECTION,
1273 aiTextureType_SHININESS,
1274 aiTextureType_SPECULAR};
1275
1276 if (m_scene->m_textureToParameterName.isEmpty()) {
1277 m_scene->m_textureToParameterName.insert(aiTextureType_AMBIENT, ASSIMP_MATERIAL_AMBIENT_TEXTURE);
1278 m_scene->m_textureToParameterName.insert(aiTextureType_DIFFUSE, ASSIMP_MATERIAL_DIFFUSE_TEXTURE);
1279 m_scene->m_textureToParameterName.insert(aiTextureType_DISPLACEMENT, ASSIMP_MATERIAL_DISPLACEMENT_TEXTURE);
1280 m_scene->m_textureToParameterName.insert(aiTextureType_EMISSIVE, ASSIMP_MATERIAL_EMISSIVE_TEXTURE);
1281 m_scene->m_textureToParameterName.insert(aiTextureType_HEIGHT, ASSIMP_MATERIAL_HEIGHT_TEXTURE);
1282 m_scene->m_textureToParameterName.insert(aiTextureType_LIGHTMAP, ASSIMP_MATERIAL_LIGHTMAP_TEXTURE);
1283 m_scene->m_textureToParameterName.insert(aiTextureType_NORMALS, ASSIMP_MATERIAL_NORMALS_TEXTURE);
1284 m_scene->m_textureToParameterName.insert(aiTextureType_OPACITY, ASSIMP_MATERIAL_OPACITY_TEXTURE);
1285 m_scene->m_textureToParameterName.insert(aiTextureType_REFLECTION, ASSIMP_MATERIAL_REFLECTION_TEXTURE);
1286 m_scene->m_textureToParameterName.insert(aiTextureType_SHININESS, ASSIMP_MATERIAL_SHININESS_TEXTURE);
1287 m_scene->m_textureToParameterName.insert(aiTextureType_SPECULAR, ASSIMP_MATERIAL_SPECULAR_TEXTURE);
1288 }
1289
1290 for (unsigned int i = 0; i < sizeof(textureType)/sizeof(textureType[0]); i++) {
1291 aiString path;
1292 if (assimpMaterial->GetTexture(textureType[i], 0, &path) == AI_SUCCESS) {
1293 const QString fullPath = m_sceneDir.absoluteFilePath(texturePath(path));
1294 // Load texture if not already loaded
1295 QAbstractTexture *tex = QAbstractNodeFactory::createNode<QTexture2D>("QTexture2D");
1296 QTextureImage *texImage = QAbstractNodeFactory::createNode<QTextureImage>("QTextureImage");
1297 texImage->setSource(QUrl::fromLocalFile(fullPath));
1298 texImage->setMirrored(false);
1299 tex->addTextureImage(texImage);
1300
1301 // Set proper wrapping mode
1302 QTextureWrapMode wrapMode(QTextureWrapMode::Repeat);
1303 int xMode = 0;
1304 int yMode = 0;
1305
1306 if (assimpMaterial->Get(AI_MATKEY_MAPPINGMODE_U(textureType[i], 0), xMode) == aiReturn_SUCCESS)
1307 wrapMode.setX(wrapModeFromaiTextureMapMode(xMode));
1308 if (assimpMaterial->Get(AI_MATKEY_MAPPINGMODE_V(textureType[i], 0), yMode) == aiReturn_SUCCESS)
1309 wrapMode.setY(wrapModeFromaiTextureMapMode(yMode));
1310
1311 tex->setWrapMode(wrapMode);
1312
1313 qCDebug(AssimpImporterLog) << Q_FUNC_INFO << " Loaded Texture " << fullPath;
1314 setParameterValue(m_scene->m_textureToParameterName[textureType[i]],
1315 material, QVariant::fromValue(tex));
1316 }
1317 }
1318}
1319
1320/*!
1321 * Retrieves a \a material float property.
1322 */
1323void AssimpImporter::copyMaterialFloatProperties(QMaterial *material, aiMaterial *assimpMaterial)
1324{
1325 float value = 0;
1326 if (assimpMaterial->Get(AI_MATKEY_OPACITY, value) == aiReturn_SUCCESS)
1327 setParameterValue(ASSIMP_MATERIAL_OPACITY, material, value);
1328 if (assimpMaterial->Get(AI_MATKEY_SHININESS, value) == aiReturn_SUCCESS)
1329 setParameterValue(ASSIMP_MATERIAL_SHININESS, material, value);
1330 if (assimpMaterial->Get(AI_MATKEY_SHININESS_STRENGTH, value) == aiReturn_SUCCESS)
1331 setParameterValue(ASSIMP_MATERIAL_SHININESS_STRENGTH, material, value);
1332 if (assimpMaterial->Get(AI_MATKEY_REFRACTI, value) == aiReturn_SUCCESS)
1333 setParameterValue(ASSIMP_MATERIAL_REFRACTI, material, value);
1334 if (assimpMaterial->Get(AI_MATKEY_REFLECTIVITY, value) == aiReturn_SUCCESS)
1335 setParameterValue(ASSIMP_MATERIAL_REFLECTIVITY, material, value);
1336}
1337
1338AssimpRawTextureImage::AssimpRawTextureImage(QNode *parent)
1339 : QAbstractTextureImage(parent)
1340{
1341}
1342
1343QTextureImageDataGeneratorPtr AssimpRawTextureImage::dataGenerator() const
1344{
1345 return QTextureImageDataGeneratorPtr(new AssimpRawTextureImageFunctor(m_data));
1346}
1347
1348void AssimpRawTextureImage::setData(const QByteArray &data)
1349{
1350 if (data != m_data) {
1351 m_data = data;
1352 notifyDataGeneratorChanged();
1353 }
1354}
1355
1356AssimpRawTextureImage::AssimpRawTextureImageFunctor::AssimpRawTextureImageFunctor(const QByteArray &data)
1357 : QTextureImageDataGenerator()
1358 , m_data(data)
1359{
1360}
1361
1362QTextureImageDataPtr AssimpRawTextureImage::AssimpRawTextureImageFunctor::operator()()
1363{
1364 QTextureImageDataPtr dataPtr = QTextureImageDataPtr::create();
1365 // Note: we assume 4 components per pixel and not compressed for now
1366 dataPtr->setData(m_data, 4, false);
1367 return dataPtr;
1368}
1369
1370bool AssimpRawTextureImage::AssimpRawTextureImageFunctor::operator ==(const QTextureImageDataGenerator &other) const
1371{
1372 const AssimpRawTextureImageFunctor *otherFunctor = functor_cast<AssimpRawTextureImageFunctor>(&other);
1373 return (otherFunctor != nullptr && otherFunctor->m_data == m_data);
1374}
1375
1376AssimpImporter::SceneImporter::SceneImporter()
1377 : m_importer(new Assimp::Importer())
1378 , m_aiScene(nullptr)
1379{
1380 // The Assimp::Importer manages the lifetime of the aiScene object
1381}
1382
1383
1384
1385AssimpImporter::SceneImporter::~SceneImporter()
1386{
1387 delete m_importer;
1388}
1389
1390} // namespace Qt3DRender
1391
1392QT_END_NAMESPACE
1393
1394#include "assimpimporter.moc"
1395