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

source code of qt3d/src/plugins/sceneparsers/assimp/assimpimporter.cpp