1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Quick 3D.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 or (at your option) any later version
20 ** approved by the KDE Free Qt Foundation. The licenses are as published by
21 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "assimpimporter.h"
31 
32 #include <assimp/Importer.hpp>
33 #include <assimp/scene.h>
34 #include <assimp/Logger.hpp>
35 #include <assimp/DefaultLogger.hpp>
36 #include <assimp/postprocess.h>
37 #include <assimp/pbrmaterial.h>
38 
39 #include <QtQuick3DAssetImport/private/qssgmeshutilities_p.h>
40 
41 #include <QtGui/QImage>
42 #include <QtGui/QImageReader>
43 #include <QtGui/QImageWriter>
44 #include <QtGui/QQuaternion>
45 
46 #include <QtCore/QBuffer>
47 #include <QtCore/QByteArray>
48 #include <QtCore/QList>
49 #include <QtCore/QJsonDocument>
50 #include <QtCore/QJsonObject>
51 
52 #include <qmath.h>
53 
54 #include <algorithm>
55 #include <limits>
56 
57 QT_BEGIN_NAMESPACE
58 
59 #define demonPostProcessPresets ( \
60     aiProcess_CalcTangentSpace              |  \
61     aiProcess_GenSmoothNormals              |  \
62     aiProcess_JoinIdenticalVertices         |  \
63     aiProcess_ImproveCacheLocality          |  \
64     aiProcess_LimitBoneWeights              |  \
65     aiProcess_RemoveRedundantMaterials      |  \
66     aiProcess_SplitLargeMeshes              |  \
67     aiProcess_Triangulate                   |  \
68     aiProcess_GenUVCoords                   |  \
69     aiProcess_SortByPType                   |  \
70     aiProcess_FindDegenerates               |  \
71     aiProcess_FindInvalidData               |  \
72     0 )
73 
AssimpImporter()74 AssimpImporter::AssimpImporter()
75 {
76     QFile optionFile(":/assimpimporter/options.json");
77     optionFile.open(QIODevice::ReadOnly);
78     QByteArray options = optionFile.readAll();
79     optionFile.close();
80     auto optionsDocument = QJsonDocument::fromJson(options);
81     m_options = optionsDocument.object().toVariantMap();
82     m_postProcessSteps = aiPostProcessSteps(demonPostProcessPresets);
83 
84     m_importer = new Assimp::Importer();
85     // Remove primatives that are not Triangles
86     m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
87 }
88 
~AssimpImporter()89 AssimpImporter::~AssimpImporter()
90 {
91     for (auto *animation : m_animations)
92         delete animation;
93     delete m_importer;
94 }
95 
name() const96 const QString AssimpImporter::name() const
97 {
98     return QStringLiteral("assimp");
99 }
100 
inputExtensions() const101 const QStringList AssimpImporter::inputExtensions() const
102 {
103     QStringList extensions;
104     extensions.append(QStringLiteral("fbx"));
105     extensions.append(QStringLiteral("dae"));
106     extensions.append(QStringLiteral("obj"));
107     extensions.append(QStringLiteral("blend"));
108     extensions.append(QStringLiteral("gltf"));
109     extensions.append(QStringLiteral("glb"));
110     return extensions;
111 }
112 
outputExtension() const113 const QString AssimpImporter::outputExtension() const
114 {
115     return QStringLiteral(".qml");
116 }
117 
type() const118 const QString AssimpImporter::type() const
119 {
120     return QStringLiteral("Scene");
121 }
122 
typeDescription() const123 const QString AssimpImporter::typeDescription() const
124 {
125     return QObject::tr("3D Scene");
126 }
127 
importOptions() const128 const QVariantMap AssimpImporter::importOptions() const
129 {
130     return m_options;
131 }
132 
import(const QString & sourceFile,const QDir & savePath,const QVariantMap & options,QStringList * generatedFiles)133 const QString AssimpImporter::import(const QString &sourceFile, const QDir &savePath, const QVariantMap &options, QStringList *generatedFiles)
134 {
135     Q_UNUSED(options)
136 
137     QString errorString;
138     m_savePath = savePath;
139     m_sourceFile = QFileInfo(sourceFile);
140 
141     // Create savePath if it doesn't exist already
142     m_savePath.mkdir(".");
143 
144     // There is special handling needed for GLTF assets
145     const auto extension = m_sourceFile.suffix().toLower();
146     if (extension == QStringLiteral("gltf") || extension == QStringLiteral("glb")) {
147         // assimp bug #3009
148         // Currently meshOffsets are not cleared for GLTF files
149         // If a GLTF file is imported, we just reset the importer before reading a new gltf file
150         if (m_gltfUsed) { // it means that one of previous imported files is gltf format
151             for (auto *animation : m_animations)
152                 delete animation;
153             m_animations.clear();
154             m_cameras.clear();
155             m_lights.clear();
156             m_uniqueIds.clear();
157             m_nodeIdMap.clear();
158             m_nodeTypeMap.clear();
159             delete m_importer;
160             m_scene = nullptr;
161             m_importer = new Assimp::Importer();
162             // Remove primatives that are not Triangles
163             m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
164             m_gltfUsed = false;
165         } else {
166             m_gltfUsed = true;
167         }
168         m_gltfMode = true;
169     }
170     else {
171         m_gltfMode = false;
172     }
173 
174     processOptions(options);
175 
176     m_scene = m_importer->ReadFile(sourceFile.toStdString(), m_postProcessSteps);
177     if (!m_scene) {
178         // Scene failed to load, use logger to get the reason
179         return QString::fromLocal8Bit(m_importer->GetErrorString());
180     }
181 
182     // Generate Embedded Texture Sources
183     if (m_scene->mNumTextures)
184         m_savePath.mkdir(QStringLiteral("./maps"));
185     for (uint i = 0; i < m_scene->mNumTextures; ++i) {
186         aiTexture *texture = m_scene->mTextures[i];
187         if (texture->mHeight == 0) {
188             // compressed format, try to load with Image Loader
189             QByteArray data(reinterpret_cast<char *>(texture->pcData), texture->mWidth);
190             QBuffer readBuffer(&data);
191             QByteArray format = texture->achFormatHint;
192             QImageReader imageReader(&readBuffer, format);
193             QImage image = imageReader.read();
194             if (image.isNull()) {
195                 qWarning() << imageReader.errorString();
196                 continue;
197             }
198 
199             // ### maybe dont always use png
200             const QString saveFileName = savePath.absolutePath() +
201                     QStringLiteral("/maps/") +
202                     QString::number(i) +
203                     QStringLiteral(".png");
204             image.save(saveFileName);
205 
206         } else {
207             // Raw format, just convert data to QImage
208             QImage rawImage(reinterpret_cast<uchar *>(texture->pcData), texture->mWidth, texture->mHeight, QImage::Format_RGBA8888);
209             const QString saveFileName = savePath.absolutePath() +
210                     QStringLiteral("/maps/") +
211                     QString::number(i) +
212                     QStringLiteral(".png");
213             rawImage.save(saveFileName);
214         }
215     }
216 
217     // Check for Cameras
218     if (m_scene->HasCameras()) {
219         for (uint i = 0; i < m_scene->mNumCameras; ++i) {
220             aiCamera *camera = m_scene->mCameras[i];
221             aiNode *node = m_scene->mRootNode->FindNode(camera->mName);
222             if (camera && node)
223                 m_cameras.insert(node, camera);
224         }
225     }
226 
227     // Check for Lights
228     if (m_scene->HasLights()) {
229         for (uint i = 0; i < m_scene->mNumLights; ++i) {
230             aiLight *light = m_scene->mLights[i];
231             aiNode *node = m_scene->mRootNode->FindNode(light->mName);
232             if (light && node)
233                 m_lights.insert(node, light);
234         }
235     }
236 
237     // Materials
238 
239     // Traverse Node Tree
240 
241     // Animations (timeline based)
242     if (m_scene->HasAnimations()) {
243         for (uint i = 0; i < m_scene->mNumAnimations; ++i) {
244             aiAnimation *animation = m_scene->mAnimations[i];
245             if (!animation)
246                 continue;
247             m_animations.push_back(new QHash<aiNode *, aiNodeAnim *>());
248             for (uint j = 0; j < animation->mNumChannels; ++j) {
249                 aiNodeAnim *channel = animation->mChannels[j];
250                 aiNode *node = m_scene->mRootNode->FindNode(channel->mNodeName);
251                 if (channel && node)
252                     m_animations.back()->insert(node, channel);
253             }
254         }
255     }
256 
257     // Create QML Component
258     QFileInfo sourceFileInfo(sourceFile);
259 
260 
261     QString targetFileName = savePath.absolutePath() + QDir::separator() +
262             QSSGQmlUtilities::qmlComponentName(sourceFileInfo.completeBaseName()) +
263             QStringLiteral(".qml");
264     QFile targetFile(targetFileName);
265     if (!targetFile.open(QIODevice::WriteOnly)) {
266         errorString += QString("Could not write to file: ") + targetFileName;
267     } else {
268         QTextStream output(&targetFile);
269 
270         // Imports header
271         writeHeader(output);
272 
273         // Component Code
274         processNode(m_scene->mRootNode, output);
275 
276         targetFile.close();
277         if (generatedFiles)
278             *generatedFiles += targetFileName;
279     }
280 
281     return errorString;
282 }
283 
writeHeader(QTextStream & output)284 void AssimpImporter::writeHeader(QTextStream &output)
285 {
286     output << "import QtQuick 2.15\n";
287     output << "import QtQuick3D 1.15\n";
288     if (m_scene->HasAnimations())
289         output << "import QtQuick.Timeline 1.0\n";
290 }
291 
processNode(aiNode * node,QTextStream & output,int tabLevel)292 void AssimpImporter::processNode(aiNode *node, QTextStream &output, int tabLevel)
293 {
294     aiNode *currentNode = node;
295     if (currentNode) {
296         output << QStringLiteral("\n");
297         // Figure out what kind of node this is
298         if (isModel(currentNode)) {
299             // Model
300             output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("Model {\n");
301             generateModelProperties(currentNode, output, tabLevel + 1);
302             m_nodeTypeMap.insert(node, QSSGQmlUtilities::PropertyMap::Model);
303         } else if (isLight(currentNode)) {
304             // Light
305             // Light property name will be produced in the function,
306             // and then tabLevel will be increased.
307             auto type = generateLightProperties(currentNode, output, tabLevel);
308             m_nodeTypeMap.insert(node, type);
309         } else if (isCamera(currentNode)) {
310             // Camera (always assumed to be perspective for some reason)
311             output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("PerspectiveCamera {\n");
312             generateCameraProperties(currentNode, output, tabLevel + 1);
313             m_nodeTypeMap.insert(node, QSSGQmlUtilities::PropertyMap::Camera);
314         } else {
315             // Transform Node
316 
317             // ### Make empty transform node removal optional
318             // Check if the node actually does something before generating it
319             // and return early without processing the rest of the branch
320             if (!containsNodesOfConsequence(node))
321                 return;
322 
323             output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("Node {\n");
324             generateNodeProperties(currentNode, output, tabLevel + 1);
325             m_nodeTypeMap.insert(node, QSSGQmlUtilities::PropertyMap::Node);
326         }
327 
328         // Process All Children Nodes
329         for (uint i = 0; i < currentNode->mNumChildren; ++i)
330             processNode(currentNode->mChildren[i], output, tabLevel + 1);
331 
332         if (tabLevel == 0)
333             processAnimations(output);
334 
335         // Write the QML Footer
336         output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("}\n");
337     }
338 }
339 
generateModelProperties(aiNode * modelNode,QTextStream & output,int tabLevel)340 void AssimpImporter::generateModelProperties(aiNode *modelNode, QTextStream &output, int tabLevel)
341 {
342     generateNodeProperties(modelNode, output, tabLevel);
343 
344     // source
345     // Combine all the meshes referenced by this model into a single MultiMesh file
346     QVector<aiMesh *> meshes;
347     QVector<aiMaterial *> materials;
348     for (uint i = 0; i < modelNode->mNumMeshes; ++i) {
349         aiMesh *mesh = m_scene->mMeshes[modelNode->mMeshes[i]];
350         aiMaterial *material = m_scene->mMaterials[mesh->mMaterialIndex];
351         meshes.append(mesh);
352         materials.append(material);
353     }
354 
355     // Model name can contain invalid characters for filename, so just to be safe, convert the name
356     // into qml id first.
357     QString modelName = QString::fromUtf8(modelNode->mName.C_Str());
358     modelName = QSSGQmlUtilities::sanitizeQmlId(modelName);
359 
360     QString outputMeshFile = QStringLiteral("meshes/") + modelName + QStringLiteral(".mesh");
361 
362     m_savePath.mkdir(QStringLiteral("./meshes"));
363     QString meshFilePath = m_savePath.absolutePath() + QLatin1Char('/') + outputMeshFile;
364     int index = 0;
365     while (m_generatedFiles.contains(meshFilePath)) {
366         outputMeshFile = QStringLiteral("meshes/%1_%2.mesh").arg(modelName).arg(++index);
367         meshFilePath = m_savePath.absolutePath() + QLatin1Char('/') + outputMeshFile;
368     }
369     QFile meshFile(meshFilePath);
370     if (generateMeshFile(meshFile, meshes).isEmpty())
371         m_generatedFiles << meshFilePath;
372 
373     output << QSSGQmlUtilities::insertTabs(tabLevel) << "source: \"" << outputMeshFile
374            << QStringLiteral("\"") << QStringLiteral("\n");
375 
376     // skeletonRoot
377 
378     // materials
379     // If there are any new materials, add them as children of the Model first
380     for (int i = 0; i < materials.count(); ++i) {
381         if (!m_materialIdMap.contains(materials[i])) {
382             generateMaterial(materials[i], output, tabLevel);
383             output << QStringLiteral("\n");
384         }
385     }
386 
387     // For each sub-mesh, generate a material reference for this list
388     output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("materials: [\n");
389     for (int i = 0; i < materials.count(); ++i) {
390         output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << m_materialIdMap[materials[i]];
391         if (i < materials.count() - 1)
392             output << QStringLiteral(",");
393         output << QStringLiteral("\n");
394     }
395 
396     output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("]\n");
397 }
398 
generateLightProperties(aiNode * lightNode,QTextStream & output,int tabLevel)399 QSSGQmlUtilities::PropertyMap::Type AssimpImporter::generateLightProperties(aiNode *lightNode, QTextStream &output, int tabLevel)
400 {
401     aiLight *light = m_lights.value(lightNode);
402     // We assume that the direction vector for a light is (0, 0, -1)
403     // so if the direction vector is non-null, but not (0, 0, -1) we
404     // need to correct the translation
405     aiMatrix4x4 correctionMatrix;
406     if (light->mDirection != aiVector3D(0, 0, 0)) {
407         if (light->mDirection != aiVector3D(0, 0, -1)) {
408             aiMatrix4x4::FromToMatrix(light->mDirection, aiVector3D(0, 0, -1), correctionMatrix);
409         }
410     }
411 
412 
413     // lightType
414     QSSGQmlUtilities::PropertyMap::Type lightType;
415     if (light->mType == aiLightSource_DIRECTIONAL || light->mType == aiLightSource_AMBIENT ) {
416         lightType = QSSGQmlUtilities::PropertyMap::DirectionalLight;
417         output << QSSGQmlUtilities::insertTabs(tabLevel++) << QStringLiteral("DirectionalLight {\n");
418     } else if (light->mType == aiLightSource_POINT) {
419         lightType = QSSGQmlUtilities::PropertyMap::PointLight;
420         output << QSSGQmlUtilities::insertTabs(tabLevel++) << QStringLiteral("PointLight {\n");
421     } else if (light->mType == aiLightSource_AREA) {
422         lightType = QSSGQmlUtilities::PropertyMap::AreaLight;
423         output << QSSGQmlUtilities::insertTabs(tabLevel++) << QStringLiteral("AreaLight {\n");
424     } else if (light->mType == aiLightSource_SPOT) {
425         lightType = QSSGQmlUtilities::PropertyMap::SpotLight;
426         output << QSSGQmlUtilities::insertTabs(tabLevel++) << QStringLiteral("SpotLight {\n");
427     } else {
428         // We dont know what it is, assume its a point light
429         lightType = QSSGQmlUtilities::PropertyMap::PointLight;
430         output << QSSGQmlUtilities::insertTabs(tabLevel++) << QStringLiteral("PointLight {\n");
431     }
432 
433     generateNodeProperties(lightNode, output, tabLevel, correctionMatrix, true);
434 
435     // diffuseColor
436     QColor diffuseColor = QColor::fromRgbF(light->mColorDiffuse.r, light->mColorDiffuse.g, light->mColorDiffuse.b);
437     QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("color"), diffuseColor);
438 
439     // ambientColor
440     if (light->mType == aiLightSource_AMBIENT) {
441         // We only want ambient light color if it is explicit
442         QColor ambientColor = QColor::fromRgbF(light->mColorAmbient.r, light->mColorAmbient.g, light->mColorAmbient.b);
443         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("ambientColor"), ambientColor);
444     }
445     // brightness
446     // Its default value is 100 and the normalized value 1 will be used.
447 
448     if (light->mType == aiLightSource_POINT || light->mType == aiLightSource_SPOT) {
449         // constantFade
450         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("constantFade"), light->mAttenuationConstant);
451 
452         // linearFade
453         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("linearFade"), light->mAttenuationLinear);
454 
455         // exponentialFade
456         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("quadraticFade"), light->mAttenuationQuadratic);
457 
458         if (light->mType == aiLightSource_SPOT) {
459             // coneAngle
460             QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("coneAngle"), qRadiansToDegrees(light->mAngleOuterCone));
461 
462             // innerConeAngle
463             QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("innerConeAngle"), qRadiansToDegrees(light->mAngleInnerCone));
464         }
465     }
466 
467     if (light->mType == aiLightSource_AREA) {
468         // areaWidth
469         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("width"), light->mSize.x);
470 
471         // areaHeight
472         QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, lightType, QStringLiteral("height"), light->mSize.y);
473     }
474 
475     // castShadow
476 
477     // shadowBias
478 
479     // shadowFactor
480 
481     // shadowMapResolution
482 
483     // shadowMapFar
484 
485     // shadowMapFieldOfView
486 
487     // shadowFilter
488 
489     return lightType;
490 }
491 
generateCameraProperties(aiNode * cameraNode,QTextStream & output,int tabLevel)492 void AssimpImporter::generateCameraProperties(aiNode *cameraNode, QTextStream &output, int tabLevel)
493 {
494     aiCamera *camera = m_cameras.value(cameraNode);
495 
496     // We assume these default forward and up vectors, so if this isn't
497     // the case we have to do additional transform
498     aiMatrix4x4 correctionMatrix;
499     if (camera->mLookAt != aiVector3D(0, 0, -1))
500     {
501         aiMatrix4x4 lookAtCorrection;
502         aiMatrix4x4::FromToMatrix(camera->mLookAt, aiVector3D(0, 0, -1), lookAtCorrection);
503         correctionMatrix *= lookAtCorrection;
504     }
505 
506     if (camera->mUp != aiVector3D(0, 1, 0)) {
507         aiMatrix4x4 upCorrection;
508         aiMatrix4x4::FromToMatrix(camera->mUp, aiVector3D(0, 1, 0), upCorrection);
509         correctionMatrix *= upCorrection;
510     }
511 
512     generateNodeProperties(cameraNode, output, tabLevel, correctionMatrix, true);
513 
514     // clipNear
515     QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, QSSGQmlUtilities::PropertyMap::Camera, QStringLiteral("clipNear"), camera->mClipPlaneNear);
516 
517     // clipFar
518     QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, QSSGQmlUtilities::PropertyMap::Camera, QStringLiteral("clipFar"), camera->mClipPlaneFar);
519 
520     // fieldOfView
521     float fov = qRadiansToDegrees(camera->mHorizontalFOV);
522     QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, QSSGQmlUtilities::PropertyMap::Camera, QStringLiteral("fieldOfView"), fov);
523 
524     // isFieldOfViewHorizontal
525     QSSGQmlUtilities::writeQmlPropertyHelper(output,tabLevel, QSSGQmlUtilities::PropertyMap::Camera, QStringLiteral("fieldOfViewOrientation"), "Camera.Horizontal");
526 
527     // projectionMode
528 
529     // scaleMode
530 
531     // scaleAnchor
532 
533     // frustomScaleX
534 
535     // frustomScaleY
536 
537 }
538 
generateNodeProperties(aiNode * node,QTextStream & output,int tabLevel,const aiMatrix4x4 & transformCorrection,bool skipScaling)539 void AssimpImporter::generateNodeProperties(aiNode *node, QTextStream &output, int tabLevel, const aiMatrix4x4 &transformCorrection, bool skipScaling)
540 {
541     // id
542     QString name = QString::fromUtf8(node->mName.C_Str());
543     if (!name.isEmpty()) {
544         // ### we may need to account of non-unique and empty names
545         QString id = generateUniqueId(QSSGQmlUtilities::sanitizeQmlId(name));
546         m_nodeIdMap.insert(node, id);
547         output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("id: ") << id << QStringLiteral("\n");
548     }
549 
550     // Apply correction if necessary
551     aiMatrix4x4 transformMatrix = node->mTransformation;
552     if (!transformCorrection.IsIdentity())
553         transformMatrix *= transformCorrection;
554 
555     // Decompose Transform Matrix to get properties
556     aiVector3D scaling;
557     aiVector3D rotation;
558     aiVector3D translation;
559     transformMatrix.Decompose(scaling, rotation, translation);
560 
561     // translate
562     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("x"), translation.x);
563     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("y"), translation.y);
564     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("z"), translation.z);
565 
566     // rotation
567     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("eulerRotation.x"), qRadiansToDegrees(rotation.x));
568     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("eulerRotation.y"), qRadiansToDegrees(rotation.y));
569     QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("eulerRotation.z"), qRadiansToDegrees(rotation.z));
570 
571     // scale
572     if (!skipScaling) {
573         // Apply the global scale for a root node
574         if (tabLevel == 1)
575             scaling *= m_globalScaleValue;
576 
577         QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("scale.x"), scaling.x);
578         QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("scale.y"), scaling.y);
579         QSSGQmlUtilities::writeQmlPropertyHelper(output, tabLevel, QSSGQmlUtilities::PropertyMap::Node, QStringLiteral("scale.z"), scaling.z);
580     }
581     // pivot
582 
583     // opacity
584 
585     // boneid
586 
587     // visible
588 
589 }
590 
generateMeshFile(QIODevice & file,const QVector<aiMesh * > & meshes)591 QString AssimpImporter::generateMeshFile(QIODevice &file, const QVector<aiMesh *> &meshes)
592 {
593     if (!file.open(QIODevice::WriteOnly))
594         return QStringLiteral("Could not open device to write mesh file");
595 
596 
597     auto meshBuilder = QSSGMeshUtilities::QSSGMeshBuilder::createMeshBuilder();
598 
599     struct SubsetEntryData {
600         QString name;
601         int indexLength;
602         int indexOffset;
603     };
604 
605     // Check if we need placeholders in certain channels
606     bool needsPositionData = false;
607     bool needsNormalData = false;
608     bool needsUV1Data = false;
609     bool needsUV2Data = false;
610     bool needsTangentData = false;
611     bool needsVertexColorData = false;
612     unsigned uv1Components = 0;
613     unsigned uv2Components = 0;
614     unsigned totalVertices = 0;
615     for (const auto *mesh : meshes) {
616         totalVertices += mesh->mNumVertices;
617         uv1Components = qMax(mesh->mNumUVComponents[0], uv1Components);
618         uv2Components = qMax(mesh->mNumUVComponents[1], uv2Components);
619         needsPositionData |= mesh->HasPositions();
620         needsNormalData |= mesh->HasNormals();
621         needsUV1Data |= mesh->HasTextureCoords(0);
622         needsUV2Data |= mesh->HasTextureCoords(1);
623         needsTangentData |= mesh->HasTangentsAndBitangents();
624         needsVertexColorData |=mesh->HasVertexColors(0);
625     }
626 
627     QByteArray positionData;
628     QByteArray normalData;
629     QByteArray uv1Data;
630     QByteArray uv2Data;
631     QByteArray tangentData;
632     QByteArray binormalData;
633     QByteArray vertexColorData;
634     QByteArray indexBufferData;
635     QVector<SubsetEntryData> subsetData;
636     quint32 baseIndex = 0;
637     QSSGRenderComponentType indexType = QSSGRenderComponentType::UnsignedInteger32;
638     if ((totalVertices / 3) > std::numeric_limits<quint16>::max())
639         indexType = QSSGRenderComponentType::UnsignedInteger32;
640 
641     for (const auto *mesh : meshes) {
642         // Position
643         if (mesh->HasPositions())
644             positionData += QByteArray(reinterpret_cast<char*>(mesh->mVertices), mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32));
645         else if (needsPositionData)
646             positionData += QByteArray(mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
647 
648         // Normal
649         if (mesh->HasNormals())
650             normalData += QByteArray(reinterpret_cast<char*>(mesh->mNormals), mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32));
651         else if (needsNormalData)
652             normalData += QByteArray(mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
653 
654         // UV1
655         if (mesh->HasTextureCoords(0)) {
656             QVector<float> uvCoords;
657             uvCoords.resize(uv1Components * mesh->mNumVertices);
658             for (uint i = 0; i < mesh->mNumVertices; ++i) {
659                 int offset = i * uv1Components;
660                 aiVector3D *textureCoords = mesh->mTextureCoords[0];
661                 uvCoords[offset] = textureCoords[i].x;
662                 uvCoords[offset + 1] = textureCoords[i].y;
663                 if (uv1Components == 3)
664                     uvCoords[offset + 2] = textureCoords[i].z;
665             }
666             uv1Data += QByteArray(reinterpret_cast<const char*>(uvCoords.constData()), uvCoords.size() * sizeof(float));
667         } else {
668             uv1Data += QByteArray(mesh->mNumVertices * uv1Components * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
669         }
670 
671         // UV2
672         if (mesh->HasTextureCoords(1)) {
673             QVector<float> uvCoords;
674             uvCoords.resize(uv2Components * mesh->mNumVertices);
675             for (uint i = 0; i < mesh->mNumVertices; ++i) {
676                 int offset = i * uv2Components;
677                 aiVector3D *textureCoords = mesh->mTextureCoords[1];
678                 uvCoords[offset] = textureCoords[i].x;
679                 uvCoords[offset + 1] = textureCoords[i].y;
680                 if (uv2Components == 3)
681                     uvCoords[offset + 2] = textureCoords[i].z;
682             }
683             uv2Data += QByteArray(reinterpret_cast<const char*>(uvCoords.constData()), uvCoords.size() * sizeof(float));
684         } else {
685             uv2Data += QByteArray(mesh->mNumVertices * uv2Components * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
686         }
687 
688         if (mesh->HasTangentsAndBitangents()) {
689             // Tangents
690             tangentData += QByteArray(reinterpret_cast<char*>(mesh->mTangents), mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32));
691             // Binormals (They are actually supposed to be Bitangents despite what they are called)
692             binormalData += QByteArray(reinterpret_cast<char*>(mesh->mBitangents), mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32));
693         } else if (needsTangentData) {
694             tangentData += QByteArray(mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
695             binormalData += QByteArray(mesh->mNumVertices * 3 * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
696         }
697         // ### Bones + Weights
698 
699         // Color
700         if (mesh->HasVertexColors(0))
701             vertexColorData += QByteArray(reinterpret_cast<char*>(mesh->mColors[0]), mesh->mNumVertices * 4 * getSizeOfType(QSSGRenderComponentType::Float32));
702         else if (needsVertexColorData)
703             vertexColorData += QByteArray(mesh->mNumVertices * 4 * getSizeOfType(QSSGRenderComponentType::Float32), '\0');
704         // Index Buffer
705         QVector<quint32> indexes;
706         indexes.reserve(mesh->mNumFaces * 3);
707 
708         for (unsigned int faceIndex = 0;faceIndex < mesh->mNumFaces; ++faceIndex) {
709             const auto face = mesh->mFaces[faceIndex];
710             // Faces should always have 3 indicides
711             Q_ASSERT(face.mNumIndices == 3);
712             indexes.append(quint32(face.mIndices[0]) + baseIndex);
713             indexes.append(quint32(face.mIndices[1]) + baseIndex);
714             indexes.append(quint32(face.mIndices[2]) + baseIndex);
715         }
716         // Since we might be combining multiple meshes together, we also need to change the index offset
717         baseIndex = *std::max_element(indexes.constBegin(), indexes.constEnd()) + 1;
718 
719         SubsetEntryData subsetEntry;
720         subsetEntry.indexOffset = indexBufferData.length() / getSizeOfType(indexType);
721         subsetEntry.indexLength = indexes.length();
722         if (indexType == QSSGRenderComponentType::UnsignedInteger32) {
723             indexBufferData += QByteArray(reinterpret_cast<const char *>(indexes.constData()), indexes.length() * getSizeOfType(indexType));
724         } else {
725             // convert data to quint16
726             QVector<quint16> shortIndexes;
727             shortIndexes.resize(indexes.length());
728             for (int i = 0; i < shortIndexes.length(); ++i)
729                 shortIndexes[i] = quint16(indexes[i]);
730             indexBufferData += QByteArray(reinterpret_cast<const char *>(shortIndexes.constData()), shortIndexes.length() * getSizeOfType(indexType));
731         }
732 
733         // Subset
734         subsetEntry.name = QString::fromUtf8(m_scene->mMaterials[mesh->mMaterialIndex]->GetName().C_Str());
735         subsetData.append(subsetEntry);
736     }
737 
738     // Vertex Buffer Entries
739     QVector<QSSGMeshUtilities::MeshBuilderVBufEntry> entries;
740     if (positionData.length() > 0) {
741         QSSGMeshUtilities::MeshBuilderVBufEntry positionAttribute( QSSGMeshUtilities::Mesh::getPositionAttrName(),
742                                                                      positionData,
743                                                                      QSSGRenderComponentType::Float32,
744                                                                      3);
745         entries.append(positionAttribute);
746     }
747     if (normalData.length() > 0) {
748         QSSGMeshUtilities::MeshBuilderVBufEntry normalAttribute( QSSGMeshUtilities::Mesh::getNormalAttrName(),
749                                                                    normalData,
750                                                                    QSSGRenderComponentType::Float32,
751                                                                    3);
752         entries.append(normalAttribute);
753     }
754     if (uv1Data.length() > 0) {
755         QSSGMeshUtilities::MeshBuilderVBufEntry uv1Attribute( QSSGMeshUtilities::Mesh::getUVAttrName(),
756                                                                 uv1Data,
757                                                                 QSSGRenderComponentType::Float32,
758                                                                 uv1Components);
759         entries.append(uv1Attribute);
760     }
761     if (uv2Data.length() > 0) {
762         QSSGMeshUtilities::MeshBuilderVBufEntry uv2Attribute( QSSGMeshUtilities::Mesh::getUV2AttrName(),
763                                                                 uv2Data,
764                                                                 QSSGRenderComponentType::Float32,
765                                                                 uv2Components);
766         entries.append(uv2Attribute);
767     }
768 
769     if (tangentData.length() > 0) {
770         QSSGMeshUtilities::MeshBuilderVBufEntry tangentsAttribute( QSSGMeshUtilities::Mesh::getTexTanAttrName(),
771                                                                      tangentData,
772                                                                      QSSGRenderComponentType::Float32,
773                                                                      3);
774         entries.append(tangentsAttribute);
775     }
776 
777     if (binormalData.length() > 0) {
778         QSSGMeshUtilities::MeshBuilderVBufEntry binormalAttribute( QSSGMeshUtilities::Mesh::getTexBinormalAttrName(),
779                                                                      binormalData,
780                                                                      QSSGRenderComponentType::Float32,
781                                                                      3);
782         entries.append(binormalAttribute);
783     }
784 
785     if (vertexColorData.length() > 0) {
786         QSSGMeshUtilities::MeshBuilderVBufEntry vertexColorAttribute( QSSGMeshUtilities::Mesh::getColorAttrName(),
787                                                                         vertexColorData,
788                                                                         QSSGRenderComponentType::Float32,
789                                                                         4);
790         entries.append(vertexColorAttribute);
791     }
792 
793     meshBuilder->setVertexBuffer(entries);
794     meshBuilder->setIndexBuffer(indexBufferData, indexType);
795 
796     // Subsets
797     for (const auto &subset : subsetData)
798         meshBuilder->addMeshSubset(reinterpret_cast<const char16_t *>(subset.name.utf16()),
799                                    subset.indexLength,
800                                    subset.indexOffset,
801                                    0);
802 
803 
804 
805     auto &outputMesh = meshBuilder->getMesh();
806     outputMesh.saveMulti(file, 0);
807 
808     file.close();
809     return QString();
810 }
811 
812 namespace {
813 
aiColorToQColor(const aiColor3D & color)814 QColor aiColorToQColor(const aiColor3D &color)
815 {
816     return QColor::fromRgbF(double(color.r), double(color.g), double(color.b));
817 }
818 
aiColorToQColor(const aiColor4D & color)819 QColor aiColorToQColor(const aiColor4D &color)
820 {
821     QColor qtColor;
822     qtColor.setRedF(double(color.r));
823     qtColor.setGreenF(double(color.g));
824     qtColor.setBlueF(double(color.b));
825     qtColor.setAlphaF(double(color.a));
826     return qtColor;
827 }
828 
829 }
830 
generateMaterial(aiMaterial * material,QTextStream & output,int tabLevel)831 void AssimpImporter::generateMaterial(aiMaterial *material, QTextStream &output, int tabLevel)
832 {
833     output << QStringLiteral("\n");
834     if (!m_gltfMode)
835         output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("DefaultMaterial {\n");
836     else
837         output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("PrincipledMaterial {\n");
838 
839     // id
840     QString id = generateUniqueId(QSSGQmlUtilities::sanitizeQmlId(material->GetName().C_Str() + QStringLiteral("_material")));
841     output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("id: ") << id << QStringLiteral("\n");
842     m_materialIdMap.insert(material, id);
843 
844     aiReturn result;
845 
846     if (!m_gltfMode) {
847 
848         int shadingModel = 0;
849         result = material->Get(AI_MATKEY_SHADING_MODEL, shadingModel);
850         // lighting
851         if (result == aiReturn_SUCCESS) {
852             if (shadingModel == aiShadingMode_NoShading)
853                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("lighting: DefaultMaterial.NoLighting\n");
854         }
855 
856 
857         QString diffuseMapImage = generateImage(material, aiTextureType_DIFFUSE, 0, tabLevel + 1);
858         if (!diffuseMapImage.isNull())
859             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("diffuseMap: ") << diffuseMapImage << QStringLiteral("\n");
860 
861         // For some reason the normal behavior is that either you have a diffuseMap[s] or a diffuse color
862         // but no a mix of both... So only set the diffuse color if none of the diffuse maps are set:
863         if (diffuseMapImage.isNull()) {
864             aiColor3D diffuseColor;
865             result = material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
866             if (result == aiReturn_SUCCESS) {
867                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
868                                                          tabLevel + 1,
869                                                          QSSGQmlUtilities::PropertyMap::DefaultMaterial,
870                                                          QStringLiteral("diffuseColor"),
871                                                          aiColorToQColor(diffuseColor));
872             }
873         }
874 
875         QString emissiveMapImage = generateImage(material, aiTextureType_EMISSIVE, 0, tabLevel + 1);
876         if (!emissiveMapImage.isNull())
877             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("emissiveMap: ") << emissiveMapImage << QStringLiteral("\n");
878 
879         // emissiveColor AI_MATKEY_COLOR_EMISSIVE
880         aiColor3D emissiveColor;
881         result = material->Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColor);
882         if (result == aiReturn_SUCCESS) {
883             // ### set emissive color
884         }
885         // specularReflectionMap
886 
887         QString specularMapImage = generateImage(material, aiTextureType_SPECULAR, 0, tabLevel + 1);
888         if (!specularMapImage.isNull())
889             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("specularMap: ") << specularMapImage << QStringLiteral("\n");
890 
891         // specularModel AI_MATKEY_SHADING_MODEL
892 
893         // specularTint AI_MATKEY_COLOR_SPECULAR
894         aiColor3D specularTint;
895         result = material->Get(AI_MATKEY_COLOR_SPECULAR, specularTint);
896         if (result == aiReturn_SUCCESS) {
897             // ### set specular color
898         }
899 
900         // indexOfRefraction AI_MATKEY_REFRACTI
901 
902         // fresnelPower
903 
904         // specularAmount
905 
906         // specularRoughness
907 
908         // roughnessMap
909 
910         // opacity AI_MATKEY_OPACITY
911         ai_real opacity;
912         result = material->Get(AI_MATKEY_OPACITY, opacity);
913         if (result == aiReturn_SUCCESS) {
914             QSSGQmlUtilities::writeQmlPropertyHelper(output,
915                                                      tabLevel + 1,
916                                                      QSSGQmlUtilities::PropertyMap::DefaultMaterial,
917                                                      QStringLiteral("opacity"),
918                                                      opacity);
919         }
920 
921         // opacityMap aiTextureType_OPACITY 0
922         QString opacityMapImage = generateImage(material, aiTextureType_OPACITY, 0, tabLevel + 1);
923         if (!opacityMapImage.isNull())
924             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("opacityMap: ") << opacityMapImage;
925 
926         // bumpMap aiTextureType_HEIGHT 0
927         QString bumpMapImage = generateImage(material, aiTextureType_HEIGHT, 0, tabLevel + 1);
928         if (!bumpMapImage.isNull())
929             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("bumpMap: ") << bumpMapImage;
930 
931         // bumpAmount AI_MATKEY_BUMPSCALING
932 
933         // normalMap aiTextureType_NORMALS 0
934         QString normalMapImage = generateImage(material, aiTextureType_NORMALS, 0, tabLevel + 1);
935         if (!normalMapImage.isNull())
936             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("normalMap: ") << normalMapImage;
937 
938         // translucencyMap
939 
940         // translucentFalloff AI_MATKEY_TRANSPARENCYFACTOR
941 
942         // diffuseLightWrap
943 
944         // (enable) vertexColors
945 
946         // displacementMap aiTextureType_DISPLACEMENT 0
947         QString displacementMapImage = generateImage(material, aiTextureType_DISPLACEMENT, 0, tabLevel + 1);
948         if (!displacementMapImage.isNull())
949             output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("displacementMap: ") << displacementMapImage;
950 
951         // displacementAmount
952     } else {
953         // GLTF Mode
954         {
955             aiColor4D baseColorFactor;
956             result = material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, baseColorFactor);
957             if (result == aiReturn_SUCCESS)
958                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
959                                                          tabLevel + 1,
960                                                          QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
961                                                          QStringLiteral("baseColor"),
962                                                          aiColorToQColor(baseColorFactor));
963 
964             QString baseColorImage = generateImage(material, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, tabLevel + 1);
965             if (!baseColorImage.isNull()) {
966                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("baseColorMap: ") << baseColorImage << QStringLiteral("\n");
967                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("opacityChannel: Material.A\n");
968             }
969         }
970 
971         {
972             QString metalicRoughnessImage = generateImage(material, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, tabLevel + 1);
973             if (!metalicRoughnessImage.isNull()) {
974                 // there are two fields now for this, so just use it twice for now
975                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("metalnessMap: ") << metalicRoughnessImage << QStringLiteral("\n");
976                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("metalnessChannel: Material.B\n");
977                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("roughnessMap: ") << metalicRoughnessImage << QStringLiteral("\n");
978                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("roughnessChannel: Material.G\n");
979             }
980 
981             ai_real metallicFactor;
982             result = material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metallicFactor);
983             if (result == aiReturn_SUCCESS) {
984                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
985                                                          tabLevel + 1,
986                                                          QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
987                                                          QStringLiteral("metalness"),
988                                                          metallicFactor);
989             }
990 
991             ai_real roughnessFactor;
992             result = material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughnessFactor);
993             if (result == aiReturn_SUCCESS) {
994                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
995                                                          tabLevel + 1,
996                                                          QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
997                                                          QStringLiteral("roughness"),
998                                                          roughnessFactor);
999             }
1000         }
1001 
1002         {
1003             QString normalTextureImage = generateImage(material, aiTextureType_NORMALS, 0, tabLevel + 1);
1004             if (!normalTextureImage.isNull())
1005                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("normalMap: ") << normalTextureImage << QStringLiteral("\n");
1006         }
1007 
1008         // Occlusion Textures are not implimented (yet)
1009         {
1010             QString occlusionTextureImage = generateImage(material, aiTextureType_LIGHTMAP, 0, tabLevel + 1);
1011             if (!occlusionTextureImage.isNull()) {
1012                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("occlusionMap: ") << occlusionTextureImage << QStringLiteral("\n");
1013                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("occlusionChannel: Material.R\n");
1014             }
1015         }
1016 
1017         {
1018             QString emissiveTextureImage = generateImage(material, aiTextureType_EMISSIVE, 0, tabLevel + 1);
1019             if (!emissiveTextureImage.isNull())
1020                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("emissiveMap: ") << emissiveTextureImage << QStringLiteral("\n");
1021         }
1022 
1023         {
1024             aiColor3D emissiveColorFactor;
1025             result = material->Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColorFactor);
1026             if (result == aiReturn_SUCCESS) {
1027                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
1028                                                          tabLevel + 1,
1029                                                          QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1030                                                          QStringLiteral("emissiveColor"),
1031                                                          aiColorToQColor(emissiveColorFactor));
1032             }
1033         }
1034 
1035         {
1036             bool isDoubleSided;
1037             result = material->Get(AI_MATKEY_TWOSIDED, isDoubleSided);
1038             if (result == aiReturn_SUCCESS && isDoubleSided)
1039                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("cullMode: Material.NoCulling\n");
1040         }
1041 
1042         {
1043             aiString alphaMode;
1044             result = material->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
1045             if (result == aiReturn_SUCCESS) {
1046                 const QString mode = QString::fromUtf8(alphaMode.C_Str()).toLower();
1047                 QString qtMode;
1048                 if (mode == QStringLiteral("opaque"))
1049                     qtMode = QStringLiteral("PrincipledMaterial.Opaque");
1050                 else if (mode == QStringLiteral("mask"))
1051                     qtMode = QStringLiteral("PrincipledMaterial.Mask");
1052                 else if (mode == QStringLiteral("blend"))
1053                     qtMode = QStringLiteral("PrincipledMaterial.Blend");
1054 
1055                 if (!qtMode.isNull())
1056                     QSSGQmlUtilities::writeQmlPropertyHelper(output,
1057                                                              tabLevel + 1,
1058                                                              QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1059                                                              QStringLiteral("alphaMode"),
1060                                                              qtMode);
1061 
1062             }
1063         }
1064 
1065         {
1066             ai_real alphaCutoff;
1067             result = material->Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
1068             if (result == aiReturn_SUCCESS) {
1069                 QSSGQmlUtilities::writeQmlPropertyHelper(output,
1070                                                          tabLevel + 1,
1071                                                          QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1072                                                          QStringLiteral("alphaCutoff"),
1073                                                          alphaCutoff);
1074             }
1075         }
1076 
1077         {
1078             bool isUnlit;
1079             result = material->Get(AI_MATKEY_GLTF_UNLIT, isUnlit);
1080             if (result == aiReturn_SUCCESS && isUnlit)
1081                 output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("lighting: PrincipledMaterial.NoLighting\n");
1082         }
1083 
1084         // SpecularGlossiness Properties
1085         {
1086 
1087             // diffuseFactor (color) // not used (yet), but ends up being diffuseColor
1088 //            {
1089 //                aiColor4D diffuseColor;
1090 //                result = material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
1091 //                if (result == aiReturn_SUCCESS)
1092 //                    QSSGQmlUtilities::writeQmlPropertyHelper(output,
1093 //                                                             tabLevel + 1,
1094 //                                                             QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1095 //                                                             QStringLiteral("diffuseColor"),
1096 //                                                             aiColorToQColor(diffuseColor));
1097 //            }
1098 
1099             // specularColor (color) (our property is a float?)
1100 //            {
1101 //                aiColor3D specularColor;
1102 //                result = material->Get(AI_MATKEY_COLOR_SPECULAR, specularColor);
1103 //                if (result == aiReturn_SUCCESS)
1104 //                    QSSGQmlUtilities::writeQmlPropertyHelper(output,
1105 //                                                             tabLevel + 1,
1106 //                                                             QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1107 //                                                             QStringLiteral("specularTint"),
1108 //                                                             aiColorToQColor(specularColor));
1109 //            }
1110 
1111             // glossinessFactor (float)
1112             {
1113                 ai_real glossiness;
1114                 result = material->Get(AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR, glossiness);
1115                 if (result == aiReturn_SUCCESS)
1116                     QSSGQmlUtilities::writeQmlPropertyHelper(output,
1117                                                              tabLevel + 1,
1118                                                              QSSGQmlUtilities::PropertyMap::PrincipledMaterial,
1119                                                              QStringLiteral("specularAmount"),
1120                                                              glossiness);
1121             }
1122 
1123             // diffuseTexture // not used (yet), but ends up being diffuseMap(1)
1124 //            {
1125 //                QString diffuseMapImage = generateImage(material, aiTextureType_DIFFUSE, 0, tabLevel + 1);
1126 //                if (!diffuseMapImage.isNull())
1127 //                    output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("diffuseMap: ") << diffuseMapImage << QStringLiteral("\n");
1128 //            }
1129 
1130             // specularGlossinessTexture
1131             {
1132                 QString specularMapImage = generateImage(material, aiTextureType_SPECULAR, 0, tabLevel + 1);
1133                 if (!specularMapImage.isNull())
1134                     output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("specularMap: ") << specularMapImage << QStringLiteral("\n");
1135             }
1136         }
1137     }
1138 
1139     output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("}");
1140 }
1141 
1142 namespace  {
aiTilingMode(int tilingMode)1143 QString aiTilingMode(int tilingMode) {
1144     if (tilingMode == aiTextureMapMode_Wrap)
1145         return QStringLiteral("Texture.Repeat");
1146     if (tilingMode == aiTextureMapMode_Mirror)
1147         return QStringLiteral("Texture.Mirror");
1148 
1149     return QStringLiteral("Texture.ClampToEdge");
1150 }
1151 }
1152 
generateImage(aiMaterial * material,aiTextureType textureType,unsigned index,int tabLevel)1153 QString AssimpImporter::generateImage(aiMaterial *material, aiTextureType textureType, unsigned index, int tabLevel)
1154 {
1155     // Figure out if there is actually something to generate
1156     aiString texturePath;
1157     material->Get(AI_MATKEY_TEXTURE(textureType, index), texturePath);
1158     // If there is no texture, then there is nothing to generate
1159     if (texturePath.length == 0)
1160         return QString();
1161     QString texture = QString::fromUtf8(texturePath.C_Str());
1162     // Replace Windows separator to Unix separator
1163     // so that assets including Windows relative path can be converted on Unix.
1164     texture.replace("\\","/");
1165     QString targetFileName;
1166     // Is this an embedded texture or a file
1167     if (texture.startsWith("*")) {
1168         // Embedded Texture (already exists)
1169         texture.remove(0, 1);
1170         targetFileName =  QStringLiteral("maps/") + texture + QStringLiteral(".png");
1171     } else {
1172         // File Reference (needs to be copied into component)
1173         // Check that this file exists
1174         QString sourcePath(m_sourceFile.absolutePath() + "/" + texture);
1175         QFileInfo sourceFile(sourcePath);
1176         // If it doesn't exist, there is nothing to generate
1177         if (!sourceFile.exists()) {
1178             qWarning() << sourcePath << " (a.k.a. " << sourceFile.absoluteFilePath() << ")"
1179                        << " does not exist, skipping";
1180             return QString();
1181         }
1182         targetFileName = QStringLiteral("maps/") + sourceFile.fileName();
1183         // Copy the file to the maps directory
1184         m_savePath.mkdir(QStringLiteral("./maps"));
1185         QFileInfo targetFile = m_savePath.absolutePath() + QDir::separator() + targetFileName;
1186         if (QFile::copy(sourceFile.absoluteFilePath(), targetFile.absoluteFilePath()))
1187             m_generatedFiles += targetFile.absoluteFilePath();
1188     }
1189     // Start QML generation
1190     QString outputString;
1191     QTextStream output(&outputString, QIODevice::WriteOnly);
1192     output << QStringLiteral("Texture {\n");
1193 
1194     output << QSSGQmlUtilities::insertTabs(tabLevel + 1) << QStringLiteral("source: \"")
1195         << targetFileName << QStringLiteral("\"\n");
1196 
1197     // mapping
1198     int textureMapping;
1199     aiReturn result = material->Get(AI_MATKEY_MAPPING(textureType, index), textureMapping);
1200     if (result == aiReturn_SUCCESS) {
1201         if (textureMapping == aiTextureMapping_UV) {
1202             // So we should be able to always hit this case by passing the right flags
1203             // at import.
1204             QSSGQmlUtilities::writeQmlPropertyHelper(output,
1205                                                        tabLevel + 1,
1206                                                        QSSGQmlUtilities::PropertyMap::Texture,
1207                                                        QStringLiteral("mappingMode"),
1208                                                        QStringLiteral("Texture.Normal"));
1209             // It would be possible to use another channel than UV0 to map texture data
1210             // but for now we force everything to use UV0
1211             //int uvSource;
1212             //material->Get(AI_MATKEY_UVWSRC(textureType, index), uvSource);
1213         } else if (textureMapping == aiTextureMapping_SPHERE) {
1214             // (not supported)
1215         } else if (textureMapping == aiTextureMapping_CYLINDER) {
1216             // (not supported)
1217         } else if (textureMapping == aiTextureMapping_BOX) {
1218             // (not supported)
1219         } else if (textureMapping == aiTextureMapping_PLANE) {
1220             // (not supported)
1221         } else {
1222             // other... (not supported)
1223         }
1224     }
1225 
1226     // mapping mode U
1227     int mappingModeU;
1228     result = material->Get(AI_MATKEY_MAPPINGMODE_U(textureType, index), mappingModeU);
1229     if (result == aiReturn_SUCCESS) {
1230         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1231                                                    tabLevel + 1,
1232                                                    QSSGQmlUtilities::PropertyMap::Texture,
1233                                                    QStringLiteral("tilingModeHorizontal"),
1234                                                    aiTilingMode(mappingModeU));
1235     } else {
1236         // import formats seem to think repeat is the default
1237         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1238                                                    tabLevel + 1,
1239                                                    QSSGQmlUtilities::PropertyMap::Texture,
1240                                                    QStringLiteral("tilingModeHorizontal"),
1241                                                    QStringLiteral("Texture.Repeat"));
1242     }
1243 
1244     // mapping mode V
1245     int mappingModeV;
1246     result = material->Get(AI_MATKEY_MAPPINGMODE_V(textureType, index), mappingModeV);
1247     if (result == aiReturn_SUCCESS) {
1248         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1249                                                    tabLevel + 1,
1250                                                    QSSGQmlUtilities::PropertyMap::Texture,
1251                                                    QStringLiteral("tilingModeVertical"),
1252                                                    aiTilingMode(mappingModeV));
1253     } else {
1254         // import formats seem to think repeat is the default
1255         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1256                                                    tabLevel + 1,
1257                                                    QSSGQmlUtilities::PropertyMap::Texture,
1258                                                    QStringLiteral("tilingModeVertical"),
1259                                                    QStringLiteral("Texture.Repeat"));
1260     }
1261 
1262     aiUVTransform transforms;
1263     result = material->Get(AI_MATKEY_UVTRANSFORM(textureType, index), transforms);
1264     if (result == aiReturn_SUCCESS) {
1265         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1266                                                    tabLevel + 1,
1267                                                    QSSGQmlUtilities::PropertyMap::Texture,
1268                                                    QStringLiteral("rotationUV"),
1269                                                    transforms.mRotation);
1270         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1271                                                    tabLevel + 1,
1272                                                    QSSGQmlUtilities::PropertyMap::Texture,
1273                                                    QStringLiteral("positionU"),
1274                                                    transforms.mTranslation.x);
1275         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1276                                                    tabLevel + 1,
1277                                                    QSSGQmlUtilities::PropertyMap::Texture,
1278                                                    QStringLiteral("positionV"),
1279                                                    transforms.mTranslation.y);
1280         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1281                                                    tabLevel + 1,
1282                                                    QSSGQmlUtilities::PropertyMap::Texture,
1283                                                    QStringLiteral("scaleU"),
1284                                                    transforms.mScaling.x);
1285         QSSGQmlUtilities::writeQmlPropertyHelper(output,
1286                                                    tabLevel + 1,
1287                                                    QSSGQmlUtilities::PropertyMap::Texture,
1288                                                    QStringLiteral("scaleV"),
1289                                                    transforms.mScaling.y);
1290     }
1291     // We don't make use of the data here, but there are additional flags
1292     // available for example the usage of the alpha channel
1293     // texture flags
1294     //int textureFlags;
1295     //material->Get(AI_MATKEY_TEXFLAGS(textureType, index), textureFlags);
1296 
1297     output << QSSGQmlUtilities::insertTabs(tabLevel) << QStringLiteral("}");
1298 
1299     return outputString;
1300 }
1301 
processAnimations(QTextStream & output)1302 void AssimpImporter::processAnimations(QTextStream &output)
1303 {
1304     for (int idx = 0; idx < m_animations.size(); ++idx) {
1305         QHash<aiNode *, aiNodeAnim *> *animation = m_animations[idx];
1306         output << QStringLiteral("\n");
1307         output << QSSGQmlUtilities::insertTabs(1) << "Timeline {\n";
1308         output << QSSGQmlUtilities::insertTabs(2) << "id: timeline" << idx << "\n";
1309         output << QSSGQmlUtilities::insertTabs(2) << "startFrame: 0\n";
1310 
1311         QString keyframeString;
1312         QTextStream keyframeStream(&keyframeString);
1313         qreal endFrameTime = 0;
1314 
1315         for (auto itr = animation->begin(); itr != animation->end(); ++itr) {
1316             aiNode *node = itr.key();
1317 
1318             // We cannot set keyframes to nodes which do not have id.
1319             if (!m_nodeIdMap.contains(node))
1320                 continue;
1321             QString id = m_nodeIdMap[node];
1322 
1323             // We can set animation only on Node, Model, Camera or Light.
1324             if (!m_nodeTypeMap.contains(node))
1325                 continue;
1326             QSSGQmlUtilities::PropertyMap::Type type = m_nodeTypeMap[node];
1327             if (type != QSSGQmlUtilities::PropertyMap::Node
1328                 && type != QSSGQmlUtilities::PropertyMap::Model
1329                 && type != QSSGQmlUtilities::PropertyMap::Camera
1330                 && type != QSSGQmlUtilities::PropertyMap::DirectionalLight
1331                 && type != QSSGQmlUtilities::PropertyMap::PointLight
1332                 && type != QSSGQmlUtilities::PropertyMap::AreaLight
1333                 && type != QSSGQmlUtilities::PropertyMap::SpotLight)
1334                 continue;
1335 
1336             aiNodeAnim *nodeAnim = itr.value();
1337             generateKeyframes(id, "position", nodeAnim->mNumPositionKeys, nodeAnim->mPositionKeys,
1338                               keyframeStream, endFrameTime);
1339             generateKeyframes(id, "eulerRotation", nodeAnim->mNumRotationKeys, nodeAnim->mRotationKeys,
1340                               keyframeStream, endFrameTime);
1341             generateKeyframes(id, "scale", nodeAnim->mNumScalingKeys, nodeAnim->mScalingKeys,
1342                               keyframeStream, endFrameTime);
1343         }
1344 
1345         int endFrameTimeInt = qCeil(endFrameTime);
1346         output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("endFrame: ") << endFrameTimeInt << QStringLiteral("\n");
1347         output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("currentFrame: 0\n");
1348         // only the first set of animations is enabled for now.
1349         output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("enabled: ")
1350                << (animation == *m_animations.begin() ? QStringLiteral("true\n") : QStringLiteral("false\n"));
1351         output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("animations: [\n");
1352         output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("TimelineAnimation {\n");
1353         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("duration: ") << endFrameTimeInt << QStringLiteral("\n");
1354         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("from: 0\n");
1355         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("to: ") << endFrameTimeInt << QStringLiteral("\n");
1356         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("running: true\n");
1357         output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("}\n");
1358         output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("]\n");
1359 
1360         output << keyframeString;
1361 
1362         output << QSSGQmlUtilities::insertTabs(1) << QStringLiteral("}\n");
1363     }
1364 }
1365 
1366 namespace {
1367 
convertToQVector3D(const aiVector3D & vec)1368 QVector3D convertToQVector3D(const aiVector3D &vec)
1369 {
1370     return QVector3D(vec.x, vec.y, vec.z);
1371 }
1372 
convertToQVector3D(const aiQuaternion & q)1373 QVector3D convertToQVector3D(const aiQuaternion &q)
1374 {
1375     return QQuaternion(q.w, q.x, q.y, q.z).toEulerAngles();
1376 }
1377 
1378 }
1379 
1380 template <typename T>
generateKeyframes(const QString & id,const QString & propertyName,uint numKeys,const T * keys,QTextStream & output,qreal & maxKeyframeTime)1381 void AssimpImporter::generateKeyframes(const QString &id, const QString &propertyName, uint numKeys, const T *keys,
1382                                        QTextStream &output, qreal &maxKeyframeTime)
1383 {
1384     output << QStringLiteral("\n");
1385     output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("KeyframeGroup {\n");
1386     output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("target: ") << id << QStringLiteral("\n");
1387     output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("property: \"") << propertyName << QStringLiteral("\"\n");
1388     output << QStringLiteral("\n");
1389 
1390     struct Keyframe {
1391         qreal time;
1392         QVector3D value;
1393     };
1394 
1395     // First, convert all the keyframe values to QVector3D
1396     // so that adjacent keyframes can be compared with qFuzzyCompare.
1397     QList<Keyframe> keyframes;
1398     for (uint i = 0; i < numKeys; ++i) {
1399         T key = keys[i];
1400         Keyframe keyframe = {key.mTime, convertToQVector3D(key.mValue)};
1401         keyframes.push_back(keyframe);
1402         if (i == numKeys-1)
1403             maxKeyframeTime = qMax(maxKeyframeTime, keyframe.time);
1404     }
1405 
1406     // Output all the Keyframes except similar ones.
1407     for (int i = 0; i < keyframes.size(); ++i) {
1408         const Keyframe &keyframe = keyframes[i];
1409         // Skip keyframes if those are very similar to adjacent ones.
1410         if (i > 0 && i < keyframes.size()-1
1411            && qFuzzyCompare(keyframe.value, keyframes[i-1].value)
1412            && qFuzzyCompare(keyframe.value, keyframes[i+1].value)) {
1413             keyframes.removeAt(i--);
1414             continue;
1415         }
1416 
1417         output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("Keyframe {\n");
1418         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("frame: ") << keyframe.time << QStringLiteral("\n");
1419         output << QSSGQmlUtilities::insertTabs(4) << QStringLiteral("value: ")
1420                << QSSGQmlUtilities::variantToQml(keyframe.value) << QStringLiteral("\n");
1421         output << QSSGQmlUtilities::insertTabs(3) << QStringLiteral("}\n");
1422     }
1423     output << QSSGQmlUtilities::insertTabs(2) << QStringLiteral("}\n");
1424 }
1425 
isModel(aiNode * node)1426 bool AssimpImporter::isModel(aiNode *node)
1427 {
1428     return node && node->mNumMeshes > 0;
1429 }
1430 
isLight(aiNode * node)1431 bool AssimpImporter::isLight(aiNode *node)
1432 {
1433     return node && m_lights.contains(node);
1434 }
1435 
isCamera(aiNode * node)1436 bool AssimpImporter::isCamera(aiNode *node)
1437 {
1438     return node && m_cameras.contains(node);
1439 }
1440 
generateUniqueId(const QString & id)1441 QString AssimpImporter::generateUniqueId(const QString &id)
1442 {
1443     int index = 0;
1444     QString uniqueID = id;
1445     while (m_uniqueIds.contains(uniqueID))
1446         uniqueID = id + QStringLiteral("_") + QString::number(++index);
1447     m_uniqueIds.insert(uniqueID);
1448     return uniqueID;
1449 }
1450 
1451 // This method is used to walk a subtree to see if any of the nodes actually
1452 // add any state to the scene.  A branch of empty transform nodes would only be
1453 // useful if they were being used somewhere else (like where to aim a camera),
1454 // but the general case is that they can be safely culled
containsNodesOfConsequence(aiNode * node)1455 bool AssimpImporter::containsNodesOfConsequence(aiNode *node)
1456 {
1457     bool isUseful = false;
1458 
1459     isUseful |= isLight(node);
1460     isUseful |= isModel(node);
1461     isUseful |= isCamera(node);
1462 
1463     // Return early if we know already
1464     if (isUseful)
1465         return true;
1466 
1467     for (uint i = 0; i < node->mNumChildren; ++i)
1468         isUseful |= containsNodesOfConsequence(node->mChildren[i]);
1469 
1470     return isUseful;
1471 }
1472 
processOptions(const QVariantMap & options)1473 void AssimpImporter::processOptions(const QVariantMap &options)
1474 {
1475     // Setup import settings based given options
1476     // You can either pass the whole options object, or just the "options" object
1477     // so get the right scope.
1478     QJsonObject optionsObject = QJsonObject::fromVariantMap(options);
1479     if (optionsObject.contains(QStringLiteral("options")))
1480         optionsObject = optionsObject.value(QStringLiteral("options")).toObject();
1481 
1482     if (optionsObject.isEmpty())
1483         return;
1484 
1485     // parse the options list for values
1486     // We always need to triangulate and remove non triangles
1487     m_postProcessSteps = aiPostProcessSteps(aiProcess_Triangulate | aiProcess_SortByPType);
1488 
1489     if (checkBooleanOption(QStringLiteral("calculateTangentSpace"), optionsObject))
1490         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_CalcTangentSpace);
1491 
1492     if (checkBooleanOption(QStringLiteral("joinIdenticalVertices"), optionsObject))
1493         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_JoinIdenticalVertices);
1494 
1495     if (checkBooleanOption(QStringLiteral("generateNormals"), optionsObject))
1496         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_GenNormals);
1497 
1498     if (checkBooleanOption(QStringLiteral("generateSmoothNormals"), optionsObject))
1499         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_GenSmoothNormals);
1500 
1501     if (checkBooleanOption(QStringLiteral("splitLargeMeshes"), optionsObject))
1502         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_SplitLargeMeshes);
1503 
1504     if (checkBooleanOption(QStringLiteral("preTransformVertices"), optionsObject))
1505         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_PreTransformVertices);
1506 
1507     if (checkBooleanOption(QStringLiteral("limitBoneWeights"), optionsObject))
1508         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_LimitBoneWeights);
1509 
1510     if (checkBooleanOption(QStringLiteral("improveCacheLocality"), optionsObject))
1511         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_ImproveCacheLocality);
1512 
1513     if (checkBooleanOption(QStringLiteral("removeRedundantMaterials"), optionsObject))
1514         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_RemoveRedundantMaterials);
1515 
1516     if (checkBooleanOption(QStringLiteral("fixInfacingNormals"), optionsObject))
1517         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_FixInfacingNormals);
1518 
1519     if (checkBooleanOption(QStringLiteral("findDegenerates"), optionsObject))
1520         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_FindDegenerates);
1521 
1522     if (checkBooleanOption(QStringLiteral("findInvalidData"), optionsObject))
1523         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_FindInvalidData);
1524 
1525     if (checkBooleanOption(QStringLiteral("transformUVCoordinates"), optionsObject))
1526         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_TransformUVCoords);
1527 
1528     if (checkBooleanOption(QStringLiteral("findInstances"), optionsObject))
1529         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_FindInstances);
1530 
1531     if (checkBooleanOption(QStringLiteral("optimizeMeshes"), optionsObject))
1532         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_OptimizeMeshes);
1533 
1534     if (checkBooleanOption(QStringLiteral("optimizeGraph"), optionsObject))
1535         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_OptimizeGraph);
1536 
1537     if (checkBooleanOption(QStringLiteral("globalScale"), optionsObject)) {
1538         m_globalScaleValue = getRealOption(QStringLiteral("globalScaleValue"), optionsObject);
1539         if (m_globalScaleValue == 0.0)
1540             m_globalScaleValue = 1.0;
1541     }
1542 
1543     if (checkBooleanOption(QStringLiteral("dropNormals"), optionsObject))
1544         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_DropNormals);
1545 
1546     aiComponent removeComponents = aiComponent(0);
1547 
1548     if (checkBooleanOption(QStringLiteral("removeComponentNormals"), optionsObject))
1549         removeComponents = aiComponent(removeComponents | aiComponent_NORMALS);
1550 
1551     if (checkBooleanOption(QStringLiteral("removeComponentTangentsAndBitangents"), optionsObject))
1552         removeComponents = aiComponent(removeComponents | aiComponent_TANGENTS_AND_BITANGENTS);
1553 
1554     if (checkBooleanOption(QStringLiteral("removeComponentColors"), optionsObject))
1555         removeComponents = aiComponent(removeComponents | aiComponent_COLORS);
1556 
1557     if (checkBooleanOption(QStringLiteral("removeComponentUVs"), optionsObject))
1558         removeComponents = aiComponent(removeComponents | aiComponent_TEXCOORDS);
1559 
1560     if (checkBooleanOption(QStringLiteral("removeComponentBoneWeights"), optionsObject))
1561         removeComponents = aiComponent(removeComponents | aiComponent_BONEWEIGHTS);
1562 
1563     if (checkBooleanOption(QStringLiteral("removeComponentAnimations"), optionsObject))
1564         removeComponents = aiComponent(removeComponents | aiComponent_ANIMATIONS);
1565 
1566     if (checkBooleanOption(QStringLiteral("removeComponentTextures"), optionsObject))
1567         removeComponents = aiComponent(removeComponents | aiComponent_TEXTURES);
1568 
1569     if (removeComponents != aiComponent(0)) {
1570         m_postProcessSteps = aiPostProcessSteps(m_postProcessSteps | aiProcess_RemoveComponent);
1571         m_importer->SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, removeComponents);
1572     }
1573 
1574     bool preservePivots = checkBooleanOption(QStringLiteral("fbxPreservePivots"), optionsObject);
1575     m_importer->SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, preservePivots);
1576 }
1577 
checkBooleanOption(const QString & optionName,const QJsonObject & options)1578 bool AssimpImporter::checkBooleanOption(const QString &optionName, const QJsonObject &options)
1579 {
1580     if (!options.contains(optionName))
1581         return false;
1582 
1583     QJsonObject option = options.value(optionName).toObject();
1584     return option.value(QStringLiteral("value")).toBool();
1585 }
1586 
getRealOption(const QString & optionName,const QJsonObject & options)1587 qreal AssimpImporter::getRealOption(const QString &optionName, const QJsonObject &options)
1588 {
1589     if (!options.contains(optionName))
1590         return false;
1591 
1592     QJsonObject option = options.value(optionName).toObject();
1593     return option.value(QStringLiteral("value")).toDouble();
1594 }
1595 
1596 QT_END_NAMESPACE
1597