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

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