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