Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
assimpimporter_rt.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#include <assimputils.h>
9
10#include <QtCore/qurl.h>
11#include <QtCore/qbytearrayalgorithms.h>
12#include <QtGui/QQuaternion>
13#include <QtQml/QQmlFile>
14
15#include <QtQuick3DAssetImport/private/qssgassetimporterfactory_p.h>
16#include <QtQuick3DAssetImport/private/qssgassetimporter_p.h>
17#include <QtQuick3DAssetUtils/private/qssgscenedesc_p.h>
18#include <QtQuick3DAssetUtils/private/qssgsceneedit_p.h>
19#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
20
21#include <QtQuick3DUtils/private/qssgutils_p.h>
22
23// ASSIMP INC
24#include <assimp/Importer.hpp>
25#include <assimp/scene.h>
26#include <assimp/Logger.hpp>
27#include <assimp/DefaultLogger.hpp>
28#include <assimp/postprocess.h>
29#include <assimp/material.h>
30#include <assimp/GltfMaterial.h>
31#include <assimp/importerdesc.h>
32#include <assimp/IOSystem.hpp>
33#include <assimp/IOStream.hpp>
34
35// ASSIMP INC
36
37QT_BEGIN_NAMESPACE
38
39//////////////////////// ASSIMP IMP
40
41#define AI_GLTF_FILTER_NEAREST 0x2600
42#define AI_GLTF_FILTER_LINEAR 0x2601
43#define AI_GLTF_FILTER_NEAREST_MIPMAP_NEAREST 0x2700
44#define AI_GLTF_FILTER_LINEAR_MIPMAP_NEAREST 0x2701
45#define AI_GLTF_FILTER_NEAREST_MIPMAP_LINEAR 0x2702
46#define AI_GLTF_FILTER_LINEAR_MIPMAP_LINEAR 0x2703
47
48Q_REQUIRED_RESULT static inline QColor aiColorToQColor(const aiColor3D &color)
49{
50 return QColor::fromRgbF(color.r, color.g, color.b, 1.0f);
51}
52
53Q_REQUIRED_RESULT static inline QColor aiColorToQColor(const aiColor4D &color)
54{
55 return QColor::fromRgbF(color.r, color.g, color.b, color.a);
56}
57
58static QByteArray fromAiString(const aiString &string)
59{
60 const qsizetype length = string.length;
61 return QByteArray(string.data, length);
62}
63
70
72
73using NodeMap = QHash<const aiNode *, NodeInfo>;
74
75using AnimationNodeMap = QHash<QByteArray, QSSGSceneDesc::Node *>;
76
77[[nodiscard]] static inline bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
78{
79 return (a.mTranslation == b.mTranslation && a.mScaling == b.mScaling && a.mRotation == b.mRotation);
80};
81
83{
84 aiTextureMapMode modes[3] {};
85 aiTextureMapping mapping = aiTextureMapping::aiTextureMapping_UV;
88 uint uvIndex { 0 };
89 aiUVTransform transform;
90};
91
92bool operator==(const TextureInfo &a, const TextureInfo &b)
93{
94 return (a.mapping == b.mapping)
95 && (std::memcmp(a.modes, b.modes, sizeof(a.modes)) == 0)
96 && (a.minFilter == b.minFilter)
97 && (a.magFilter == b.magFilter)
98 && (a.uvIndex == b.uvIndex)
99 && isEqual(a.transform, b.transform);
100}
101
108
109size_t qHash(const TextureEntry &key, size_t seed)
110{
111 static_assert(std::is_same_v<decltype(key.info.transform), aiUVTransform>, "Unexpected type");
112 const auto infoKey = quintptr(key.info.mapping)
113 ^ (quintptr(key.info.modes[0]) ^ quintptr(key.info.modes[1]) ^ quintptr(key.info.modes[2]))
114 ^ quintptr(key.info.minFilter ^ key.info.magFilter)
115 ^ quintptr(key.info.uvIndex)
116 ^ qHashBits(&key.info.transform, sizeof(aiUVTransform), seed);
117
118 return qHash(key.name, seed) ^ infoKey;
119}
120
121bool operator==(const TextureEntry &a, const TextureEntry &b)
122{
123 return (a.name == b.name) && (a.info == b.info);
124}
125
167
168class ResourceIOStream : public Assimp::IOStream
169{
170public:
171 ResourceIOStream(const char *pFile, const char *pMode);
172
173 // IOStream interface
174 size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override;
175 size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override;
176 aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override;
177 size_t Tell() const override;
178 size_t FileSize() const override;
179 void Flush() override;
180
181private:
182 QFile file;
183};
184
185ResourceIOStream::ResourceIOStream(const char *pFile, const char *pMode) : file(QString::fromStdString(pFile))
186{
187 QByteArray mode = QByteArray(pMode);
188 QFile::OpenMode openMode = QFile::NotOpen;
189 if (mode.startsWith("r"))
190 openMode |= QFile::ReadOnly;
191 else if (mode.startsWith("w"))
192 openMode |= QFile::WriteOnly;
193 if (mode.endsWith("t"))
194 openMode |= QFile::Text;
195 if (!file.open(openMode)) {
196 qWarning("Failed to open file %s: %s",
197 qPrintable(file.fileName()), qPrintable(file.errorString()));
198 }
199}
200
201size_t ResourceIOStream::Read(void *pvBuffer, size_t pSize, size_t pCount)
202{
203 size_t ret = 0;
204 auto buffer = static_cast<char *>(pvBuffer);
205 for (ret = 0; ret < pCount; ret++) {
206 size_t read = file.read(buffer, pSize);
207 if (read != pSize)
208 return ret;
209 buffer += read;
210 }
211 return ret;
212}
213
214size_t ResourceIOStream::Write(const void *pvBuffer, size_t pSize, size_t pCount)
215{
216 Q_UNUSED(pvBuffer);
217 Q_UNUSED(pSize);
218 Q_UNUSED(pCount);
219 Q_UNIMPLEMENTED();
220 return 0;
221}
222
223aiReturn ResourceIOStream::Seek(size_t pOffset, aiOrigin pOrigin)
224{
225 switch (pOrigin) {
226 case aiOrigin_SET:
227 file.seek(pOffset);
228 break;
229 case aiOrigin_CUR:
230 file.seek(file.pos() + pOffset);
231 break;
232 case aiOrigin_END:
233 file.seek(file.size() + pOffset);
234 break;
235 default:
236 return aiReturn_FAILURE;
237 }
238 return aiReturn_SUCCESS;
239}
240
242{
243 return file.pos();
244}
245
247{
248 return file.size();
249}
250
252{
253}
254
255class ResourceIOSystem : public Assimp::IOSystem
256{
257public:
259 // IOSystem interface
260 bool Exists(const char *pFile) const override;
261 char getOsSeparator() const override;
262 Assimp::IOStream *Open(const char *pFile, const char *pMode) override;
263 void Close(Assimp::IOStream *pFile) override;
264};
265
266ResourceIOSystem::ResourceIOSystem() : Assimp::IOSystem() { }
267
268bool ResourceIOSystem::Exists(const char *pFile) const
269{
270 return QFile::exists(QString::fromStdString(pFile));
271}
272
274{
275 return QDir::separator().toLatin1();
276}
277
278Assimp::IOStream *ResourceIOSystem::Open(const char *pFile, const char *pMode)
279{
280 return new ResourceIOStream(pFile, pMode);
281}
282
283void ResourceIOSystem::Close(Assimp::IOStream *pFile)
284{
285 delete pFile;
286}
287
288static void setNodeProperties(QSSGSceneDesc::Node &target,
289 const aiNode &source,
290 const SceneInfo &sceneInfo,
291 aiMatrix4x4 *transformCorrection)
292{
293 // objectName
294 if (target.name.isNull()) {
295 if (source.mName.length > 0)
296 target.name = fromAiString(source.mName);
297 else
298 target.name = QSSGQmlUtilities::getQmlElementName(target);
299 }
300
301 // Apply correction if necessary
302 aiMatrix4x4 transformMatrix;
303 if (transformCorrection)
304 transformMatrix = source.mTransformation * *transformCorrection;
305 else
306 transformMatrix = source.mTransformation;
307
308 // Decompose Transform Matrix to get properties
309 aiVector3D scaling;
310 aiQuaternion rotation;
311 aiVector3D translation;
312 transformMatrix.Decompose(scaling, rotation, translation);
313
314 // translate
315 if (!sceneInfo.opt.designStudioWorkarounds) {
316 QSSGSceneDesc::setProperty(target, "position", &QQuick3DNode::setPosition, QVector3D { translation.x, translation.y, translation.z });
317 } else {
318 QSSGSceneDesc::setProperty(target, "x", &QQuick3DNode::setX, translation.x);
319 QSSGSceneDesc::setProperty(target, "y", &QQuick3DNode::setY, translation.y);
320 QSSGSceneDesc::setProperty(target, "z", &QQuick3DNode::setZ, translation.z);
321 }
322
323
324 // rotation
325 const QQuaternion rot(rotation.w, rotation.x, rotation.y, rotation.z);
326 QSSGSceneDesc::setProperty(target, "rotation", &QQuick3DNode::setRotation, rot);
327
328 // scale
329 QSSGSceneDesc::setProperty(target, "scale", &QQuick3DNode::setScale, QVector3D { scaling.x, scaling.y, scaling.z });
330 // pivot
331
332 // opacity
333
334 // visible
335}
336
337static void setTextureProperties(QSSGSceneDesc::Texture &target, const TextureInfo &texInfo, const SceneInfo &sceneInfo)
338{
339 const bool forceMipMapGeneration = sceneInfo.opt.forceMipMapGeneration;
340
341 if (texInfo.uvIndex > 0) {
342 // Quick3D supports 2 tex coords.
343 // According to gltf's khronos default implementation,
344 // the index will be selected to the nearest one.
345 QSSGSceneDesc::setProperty(target, "indexUV", &QQuick3DTexture::setIndexUV, 1);
346 }
347
348 // mapping
349 if (texInfo.mapping == aiTextureMapping_UV) {
350 // So we should be able to always hit this case by passing the right flags
351 // at import.
352 QSSGSceneDesc::setProperty(target, "mappingMode", &QQuick3DTexture::setMappingMode, QQuick3DTexture::MappingMode::UV);
353 // It would be possible to use another channel than UV0 to map texture data
354 // but for now we force everything to use UV0
355 //int uvSource;
356 //material->Get(AI_MATKEY_UVWSRC(textureType, index), uvSource);
357 } // else (not supported)
358
359 static const auto asQtTilingMode = [](aiTextureMapMode mode) {
360 switch (mode) {
361 case aiTextureMapMode_Wrap:
362 return QQuick3DTexture::TilingMode::Repeat;
363 case aiTextureMapMode_Clamp:
364 return QQuick3DTexture::TilingMode::ClampToEdge;
365 case aiTextureMapMode_Mirror:
366 return QQuick3DTexture::TilingMode::MirroredRepeat;
367 default:
368 break;
369 }
370
371 return QQuick3DTexture::TilingMode::Repeat;
372 };
373
374 // mapping mode U
375 QSSGSceneDesc::setProperty(target, "tilingModeHorizontal", &QQuick3DTexture::setHorizontalTiling, asQtTilingMode(texInfo.modes[0]));
376
377 // mapping mode V
378 QSSGSceneDesc::setProperty(target, "tilingModeVertical", &QQuick3DTexture::setVerticalTiling, asQtTilingMode(texInfo.modes[1]));
379
380 const bool applyUvTransform = !isEqual(texInfo.transform, aiUVTransform());
381 if (applyUvTransform) {
382 // UV origins -
383 // glTF: 0, 1 (top left of texture)
384 // Assimp, Collada?, FBX?: 0.5, 0.5
385 // Quick3D: 0, 0 (bottom left of texture)
386 // Assimp already tries to fix it but it's not correct.
387 // So, we restore original values and then use pivot
388 const auto &transform = texInfo.transform;
389 float rotation = -transform.mRotation;
390 float rotationUV = qRadiansToDegrees(rotation);
391 float posU = transform.mTranslation.x;
392 float posV = transform.mTranslation.y;
393 if (sceneInfo.opt.gltfMode) {
394 const float rcos = std::cos(rotation);
395 const float rsin = std::sin(rotation);
396 posU -= 0.5f * transform.mScaling.x * (-rcos + rsin + 1.0f);
397 posV -= (0.5f * transform.mScaling.y * (rcos + rsin - 1.0f) + 1.0f - transform.mScaling.y);
398 QSSGSceneDesc::setProperty(target, "pivotV", &QQuick3DTexture::setPivotV, 1.0f);
399 } else {
400 QSSGSceneDesc::setProperty(target, "pivotU", &QQuick3DTexture::setPivotV, 0.5f);
401 QSSGSceneDesc::setProperty(target, "pivotV", &QQuick3DTexture::setPivotV, 0.5f);
402 }
403
404 QSSGSceneDesc::setProperty(target, "positionU", &QQuick3DTexture::setPositionU, posU);
405 QSSGSceneDesc::setProperty(target, "positionV", &QQuick3DTexture::setPositionV, posV);
406 QSSGSceneDesc::setProperty(target, "rotationUV", &QQuick3DTexture::setRotationUV, rotationUV);
407 QSSGSceneDesc::setProperty(target, "scaleU", &QQuick3DTexture::setScaleU, transform.mScaling.x);
408 QSSGSceneDesc::setProperty(target, "scaleV", &QQuick3DTexture::setScaleV, transform.mScaling.y);
409 }
410 // We don't make use of the data here, but there are additional flags
411 // available for example the usage of the alpha channel
412 // texture flags
413 //int textureFlags;
414 //material->Get(AI_MATKEY_TEXFLAGS(textureType, index), textureFlags);
415
416 // Always generate and use mipmaps for imported assets
417 bool generateMipMaps = forceMipMapGeneration;
418 auto mipFilter = forceMipMapGeneration ? QQuick3DTexture::Filter::Linear : QQuick3DTexture::Filter::None;
419
420 // magFilter
421 auto filter = (texInfo.magFilter == AI_GLTF_FILTER_NEAREST) ? QQuick3DTexture::Filter::Nearest : QQuick3DTexture::Filter::Linear;
422 QSSGSceneDesc::setProperty(target, "magFilter", &QQuick3DTexture::setMagFilter, filter);
423
424 // minFilter
425 if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST) {
426 filter = QQuick3DTexture::Filter::Nearest;
427 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR) {
428 filter = QQuick3DTexture::Filter::Linear;
430 filter = QQuick3DTexture::Filter::Nearest;
431 mipFilter = QQuick3DTexture::Filter::Nearest;
433 filter = QQuick3DTexture::Filter::Linear;
434 mipFilter = QQuick3DTexture::Filter::Nearest;
436 filter = QQuick3DTexture::Filter::Nearest;
437 mipFilter = QQuick3DTexture::Filter::Linear;
438 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR_MIPMAP_LINEAR) {
439 filter = QQuick3DTexture::Filter::Linear;
440 mipFilter = QQuick3DTexture::Filter::Linear;
441 }
442 QSSGSceneDesc::setProperty(target, "minFilter", &QQuick3DTexture::setMinFilter, filter);
443
444 // mipFilter
445 generateMipMaps = (mipFilter != QQuick3DTexture::Filter::None);
446
447 if (generateMipMaps) {
448 QSSGSceneDesc::setProperty(target, "generateMipmaps", &QQuick3DTexture::setGenerateMipmaps, true);
449 QSSGSceneDesc::setProperty(target, "mipFilter", &QQuick3DTexture::setMipFilter, mipFilter);
450 }
451}
452
453static void setMaterialProperties(QSSGSceneDesc::Material &target, const aiMaterial &source, const SceneInfo &sceneInfo, QSSGSceneDesc::Material::RuntimeType type)
454{
455 if (target.name.isNull()) {
456 aiString materialName = source.GetName();
457 if (materialName.length > 0)
458 target.name = fromAiString(materialName);
459 else
460 target.name = QSSGQmlUtilities::getQmlElementName(target);
461 }
462
463 const auto createTextureNode = [&sceneInfo, &target](const aiMaterial &material, aiTextureType textureType, unsigned int index) {
464 const auto &srcScene = sceneInfo.scene;
465 QSSGSceneDesc::Texture *tex = nullptr;
466 aiString texturePath;
467 TextureInfo texInfo;
468
469 if (material.GetTexture(textureType, index, &texturePath, &texInfo.mapping, &texInfo.uvIndex, nullptr, nullptr, texInfo.modes) == aiReturn_SUCCESS) {
470 if (texturePath.length > 0) {
471 aiUVTransform transform;
472 if (material.Get(AI_MATKEY_UVTRANSFORM(textureType, index), transform) == aiReturn_SUCCESS)
473 texInfo.transform = transform;
474
475 material.Get(AI_MATKEY_UVWSRC(textureType, index), texInfo.uvIndex);
476 material.Get(AI_MATKEY_GLTF_MAPPINGFILTER_MIN(textureType, index), texInfo.minFilter);
477 material.Get(AI_MATKEY_GLTF_MAPPINGFILTER_MAG(textureType, index), texInfo.magFilter);
478
479 auto &textureMap = sceneInfo.textureMap;
480
481 QByteArray texName = QByteArray(texturePath.C_Str(), texturePath.length);
482 // Check if we already processed this texture
483 const auto it = textureMap.constFind(TextureEntry{texName, texInfo});
484 if (it != textureMap.cend()) {
485 Q_ASSERT(it->texture);
486 tex = it->texture;
487 } else {
488 // Two types, externally referenced or embedded
489 // Use the source file name as the identifier, since that will hopefully be fairly stable for re-import.
490 tex = new QSSGSceneDesc::Texture(QSSGSceneDesc::Texture::RuntimeType::Image2D, texName);
491 textureMap.insert(TextureEntry{fromAiString(texturePath), texInfo, tex});
492 QSSGSceneDesc::addNode(target, *tex);
493 setTextureProperties(*tex, texInfo, sceneInfo); // both
494
495 auto aEmbeddedTex = srcScene.GetEmbeddedTextureAndIndex(texturePath.C_Str());
496 const auto &embeddedTexId = aEmbeddedTex.second;
497 if (embeddedTexId > -1) {
498 QSSGSceneDesc::TextureData *textureData = nullptr;
499 auto &embeddedTextures = sceneInfo.embeddedTextureMap;
500 textureData = embeddedTextures[embeddedTexId];
501 if (!textureData) {
502 const auto *sourceTexture = aEmbeddedTex.first;
503 Q_ASSERT(sourceTexture->pcData);
504 // Two cases of embedded textures, uncompress and compressed.
505 const bool isCompressed = (sourceTexture->mHeight == 0);
506
507 // For compressed textures this is the size of the image buffer (in bytes)
508 const qsizetype asize = (isCompressed) ? sourceTexture->mWidth : (sourceTexture->mHeight * sourceTexture->mWidth) * sizeof(aiTexel);
509 const QSize size = (!isCompressed) ? QSize(int(sourceTexture->mWidth), int(sourceTexture->mHeight)) : QSize();
510 QByteArray imageData { reinterpret_cast<const char *>(sourceTexture->pcData), asize };
511 const auto format = (isCompressed) ? QByteArray(sourceTexture->achFormatHint) : QByteArrayLiteral("rgba8888");
512 const quint8 flags = isCompressed ? quint8(QSSGSceneDesc::TextureData::Flags::Compressed) : 0;
513 textureData = new QSSGSceneDesc::TextureData(imageData, size, format, flags);
514 QSSGSceneDesc::addNode(*tex, *textureData);
515 embeddedTextures[embeddedTexId] = textureData;
516 }
517
518 if (textureData)
519 QSSGSceneDesc::setProperty(*tex, "textureData", &QQuick3DTexture::setTextureData, textureData);
520 } else {
521 auto relativePath = QString::fromUtf8(texturePath.C_Str());
522 // Replace Windows separator to Unix separator
523 // so that assets including Windows relative path can be converted on Unix.
524 relativePath.replace("\\","/");
525 const auto path = sceneInfo.workingDir.absoluteFilePath(relativePath);
526 QSSGSceneDesc::setProperty(*tex, "source", &QQuick3DTexture::setSource, QUrl{ path });
527 }
528 }
529 }
530 }
531
532 return tex;
533 };
534
535 aiReturn result;
536
537 if (type == QSSGSceneDesc::Material::RuntimeType::PrincipledMaterial) {
538 {
539 aiColor4D baseColorFactor;
540 result = source.Get(AI_MATKEY_BASE_COLOR, baseColorFactor);
541 if (result == aiReturn_SUCCESS) {
542 // Some special handling required since baseColorFactor's are stored as linear factors and need to be converted for Qt Quick
543 const QColor sRGBBaseColorFactor = QSSGUtils::color::linearTosRGB(QVector4D(baseColorFactor.r, baseColorFactor.g, baseColorFactor.b, baseColorFactor.a));
544 QSSGSceneDesc::setProperty(target, "baseColor", &QQuick3DPrincipledMaterial::setBaseColor, sRGBBaseColorFactor);
545 } else {
546 // Also try diffuse color as a fallback
547 aiColor3D diffuseColor;
548 result = source.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
549 if (result == aiReturn_SUCCESS)
550 QSSGSceneDesc::setProperty(target, "baseColor", &QQuick3DPrincipledMaterial::setBaseColor, aiColorToQColor(diffuseColor));
551 }
552 }
553
554 if (auto baseColorTexture = createTextureNode(source, AI_MATKEY_BASE_COLOR_TEXTURE)) {
555 QSSGSceneDesc::setProperty(target, "baseColorMap", &QQuick3DPrincipledMaterial::setBaseColorMap, baseColorTexture);
556 QSSGSceneDesc::setProperty(target, "opacityChannel", &QQuick3DPrincipledMaterial::setOpacityChannel, QQuick3DPrincipledMaterial::TextureChannelMapping::A);
557 } else if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
558 // Also try to legacy diffuse texture as an alternative
559 QSSGSceneDesc::setProperty(target, "baseColorMap", &QQuick3DPrincipledMaterial::setBaseColorMap, diffuseMapTexture);
560 }
561
562 if (auto metalicRoughnessTexture = createTextureNode(source, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE)) {
563 QSSGSceneDesc::setProperty(target, "metalnessMap", &QQuick3DPrincipledMaterial::setMetalnessMap, metalicRoughnessTexture);
564 QSSGSceneDesc::setProperty(target, "metalnessChannel", &QQuick3DPrincipledMaterial::setMetalnessChannel, QQuick3DPrincipledMaterial::TextureChannelMapping::B);
565 QSSGSceneDesc::setProperty(target, "roughnessMap", &QQuick3DPrincipledMaterial::setRoughnessMap, metalicRoughnessTexture);
566 QSSGSceneDesc::setProperty(target, "roughnessChannel", &QQuick3DPrincipledMaterial::setRoughnessChannel, QQuick3DPrincipledMaterial::TextureChannelMapping::G);
567 }
568
569 {
570 ai_real metallicFactor;
571 result = source.Get(AI_MATKEY_METALLIC_FACTOR, metallicFactor);
572 if (result == aiReturn_SUCCESS)
573 QSSGSceneDesc::setProperty(target, "metalness", &QQuick3DPrincipledMaterial::setMetalness, float(metallicFactor));
574 }
575
576 {
577 ai_real roughnessFactor;
578 result = source.Get(AI_MATKEY_ROUGHNESS_FACTOR, roughnessFactor);
579 if (result == aiReturn_SUCCESS)
580 QSSGSceneDesc::setProperty(target, "roughness", &QQuick3DPrincipledMaterial::setRoughness, float(roughnessFactor));
581 }
582
583 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0)) {
584 QSSGSceneDesc::setProperty(target, "normalMap", &QQuick3DPrincipledMaterial::setNormalMap, normalTexture);
585 {
586 ai_real normalScale;
587 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
588 if (result == aiReturn_SUCCESS)
589 QSSGSceneDesc::setProperty(target, "normalStrength", &QQuick3DPrincipledMaterial::setNormalStrength, float(normalScale));
590 }
591 }
592
593 // Occlusion Textures are not implimented (yet)
594 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
595 QSSGSceneDesc::setProperty(target, "occlusionMap", &QQuick3DPrincipledMaterial::setOcclusionMap, occlusionTexture);
596 QSSGSceneDesc::setProperty(target, "occlusionChannel", &QQuick3DPrincipledMaterial::setOcclusionChannel, QQuick3DPrincipledMaterial::TextureChannelMapping::R);
597 {
598 ai_real occlusionAmount;
599 result = source.Get(AI_MATKEY_GLTF_TEXTURE_STRENGTH(aiTextureType_LIGHTMAP, 0), occlusionAmount);
600 if (result == aiReturn_SUCCESS)
601 QSSGSceneDesc::setProperty(target, "occlusionAmount", &QQuick3DPrincipledMaterial::setOcclusionAmount, float(occlusionAmount));
602 }
603 }
604
605 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
606 QSSGSceneDesc::setProperty(target, "emissiveMap", &QQuick3DPrincipledMaterial::setEmissiveMap, emissiveTexture);
607
608 {
609 aiColor3D emissiveColorFactor;
610 result = source.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColorFactor);
611 if (result == aiReturn_SUCCESS)
612 QSSGSceneDesc::setProperty(target, "emissiveFactor", &QQuick3DPrincipledMaterial::setEmissiveFactor, QVector3D { emissiveColorFactor.r, emissiveColorFactor.g, emissiveColorFactor.b });
613 }
614
615 {
616 bool isDoubleSided;
617 result = source.Get(AI_MATKEY_TWOSIDED, isDoubleSided);
618 if (result == aiReturn_SUCCESS && isDoubleSided)
619 QSSGSceneDesc::setProperty(target, "cullMode", &QQuick3DPrincipledMaterial::setCullMode, QQuick3DPrincipledMaterial::CullMode::NoCulling);
620 }
621
622 {
623 aiString alphaMode;
624 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
625 if (result == aiReturn_SUCCESS) {
626 auto mode = QQuick3DPrincipledMaterial::AlphaMode::Default;
627 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
628 mode = QQuick3DPrincipledMaterial::AlphaMode::Opaque;
629 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
630 mode = QQuick3DPrincipledMaterial::AlphaMode::Mask;
631 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
632 mode = QQuick3DPrincipledMaterial::AlphaMode::Blend;
633
634 if (mode != QQuick3DPrincipledMaterial::AlphaMode::Default) {
635 QSSGSceneDesc::setProperty(target, "alphaMode", &QQuick3DPrincipledMaterial::setAlphaMode, mode);
636 // If the mode is mask, we also need to force OpaquePrePassDepthDraw mode
637 if (mode == QQuick3DPrincipledMaterial::AlphaMode::Mask)
638 QSSGSceneDesc::setProperty(target, "depthDrawMode", &QQuick3DPrincipledMaterial::setDepthDrawMode, QQuick3DMaterial::OpaquePrePassDepthDraw);
639 }
640 }
641 }
642
643 {
644 ai_real alphaCutoff;
645 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
646 if (result == aiReturn_SUCCESS)
647 QSSGSceneDesc::setProperty(target, "alphaCutoff", &QQuick3DPrincipledMaterial::setAlphaCutoff, float(alphaCutoff));
648 }
649
650 {
651 int shadingModel = 0;
652 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
653 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
654 QSSGSceneDesc::setProperty(target, "lighting", &QQuick3DPrincipledMaterial::setLighting, QQuick3DPrincipledMaterial::Lighting::NoLighting);
655 }
656
657
658 {
659 // Clearcoat Properties (KHR_materials_clearcoat)
660 // factor
661 {
662 ai_real clearcoatFactor = 0.0f;
663 result = source.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoatFactor);
664 if (result == aiReturn_SUCCESS)
665 QSSGSceneDesc::setProperty(target,
666 "clearcoatAmount",
667 &QQuick3DPrincipledMaterial::setClearcoatAmount,
668 float(clearcoatFactor));
669 }
670
671 // roughness
672 {
673 ai_real clearcoatRoughnessFactor = 0.0f;
674 result = source.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoatRoughnessFactor);
675 if (result == aiReturn_SUCCESS)
676 QSSGSceneDesc::setProperty(target,
677 "clearcoatRoughnessAmount",
678 &QQuick3DPrincipledMaterial::setClearcoatRoughnessAmount,
679 float(clearcoatRoughnessFactor));
680 }
681
682 // texture
683 if (auto clearcoatTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_TEXTURE))
684 QSSGSceneDesc::setProperty(target, "clearcoatMap", &QQuick3DPrincipledMaterial::setClearcoatMap, clearcoatTexture);
685
686 // roughness texture
687 if (auto clearcoatRoughnessTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE))
688 QSSGSceneDesc::setProperty(target,
689 "clearcoatRoughnessMap",
690 &QQuick3DPrincipledMaterial::setClearcoatRoughnessMap,
691 clearcoatRoughnessTexture);
692
693 // normal texture
694 if (auto clearcoatNormalTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE))
695 QSSGSceneDesc::setProperty(target, "clearcoatNormalMap", &QQuick3DPrincipledMaterial::setClearcoatNormalMap, clearcoatNormalTexture);
696 }
697
698 {
699 // Transmission Properties (KHR_materials_transmission)
700 // factor
701 {
702 ai_real transmissionFactor = 0.0f;
703 result = source.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmissionFactor);
704 if (result == aiReturn_SUCCESS)
705 QSSGSceneDesc::setProperty(target,
706 "transmissionFactor",
707 &QQuick3DPrincipledMaterial::setTransmissionFactor,
708 float(transmissionFactor));
709 }
710
711 // texture
712 {
713 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
714 QSSGSceneDesc::setProperty(target,
715 "transmissionMap",
716 &QQuick3DPrincipledMaterial::setTransmissionMap,
717 transmissionImage);
718 }
719
720 }
721
722 {
723 // Volume Properties (KHR_materials_volume) [only used with transmission]
724 // thicknessFactor
725 {
726 ai_real thicknessFactor = 0.0f;
727 result = source.Get(AI_MATKEY_VOLUME_THICKNESS_FACTOR, thicknessFactor);
728 if (result == aiReturn_SUCCESS)
729 QSSGSceneDesc::setProperty(target, "thicknessFactor", &QQuick3DPrincipledMaterial::setThicknessFactor, float(thicknessFactor));
730 }
731
732 // thicknessMap
733 {
734 if (auto thicknessImage = createTextureNode(source, AI_MATKEY_VOLUME_THICKNESS_TEXTURE))
735 QSSGSceneDesc::setProperty(target, "thicknessMap", &QQuick3DPrincipledMaterial::setThicknessMap, thicknessImage);
736 }
737
738 // attenuationDistance
739 {
740 ai_real attenuationDistance = 0.0f;
741 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuationDistance);
742 if (result == aiReturn_SUCCESS)
743 QSSGSceneDesc::setProperty(target,
744 "attenuationDistance",
745 &QQuick3DPrincipledMaterial::setAttenuationDistance,
746 float(attenuationDistance));
747 }
748
749 // attenuationColor
750 {
751 aiColor3D attenuationColor;
752 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_COLOR, attenuationColor);
753 if (result == aiReturn_SUCCESS)
754 QSSGSceneDesc::setProperty(target,
755 "attenuationColor",
756 &QQuick3DPrincipledMaterial::setAttenuationColor,
757 aiColorToQColor(attenuationColor));
758 }
759 }
760
761
762 // KHR_materials_ior
763 {
764 ai_real ior = 0.0f;
765 result = source.Get(AI_MATKEY_REFRACTI, ior);
766 if (result == aiReturn_SUCCESS)
767 QSSGSceneDesc::setProperty(target,
768 "indexOfRefraction",
769 &QQuick3DPrincipledMaterial::setIndexOfRefraction,
770 float(ior));
771 }
772
773 {
774 // opacity AI_MATKEY_OPACITY
775 ai_real opacity = 1.0f;
776 result = source.Get(AI_MATKEY_OPACITY, opacity);
777 if (result == aiReturn_SUCCESS)
778 QSSGSceneDesc::setProperty(target, "opacity", &QQuick3DPrincipledMaterial::setOpacity, float(opacity));
779
780 // opacityMap aiTextureType_OPACITY 0
781 if (auto opacityTexture = createTextureNode(source, aiTextureType_OPACITY, 0))
782 QSSGSceneDesc::setProperty(target, "opacityMap", &QQuick3DPrincipledMaterial::setOpacityMap, opacityTexture);
783 }
784
785 } else if (type == QSSGSceneDesc::Material::RuntimeType::DefaultMaterial) { // Ver1
786 int shadingModel = 0;
787 auto material = &source;
788 result = material->Get(AI_MATKEY_SHADING_MODEL, shadingModel);
789 // lighting
790 if (result == aiReturn_SUCCESS && (shadingModel == aiShadingMode_NoShading))
791 QSSGSceneDesc::setProperty(target, "lighting", &QQuick3DDefaultMaterial::setLighting, QQuick3DDefaultMaterial::Lighting::NoLighting);
792
793 if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
794 QSSGSceneDesc::setProperty(target, "diffuseMap", &QQuick3DDefaultMaterial::setDiffuseMap, diffuseMapTexture);
795 } else {
796 // For some reason the normal behavior is that either you have a diffuseMap[s] or a diffuse color
797 // but no a mix of both... So only set the diffuse color if none of the diffuse maps are set:
798 aiColor3D diffuseColor;
799 result = material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
800 if (result == aiReturn_SUCCESS)
801 QSSGSceneDesc::setProperty(target, "diffuseColor", &QQuick3DDefaultMaterial::setDiffuseColor, aiColorToQColor(diffuseColor));
802 }
803
804 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
805 QSSGSceneDesc::setProperty(target, "emissiveMap", &QQuick3DDefaultMaterial::setEmissiveMap, emissiveTexture);
806
807 // specularReflectionMap
808 if (auto specularTexture = createTextureNode(source, aiTextureType_SPECULAR, 0))
809 QSSGSceneDesc::setProperty(target, "specularMap", &QQuick3DDefaultMaterial::setSpecularMap, specularTexture);
810
811 // opacity AI_MATKEY_OPACITY
812 ai_real opacity;
813 result = material->Get(AI_MATKEY_OPACITY, opacity);
814 if (result == aiReturn_SUCCESS)
815 QSSGSceneDesc::setProperty(target, "opacity", &QQuick3DDefaultMaterial::setOpacity, float(opacity));
816
817 // opacityMap aiTextureType_OPACITY 0
818 if (auto opacityTexture = createTextureNode(source, aiTextureType_OPACITY, 0))
819 QSSGSceneDesc::setProperty(target, "opacityMap", &QQuick3DDefaultMaterial::setOpacityMap, opacityTexture);
820
821 // bumpMap aiTextureType_HEIGHT 0
822 if (auto bumpTexture = createTextureNode(source, aiTextureType_HEIGHT, 0)) {
823 QSSGSceneDesc::setProperty(target, "bumpMap", &QQuick3DDefaultMaterial::setBumpMap, bumpTexture);
824 // bumpAmount AI_MATKEY_BUMPSCALING
825 ai_real bumpAmount;
826 result = material->Get(AI_MATKEY_BUMPSCALING, bumpAmount);
827 if (result == aiReturn_SUCCESS)
828 QSSGSceneDesc::setProperty(target, "bumpAmount", &QQuick3DDefaultMaterial::setBumpAmount, float(bumpAmount));
829 }
830
831 // normalMap aiTextureType_NORMALS 0
832 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0))
833 QSSGSceneDesc::setProperty(target, "normalMap", &QQuick3DDefaultMaterial::setNormalMap, normalTexture);
834 } else if (type == QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial) {
835 {
836 aiColor4D albedoFactor;
837 result = source.Get(AI_MATKEY_COLOR_DIFFUSE, albedoFactor);
838 if (result == aiReturn_SUCCESS)
839 QSSGSceneDesc::setProperty(target, "albedoColor", &QQuick3DSpecularGlossyMaterial::setAlbedoColor, aiColorToQColor(albedoFactor));
840 }
841
842 if (auto albedoTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
843 QSSGSceneDesc::setProperty(target, "albedoMap", &QQuick3DSpecularGlossyMaterial::setAlbedoMap, albedoTexture);
844 QSSGSceneDesc::setProperty(target, "opacityChannel", &QQuick3DSpecularGlossyMaterial::setOpacityChannel, QQuick3DSpecularGlossyMaterial::TextureChannelMapping::A);
845 }
846
847 if (auto specularGlossinessTexture = createTextureNode(source, aiTextureType_SPECULAR, 0)) {
848 QSSGSceneDesc::setProperty(target, "specularMap", &QQuick3DSpecularGlossyMaterial::setSpecularMap, specularGlossinessTexture);
849 QSSGSceneDesc::setProperty(target, "glossinessMap", &QQuick3DSpecularGlossyMaterial::setGlossinessMap, specularGlossinessTexture);
850 QSSGSceneDesc::setProperty(target, "glossinessChannel", &QQuick3DSpecularGlossyMaterial::setGlossinessChannel, QQuick3DSpecularGlossyMaterial::TextureChannelMapping::A);
851 }
852
853 {
854 aiColor4D specularColorFactor;
855 result = source.Get(AI_MATKEY_COLOR_SPECULAR, specularColorFactor);
856 if (result == aiReturn_SUCCESS)
857 QSSGSceneDesc::setProperty(target, "specularColor", &QQuick3DSpecularGlossyMaterial::setSpecularColor, aiColorToQColor(specularColorFactor));
858 }
859
860 {
861 ai_real glossinessFactor;
862 result = source.Get(AI_MATKEY_GLOSSINESS_FACTOR, glossinessFactor);
863 if (result == aiReturn_SUCCESS)
864 QSSGSceneDesc::setProperty(target, "glossiness", &QQuick3DSpecularGlossyMaterial::setGlossiness, float(glossinessFactor));
865 }
866
867 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0)) {
868 QSSGSceneDesc::setProperty(target, "normalMap", &QQuick3DSpecularGlossyMaterial::setNormalMap, normalTexture);
869 {
870 ai_real normalScale;
871 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
872 if (result == aiReturn_SUCCESS)
873 QSSGSceneDesc::setProperty(target, "normalStrength", &QQuick3DSpecularGlossyMaterial::setNormalStrength, float(normalScale));
874 }
875 }
876
877 // Occlusion Textures are not implimented (yet)
878 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
879 QSSGSceneDesc::setProperty(target, "occlusionMap", &QQuick3DSpecularGlossyMaterial::setOcclusionMap, occlusionTexture);
880 QSSGSceneDesc::setProperty(target, "occlusionChannel", &QQuick3DSpecularGlossyMaterial::setOcclusionChannel, QQuick3DSpecularGlossyMaterial::TextureChannelMapping::R);
881 {
882 ai_real occlusionAmount;
883 result = source.Get(AI_MATKEY_GLTF_TEXTURE_STRENGTH(aiTextureType_LIGHTMAP, 0), occlusionAmount);
884 if (result == aiReturn_SUCCESS)
885 QSSGSceneDesc::setProperty(target, "occlusionAmount", &QQuick3DSpecularGlossyMaterial::setOcclusionAmount, float(occlusionAmount));
886 }
887 }
888
889 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
890 QSSGSceneDesc::setProperty(target, "emissiveMap", &QQuick3DSpecularGlossyMaterial::setEmissiveMap, emissiveTexture);
891
892 {
893 aiColor3D emissiveColorFactor;
894 result = source.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColorFactor);
895 if (result == aiReturn_SUCCESS)
896 QSSGSceneDesc::setProperty(target, "emissiveFactor", &QQuick3DSpecularGlossyMaterial::setEmissiveFactor, QVector3D { emissiveColorFactor.r, emissiveColorFactor.g, emissiveColorFactor.b });
897 }
898
899 {
900 bool isDoubleSided;
901 result = source.Get(AI_MATKEY_TWOSIDED, isDoubleSided);
902 if (result == aiReturn_SUCCESS && isDoubleSided)
903 QSSGSceneDesc::setProperty(target, "cullMode", &QQuick3DSpecularGlossyMaterial::setCullMode, QQuick3DSpecularGlossyMaterial::CullMode::NoCulling);
904 }
905
906 {
907 aiString alphaMode;
908 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
909 if (result == aiReturn_SUCCESS) {
910 auto mode = QQuick3DSpecularGlossyMaterial::AlphaMode::Default;
911 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
912 mode = QQuick3DSpecularGlossyMaterial::AlphaMode::Opaque;
913 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
914 mode = QQuick3DSpecularGlossyMaterial::AlphaMode::Mask;
915 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
916 mode = QQuick3DSpecularGlossyMaterial::AlphaMode::Blend;
917
918 if (mode != QQuick3DSpecularGlossyMaterial::AlphaMode::Default) {
919 QSSGSceneDesc::setProperty(target, "alphaMode", &QQuick3DSpecularGlossyMaterial::setAlphaMode, mode);
920 // If the mode is mask, we also need to force OpaquePrePassDepthDraw mode
921 if (mode == QQuick3DSpecularGlossyMaterial::AlphaMode::Mask)
922 QSSGSceneDesc::setProperty(target, "depthDrawMode", &QQuick3DSpecularGlossyMaterial::setDepthDrawMode, QQuick3DMaterial::OpaquePrePassDepthDraw);
923 }
924 }
925 }
926
927 {
928 ai_real alphaCutoff;
929 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
930 if (result == aiReturn_SUCCESS)
931 QSSGSceneDesc::setProperty(target, "alphaCutoff", &QQuick3DSpecularGlossyMaterial::setAlphaCutoff, float(alphaCutoff));
932 }
933
934 {
935 int shadingModel = 0;
936 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
937 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
938 QSSGSceneDesc::setProperty(target, "lighting", &QQuick3DSpecularGlossyMaterial::setLighting, QQuick3DSpecularGlossyMaterial::Lighting::NoLighting);
939 }
940
941
942 {
943 // Clearcoat Properties (KHR_materials_clearcoat)
944 // factor
945 {
946 ai_real clearcoatFactor = 0.0f;
947 result = source.Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoatFactor);
948 if (result == aiReturn_SUCCESS)
949 QSSGSceneDesc::setProperty(target,
950 "clearcoatAmount",
951 &QQuick3DSpecularGlossyMaterial::setClearcoatAmount,
952 float(clearcoatFactor));
953 }
954
955 // roughness
956 {
957 ai_real clearcoatRoughnessFactor = 0.0f;
958 result = source.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoatRoughnessFactor);
959 if (result == aiReturn_SUCCESS)
960 QSSGSceneDesc::setProperty(target,
961 "clearcoatRoughnessAmount",
962 &QQuick3DSpecularGlossyMaterial::setClearcoatRoughnessAmount,
963 float(clearcoatRoughnessFactor));
964 }
965
966 // texture
967 if (auto clearcoatTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_TEXTURE))
968 QSSGSceneDesc::setProperty(target, "clearcoatMap", &QQuick3DSpecularGlossyMaterial::setClearcoatMap, clearcoatTexture);
969
970 // roughness texture
971 if (auto clearcoatRoughnessTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE))
972 QSSGSceneDesc::setProperty(target,
973 "clearcoatRoughnessMap",
974 &QQuick3DSpecularGlossyMaterial::setClearcoatRoughnessMap,
975 clearcoatRoughnessTexture);
976
977 // normal texture
978 if (auto clearcoatNormalTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE)) {
979 QSSGSceneDesc::setProperty(target, "clearcoatNormalMap", &QQuick3DSpecularGlossyMaterial::setClearcoatNormalMap, clearcoatNormalTexture);
980
981 ai_real clearcoatNormalStrength = 0.0f;
982 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_CLEARCOAT, 2), clearcoatNormalTexture);
983 if (result == aiReturn_SUCCESS)
984 QSSGSceneDesc::setProperty(target, "clearcoatNormalStrength", &QQuick3DPrincipledMaterial::setClearcoatNormalStrength, float(clearcoatNormalStrength));
985 }
986 }
987
988 {
989 // Transmission Properties (KHR_materials_transmission)
990 // factor
991 {
992 ai_real transmissionFactor = 0.0f;
993 result = source.Get(AI_MATKEY_TRANSMISSION_FACTOR, transmissionFactor);
994 if (result == aiReturn_SUCCESS)
995 QSSGSceneDesc::setProperty(target,
996 "transmissionFactor",
997 &QQuick3DSpecularGlossyMaterial::setTransmissionFactor,
998 float(transmissionFactor));
999 }
1000
1001 // texture
1002 {
1003 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
1004 QSSGSceneDesc::setProperty(target,
1005 "transmissionMap",
1006 &QQuick3DSpecularGlossyMaterial::setTransmissionMap,
1007 transmissionImage);
1008 }
1009
1010 }
1011
1012 {
1013 // Volume Properties (KHR_materials_volume) [only used with transmission]
1014 // thicknessFactor
1015 {
1016 ai_real thicknessFactor = 0.0f;
1017 result = source.Get(AI_MATKEY_VOLUME_THICKNESS_FACTOR, thicknessFactor);
1018 if (result == aiReturn_SUCCESS)
1019 QSSGSceneDesc::setProperty(target, "thicknessFactor", &QQuick3DSpecularGlossyMaterial::setThicknessFactor, float(thicknessFactor));
1020 }
1021
1022 // thicknessMap
1023 {
1024 if (auto thicknessImage = createTextureNode(source, AI_MATKEY_VOLUME_THICKNESS_TEXTURE))
1025 QSSGSceneDesc::setProperty(target, "thicknessMap", &QQuick3DSpecularGlossyMaterial::setThicknessMap, thicknessImage);
1026 }
1027
1028 // attenuationDistance
1029 {
1030 ai_real attenuationDistance = 0.0f;
1031 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuationDistance);
1032 if (result == aiReturn_SUCCESS)
1033 QSSGSceneDesc::setProperty(target,
1034 "attenuationDistance",
1035 &QQuick3DSpecularGlossyMaterial::setAttenuationDistance,
1036 float(attenuationDistance));
1037 }
1038
1039 // attenuationColor
1040 {
1041 aiColor3D attenuationColor;
1042 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_COLOR, attenuationColor);
1043 if (result == aiReturn_SUCCESS)
1044 QSSGSceneDesc::setProperty(target,
1045 "attenuationColor",
1046 &QQuick3DSpecularGlossyMaterial::setAttenuationColor,
1047 aiColorToQColor(attenuationColor));
1048 }
1049 }
1050 }
1051}
1052
1053static void setCameraProperties(QSSGSceneDesc::Camera &target, const aiCamera &source, const aiNode &sourceNode, const SceneInfo &sceneInfo)
1054{
1055 using namespace QSSGSceneDesc;
1056
1057 // assimp does not have a camera type but it works for gltf2 format.
1058 target.runtimeType = (source.mHorizontalFOV == 0.0f) ? Node::RuntimeType::OrthographicCamera
1059 : Node::RuntimeType::PerspectiveCamera;
1060
1061 // We assume these default forward and up vectors, so if this isn't
1062 // the case we have to do additional transform
1063 aiMatrix4x4 correctionMatrix;
1064 bool needsCorrection = false;
1065
1066 // Workaround For FBX,
1067 // assimp has a problem to set properties, mLookAt ans mUp
1068 // and it takes too much time for correction.
1069 // Quick3D will ignore these value and just use
1070 // the initial differences between FBX and Quick3D.
1071 if (sceneInfo.opt.fbxMode) {
1072 aiMatrix4x4::RotationY(ai_real(M_PI / 2), correctionMatrix);
1073 needsCorrection = true;
1074 } else {
1075 aiVector3D upQuick3D = aiVector3D(0, 1, 0);
1076 if (source.mLookAt != aiVector3D(0, 0, -1)) {
1077 aiMatrix4x4 lookAtCorrection;
1078 aiMatrix4x4::FromToMatrix(aiVector3D(0, 0, -1), source.mLookAt, lookAtCorrection);
1079 correctionMatrix *= lookAtCorrection;
1080 needsCorrection = true;
1081 upQuick3D *= lookAtCorrection;
1082 }
1083 if (source.mUp != upQuick3D) {
1084 aiMatrix4x4 upCorrection;
1085 aiMatrix4x4::FromToMatrix(upQuick3D, source.mUp, upCorrection);
1086 correctionMatrix = upCorrection * correctionMatrix;
1087 needsCorrection = true;
1088 }
1089 }
1090
1091 setNodeProperties(target, sourceNode, sceneInfo, needsCorrection ? &correctionMatrix : nullptr);
1092
1093 // clipNear and clipFar
1094 if (target.runtimeType == Node::RuntimeType::PerspectiveCamera) {
1095 setProperty(target, "clipNear", &QQuick3DPerspectiveCamera::setClipNear, source.mClipPlaneNear);
1096 setProperty(target, "clipFar", &QQuick3DPerspectiveCamera::setClipFar, source.mClipPlaneFar);
1097 } else { //OrthographicCamera
1098 setProperty(target, "clipNear", &QQuick3DOrthographicCamera::setClipNear, source.mClipPlaneNear);
1099 setProperty(target, "clipFar", &QQuick3DOrthographicCamera::setClipFar, source.mClipPlaneFar);
1100 }
1101
1102 if (target.runtimeType == Node::RuntimeType::PerspectiveCamera) {
1103 // fieldOfView
1104 // mHorizontalFOV is defined as a half horizontal fov
1105 // in the assimp header but it seems not half now.
1106 const float fov = qRadiansToDegrees(source.mHorizontalFOV);
1107 setProperty(target, "fieldOfView", &QQuick3DPerspectiveCamera::setFieldOfView, fov);
1108
1109 // isFieldOfViewHorizontal
1110 setProperty(target, "fieldOfViewOrientation", &QQuick3DPerspectiveCamera::setFieldOfViewOrientation,
1111 QQuick3DPerspectiveCamera::FieldOfViewOrientation::Horizontal);
1112 } else { //OrthographicCamera
1113 const float width = source.mOrthographicWidth * 2.0f;
1114 const float height = width / source.mAspect;
1115 setProperty(target, "horizontalMagnification", &QQuick3DOrthographicCamera::setHorizontalMagnification, width);
1116 setProperty(target, "verticalMagnification", &QQuick3DOrthographicCamera::setVerticalMagnification, height);
1117 }
1118 // projectionMode
1119
1120 // scaleMode
1121
1122 // scaleAnchor
1123
1124 // frustomScaleX
1125
1126 // frustomScaleY
1127}
1128
1129static void setLightProperties(QSSGSceneDesc::Light &target, const aiLight &source, const aiNode &sourceNode, const SceneInfo &sceneInfo)
1130{
1131 // We assume that the direction vector for a light is (0, 0, -1)
1132 // so if the direction vector is non-null, but not (0, 0, -1) we
1133 // need to correct the translation
1134 aiMatrix4x4 correctionMatrix;
1135 bool needsCorrection = false;
1136 if (source.mDirection != aiVector3D(0, 0, 0)) {
1137 if (source.mDirection != aiVector3D(0, 0, -1)) {
1138 aiMatrix4x4::FromToMatrix(aiVector3D(0, 0, -1), source.mDirection, correctionMatrix);
1139 needsCorrection = true;
1140 }
1141 }
1142
1143 // lightType
1144 static const auto asQtLightType = [](aiLightSourceType type) {
1145 switch (type) {
1146 case aiLightSource_AMBIENT:
1147 Q_FALLTHROUGH();
1148 case aiLightSource_DIRECTIONAL:
1149 return QSSGSceneDesc::Light::RuntimeType::DirectionalLight;
1150 case aiLightSource_POINT:
1151 return QSSGSceneDesc::Light::RuntimeType::PointLight;
1152 case aiLightSource_SPOT:
1153 return QSSGSceneDesc::Light::RuntimeType::SpotLight;
1154 default:
1155 return QSSGSceneDesc::Light::RuntimeType::PointLight;
1156 }
1157 };
1158
1159 target.runtimeType = asQtLightType(source.mType);
1160
1161 setNodeProperties(target, sourceNode, sceneInfo, needsCorrection ? &correctionMatrix : nullptr);
1162
1163 // brightness
1164 // Assimp has no property related to brightness or intensity.
1165 // They are multiplied to diffuse, ambient and specular colors.
1166 // For extracting the property value, we will check the maximum value of them.
1167 // (In most cases, Assimp uses the same specular values with diffuse values,
1168 // so we will compare just components of the diffuse and the ambient)
1169 float brightness = qMax(qMax(1.0f, source.mColorDiffuse.r),
1170 qMax(source.mColorDiffuse.g, source.mColorDiffuse.b));
1171
1172 // ambientColor
1173 if (source.mType == aiLightSource_AMBIENT) {
1174 brightness = qMax(qMax(brightness, source.mColorAmbient.r),
1175 qMax(source.mColorAmbient.g, source.mColorAmbient.b));
1176
1177 // We only want ambient light color if it is explicit
1178 const QColor ambientColor = QColor::fromRgbF(source.mColorAmbient.r / brightness,
1179 source.mColorAmbient.g / brightness,
1180 source.mColorAmbient.b / brightness);
1181 QSSGSceneDesc::setProperty(target, "ambientColor", &QQuick3DAbstractLight::setAmbientColor, ambientColor);
1182 }
1183
1184 // diffuseColor
1185 const QColor diffuseColor = QColor::fromRgbF(source.mColorDiffuse.r / brightness,
1186 source.mColorDiffuse.g / brightness,
1187 source.mColorDiffuse.b / brightness);
1188 QSSGSceneDesc::setProperty(target, "color", &QQuick3DAbstractLight::setColor, diffuseColor);
1189
1190 // describe brightness here
1191 QSSGSceneDesc::setProperty(target, "brightness", &QQuick3DAbstractLight::setBrightness, brightness);
1192
1193 const bool isSpot = (source.mType == aiLightSource_SPOT);
1194 if (source.mType == aiLightSource_POINT || isSpot) {
1195 // constantFade
1196 // Some assets have this constant attenuation value as 0.0f and it makes light attenuation makes infinite at distance 0.
1197 // In that case, we will use the default constant attenuation, 1.0f.
1198 const bool hasAttConstant = !qFuzzyIsNull(source.mAttenuationConstant);
1199
1200 if (isSpot) {
1201 if (hasAttConstant)
1202 QSSGSceneDesc::setProperty(target, "constantFade", &QQuick3DSpotLight::setConstantFade, source.mAttenuationConstant);
1203 QSSGSceneDesc::setProperty(target, "linearFade", &QQuick3DSpotLight::setLinearFade, source.mAttenuationLinear * 100.0f);
1204 QSSGSceneDesc::setProperty(target, "quadraticFade", &QQuick3DSpotLight::setQuadraticFade, source.mAttenuationQuadratic * 10000.0f);
1205 QSSGSceneDesc::setProperty(target, "coneAngle", &QQuick3DSpotLight::setConeAngle, qRadiansToDegrees(source.mAngleOuterCone) * 2.0f);
1206 QSSGSceneDesc::setProperty(target, "innerConeAngle", &QQuick3DSpotLight::setInnerConeAngle, qRadiansToDegrees(source.mAngleInnerCone) * 2.0f);
1207 } else {
1208 if (hasAttConstant)
1209 QSSGSceneDesc::setProperty(target, "constantFade", &QQuick3DPointLight::setConstantFade, source.mAttenuationConstant);
1210 QSSGSceneDesc::setProperty(target, "linearFade", &QQuick3DPointLight::setLinearFade, source.mAttenuationLinear * 100.0f);
1211 QSSGSceneDesc::setProperty(target, "quadraticFade", &QQuick3DPointLight::setQuadraticFade, source.mAttenuationQuadratic * 10000.0f);
1212 }
1213 }
1214 // castShadow
1215
1216 // shadowBias
1217
1218 // shadowFactor
1219
1220 // shadowMapResolution
1221
1222 // shadowMapFar
1223
1224 // shadowMapFieldOfView
1225
1226 // shadowFilter
1227}
1228
1229using MorphAttributes = QQuick3DMorphTarget::MorphTargetAttributes;
1235
1236static void setModelProperties(QSSGSceneDesc::Model &target, const aiNode &source, const SceneInfo &sceneInfo)
1237{
1238 if (source.mNumMeshes == 0)
1239 return;
1240
1241 auto &targetScene = target.scene;
1242 const auto &srcScene = sceneInfo.scene;
1243 // TODO: Correction and scale
1244 setNodeProperties(target, source, sceneInfo, nullptr);
1245
1246 auto &meshStorage = targetScene->meshStorage;
1247 auto &materialMap = sceneInfo.materialMap;
1248 auto &meshMap = sceneInfo.meshMap;
1249 auto &skinMap = sceneInfo.skinMap;
1250 auto &mesh2skin = sceneInfo.mesh2skin;
1251
1252 QVarLengthArray<QSSGSceneDesc::Material *> materials;
1253 materials.reserve(source.mNumMeshes); // Assumig there's max one material per mesh.
1254
1255 QString errorString;
1256
1257 const auto ensureMaterial = [&](qsizetype materialIndex) {
1258 // Get the material for the mesh
1259 auto &material = materialMap[materialIndex];
1260 // Check if we need to create a new scene node for this material
1261 auto targetMat = material.second;
1262 if (targetMat == nullptr) {
1263 const aiMaterial *sourceMat = material.first;
1264
1265 auto currentMaterialType = QSSGSceneDesc::Material::RuntimeType::PrincipledMaterial;
1266 ai_real glossinessFactor;
1267 aiReturn result = sourceMat->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossinessFactor);
1268 if (result == aiReturn_SUCCESS)
1269 currentMaterialType = QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial;
1270
1271 targetMat = new QSSGSceneDesc::Material(currentMaterialType);
1272 QSSGSceneDesc::addNode(target, *targetMat);
1273 setMaterialProperties(*targetMat, *sourceMat, sceneInfo, currentMaterialType);
1274 material.second = targetMat;
1275 }
1276
1277 Q_ASSERT(targetMat != nullptr && material.second != nullptr);
1278 // If these don't match then somethings broken...
1279 Q_ASSERT(srcScene.mMaterials[materialIndex] == material.first);
1280 materials.push_back(targetMat);
1281 };
1282
1283 AssimpUtils::MeshList meshes;
1284 qint16 skinIdx = -1;
1285 // Combine all the meshes referenced by this model into a single MultiMesh file
1286 // For the morphing, the target mesh must have the same AnimMeshes.
1287 // It means if only one mesh has a morphing animation, the other sub-meshes will
1288 // get null target attributes. However this case might not be common.
1289 // These submeshes will animate with the same morphing weight!
1290
1291 // If meshes have separate skins, they should not be combined. GLTF2 does not
1292 // seem to have problems related with this case, but When we use runtime asset
1293 // for other formats, this case must be checked again.
1294 // Here, we will use only the first skin in the mesh list
1295 const auto combineMeshes = [&](const aiNode &source, aiMesh **sceneMeshes) {
1296 for (qsizetype i = 0, end = source.mNumMeshes; i != end; ++i) {
1297 const aiMesh &mesh = *sceneMeshes[source.mMeshes[i]];
1298 ensureMaterial(mesh.mMaterialIndex);
1299 if (skinIdx == -1 && mesh.HasBones())
1300 skinIdx = mesh2skin[source.mMeshes[i]];
1301 meshes.push_back(&mesh);
1302 }
1303 };
1304
1305 const auto createMeshNode = [&](const aiString &name) {
1306 auto meshData = AssimpUtils::generateMeshData(srcScene,
1307 meshes,
1309 sceneInfo.opt.generateMeshLODs,
1310 sceneInfo.opt.lodNormalMergeAngle,
1311 sceneInfo.opt.lodNormalSplitAngle,
1312 errorString);
1313 meshStorage.push_back(std::move(meshData));
1314
1315 const auto idx = meshStorage.size() - 1;
1316 // For multimeshes we'll use the model name, but for single meshes we'll use the mesh name.
1317 return new QSSGSceneDesc::Mesh(fromAiString(name), idx);
1318 };
1319
1320 QSSGSceneDesc::Mesh *meshNode = nullptr;
1321
1322 const bool isMultiMesh = (source.mNumMeshes > 1);
1323 if (isMultiMesh) {
1324 // result is stored in 'meshes'
1325 combineMeshes(source, srcScene.mMeshes);
1326 Q_ASSERT(!meshes.isEmpty());
1327 meshNode = createMeshNode(source.mName);
1328 QSSGSceneDesc::addNode(target, *meshNode);
1329 } else { // single mesh (We shouldn't be here if there are no meshes...)
1330 Q_ASSERT(source.mNumMeshes == 1);
1331 auto &mesh = meshMap[*source.mMeshes];
1332 meshNode = mesh.second;
1333 if (meshNode == nullptr) {
1334 meshes = {mesh.first};
1335 if (mesh.first->HasBones())
1336 skinIdx = mesh2skin[*source.mMeshes];
1337 mesh.second = meshNode = createMeshNode(mesh.first->mName);
1338 QSSGSceneDesc::addNode(target, *meshNode); // We only add this the first time we create it.
1339 }
1340 ensureMaterial(mesh.first->mMaterialIndex);
1341 Q_ASSERT(meshNode != nullptr && mesh.second != nullptr);
1342 }
1343
1344 if (meshNode)
1345 QSSGSceneDesc::setProperty(target, "source", &QQuick3DModel::setSource, QVariant::fromValue(meshNode));
1346
1347 if (skinIdx != -1) {
1348 auto &skin = skinMap[skinIdx];
1349 skin.node = new QSSGSceneDesc::Skin;
1350 QSSGSceneDesc::setProperty(target, "skin", &QQuick3DModel::setSkin, skin.node);
1351 QSSGSceneDesc::addNode(target, *skin.node);
1352 // Skins' properties wil be set after all the nodes are processed
1353 }
1354
1355 // materials
1356 // Note that we use a QVector/QList here instead of a QQmlListProperty, as that would be really inconvenient.
1357 // Since we don't create any runtime objects at this point, the list also contains the node type that corresponds with the
1358 // type expected to be in the list (this is ensured at compile-time).
1359 QSSGSceneDesc::setProperty(target, "materials", &QQuick3DModel::materials, materials);
1360}
1361
1363 const aiNode &srcNode,
1364 QSSGSceneDesc::Node &parent,
1365 const SceneInfo &sceneInfo)
1366{
1367 QSSGSceneDesc::Node *node = nullptr;
1368 const auto &srcScene = sceneInfo.scene;
1369 switch (nodeInfo.type) {
1370 case QSSGSceneDesc::Node::Type::Camera:
1371 {
1372 const auto &srcType = *srcScene.mCameras[nodeInfo.index];
1373 // We set the initial rt-type to 'Custom', but we'll change it when updateing the properties.
1374 auto targetType = new QSSGSceneDesc::Camera(QSSGSceneDesc::Node::RuntimeType::CustomCamera);
1375 QSSGSceneDesc::addNode(parent, *targetType);
1376 setCameraProperties(*targetType, srcType, srcNode, sceneInfo);
1377 node = targetType;
1378 }
1379 break;
1380 case QSSGSceneDesc::Node::Type::Light:
1381 {
1382 const auto &srcType = *srcScene.mLights[nodeInfo.index];
1383 // Initial type is DirectonalLight, but will be change (if needed) when setting the properties.
1384 auto targetType = new QSSGSceneDesc::Light(QSSGSceneDesc::Node::RuntimeType::DirectionalLight);
1385 QSSGSceneDesc::addNode(parent, *targetType);
1386 setLightProperties(*targetType, srcType, srcNode, sceneInfo);
1387 node = targetType;
1388 }
1389 break;
1390 case QSSGSceneDesc::Node::Type::Model:
1391 {
1392 auto target = new QSSGSceneDesc::Model;
1393 QSSGSceneDesc::addNode(parent, *target);
1394 setModelProperties(*target, srcNode, sceneInfo);
1395 node = target;
1396 }
1397 break;
1398 case QSSGSceneDesc::Node::Type::Joint:
1399 {
1400 auto target = new QSSGSceneDesc::Joint;
1401 QSSGSceneDesc::addNode(parent, *target);
1402 setNodeProperties(*target, srcNode, sceneInfo, nullptr);
1403 QSSGSceneDesc::setProperty(*target, "index", &QQuick3DJoint::setIndex, qint32(nodeInfo.index));
1404 node = target;
1405 }
1406 break;
1407 case QSSGSceneDesc::Node::Type::Transform:
1408 {
1409 node = new QSSGSceneDesc::Node(QSSGSceneDesc::Node::Type::Transform, QSSGSceneDesc::Node::RuntimeType::Node);
1410 QSSGSceneDesc::addNode(parent, *node);
1411 // TODO: arguments for correction
1412 setNodeProperties(*node, srcNode, sceneInfo, nullptr);
1413 }
1414 break;
1415 default:
1416 break;
1417 }
1418
1419 return node;
1420}
1421
1422static void processNode(const SceneInfo &sceneInfo, const aiNode &source, QSSGSceneDesc::Node &parent, const NodeMap &nodeMap, AnimationNodeMap &animationNodes)
1423{
1424 QSSGSceneDesc::Node *node = nullptr;
1425 if (source.mNumMeshes != 0) {
1426 // Process morphTargets first and then add them to the modelNode
1427 using It = decltype(source.mNumMeshes);
1428 QVarLengthArray<MorphProperty> morphProps;
1429 for (It i = 0, end = source.mNumMeshes; i != end; ++i) {
1430 const auto &srcScene = sceneInfo.scene;
1431 const aiMesh &mesh = *srcScene.mMeshes[source.mMeshes[i]];
1432 const quint32 numMorphTargets = mesh.mNumAnimMeshes;
1433 if (numMorphTargets && mesh.mAnimMeshes) {
1434 morphProps.reserve(numMorphTargets);
1435 for (uint j = 0; j < numMorphTargets; ++j) {
1436 const auto &animMesh = mesh.mAnimMeshes[j];
1437 MorphAttributes attrib;
1438 if (animMesh->HasPositions())
1439 attrib |= QQuick3DMorphTarget::MorphTargetAttribute::Position;
1440 if (animMesh->HasNormals())
1441 attrib |= QQuick3DMorphTarget::MorphTargetAttribute::Normal;
1442 if (animMesh->HasTangentsAndBitangents()) {
1443 attrib |= QQuick3DMorphTarget::MorphTargetAttribute::Tangent;
1444 attrib |= QQuick3DMorphTarget::MorphTargetAttribute::Binormal;
1445 }
1446 morphProps.append({ fromAiString(animMesh->mName),
1447 attrib,
1448 animMesh->mWeight });
1449 }
1450 }
1451 }
1452 node = createSceneNode(NodeInfo { 0, QSSGSceneDesc::Node::Type::Model }, source, parent, sceneInfo);
1453 if (!morphProps.isEmpty()) {
1454 const QString nodeName(source.mName.C_Str());
1455 QVarLengthArray<QSSGSceneDesc::MorphTarget *> morphTargets;
1456 morphTargets.reserve(morphProps.size());
1457 for (int i = 0, end = morphProps.size(); i != end; ++i) {
1458 const auto morphProp = morphProps.at(i);
1459
1460 auto morphNode = new QSSGSceneDesc::MorphTarget;
1461 QSSGSceneDesc::addNode(*node, *morphNode);
1462 QSSGSceneDesc::setProperty(*morphNode, "weight", &QQuick3DMorphTarget::setWeight, morphProp.weight);
1463 QSSGSceneDesc::setProperty(*morphNode, "attributes", &QQuick3DMorphTarget::setAttributes, morphProp.attrib);
1464 morphNode->name = morphProp.name;
1465 morphTargets.push_back(morphNode);
1466
1467 if (!animationNodes.isEmpty()) {
1468 QString morphTargetName = nodeName + QStringLiteral("_morph") + QString::number(i);
1469 const auto aNodeIt = animationNodes.find(morphTargetName.toUtf8());
1470 if (aNodeIt != animationNodes.end() && aNodeIt.value() == nullptr)
1471 *aNodeIt = morphNode;
1472 }
1473 }
1474 QSSGSceneDesc::setProperty(*node, "morphTargets", &QQuick3DModel::morphTargets, morphTargets);
1475 }
1476 }
1477
1478 if (!node) {
1479 NodeInfo nodeInfo{ 0, QSSGSceneDesc::Node::Type::Transform };
1480 if (auto it = nodeMap.constFind(&source); it != nodeMap.constEnd())
1481 nodeInfo = (*it);
1482 node = createSceneNode(nodeInfo, source, parent, sceneInfo);
1483 }
1484
1485 if (!node)
1486 node = &parent;
1487
1488 Q_ASSERT(node->scene);
1489
1490 // Check if this node is a target for an animation
1491 if (!animationNodes.isEmpty()) {
1492 const auto &nodeName = source.mName;
1493 auto aNodeIt = animationNodes.find(QByteArray{nodeName.C_Str(), qsizetype(nodeName.length)});
1494 if (aNodeIt != animationNodes.end() && aNodeIt.value() == nullptr)
1495 *aNodeIt = node;
1496 }
1497
1498 // Process child nodes
1499 using It = decltype (source.mNumChildren);
1500 for (It i = 0, end = source.mNumChildren; i != end; ++i)
1501 processNode(sceneInfo, **(source.mChildren + i), *node, nodeMap, animationNodes);
1502}
1503
1504static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiVectorKey &key, qreal freq) {
1505 const auto flag = quint16(QSSGSceneDesc::Animation::KeyPosition::KeyType::Time) | quint16(QSSGSceneDesc::Animation::KeyPosition::ValueType::Vec3);
1506 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ key.mValue.x, key.mValue.y, key.mValue.z, 0.0f }, float(key.mTime * freq), flag };
1507}
1508
1509static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiQuatKey &key, qreal freq) {
1510 const auto flag = quint16(QSSGSceneDesc::Animation::KeyPosition::KeyType::Time) | quint16(QSSGSceneDesc::Animation::KeyPosition::ValueType::Quaternion);
1511 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ key.mValue.x, key.mValue.y, key.mValue.z, key.mValue.w }, float(key.mTime * freq), flag };
1512}
1513
1514static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiMeshMorphKey &key, qreal freq, uint morphId) {
1515 const auto flag = quint16(QSSGSceneDesc::Animation::KeyPosition::KeyType::Time) | quint16(QSSGSceneDesc::Animation::KeyPosition::ValueType::Number);
1516 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ float(key.mWeights[morphId]), 0.0f, 0.0f, 0.0f }, float(key.mTime * freq), flag };
1517}
1518
1519static bool checkBooleanOption(const QString &optionName, const QJsonObject &options)
1520{
1521 const auto it = options.constFind(optionName);
1522 const auto end = options.constEnd();
1523 QJsonValue value;
1524 if (it != end) {
1525 if (it->isObject())
1526 value = it->toObject().value("value");
1527 else
1528 value = it.value();
1529 }
1530 return value.toBool();
1531}
1532
1533static qreal getRealOption(const QString &optionName, const QJsonObject &options)
1534{
1535 const auto it = options.constFind(optionName);
1536 const auto end = options.constEnd();
1537 QJsonValue value;
1538 if (it != end) {
1539 if (it->isObject())
1540 value = it->toObject().value("value");
1541 else
1542 value = it.value();
1543 }
1544
1545 return value.toDouble();
1546}
1547
1548#define demonPostProcessPresets (
1549 aiProcess_CalcTangentSpace |
1550 aiProcess_JoinIdenticalVertices |
1551 aiProcess_ImproveCacheLocality |
1552 aiProcess_RemoveRedundantMaterials |
1553 aiProcess_SplitLargeMeshes |
1554 aiProcess_Triangulate |
1555 aiProcess_GenUVCoords |
1556 aiProcess_SortByPType |
1557 aiProcess_FindDegenerates |
1558 aiProcess_FindInvalidData |
1559 0 )
1560
1561static aiPostProcessSteps processOptions(const QJsonObject &optionsObject, std::unique_ptr<Assimp::Importer> &importer) {
1562 aiPostProcessSteps postProcessSteps = aiPostProcessSteps(aiProcess_Triangulate | aiProcess_SortByPType);;
1563
1564 // Setup import settings based given options
1565 // You can either pass the whole options object, or just the "options" object
1566 // so get the right scope.
1567 QJsonObject options = optionsObject;
1568
1569 if (auto it = options.constFind("options"), end = options.constEnd(); it != end)
1570 options = it->toObject();
1571
1572 if (options.isEmpty())
1573 return postProcessSteps;
1574
1575 // parse the options list for values
1576
1577 if (checkBooleanOption(QStringLiteral("calculateTangentSpace"), options))
1578 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_CalcTangentSpace);
1579
1580 if (checkBooleanOption(QStringLiteral("joinIdenticalVertices"), options))
1581 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_JoinIdenticalVertices);
1582
1583 if (checkBooleanOption(QStringLiteral("generateNormals"), options))
1584 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_GenNormals);
1585
1586 if (checkBooleanOption(QStringLiteral("generateSmoothNormals"), options))
1587 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_GenSmoothNormals);
1588
1589 if (checkBooleanOption(QStringLiteral("splitLargeMeshes"), options))
1590 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_SplitLargeMeshes);
1591
1592 if (checkBooleanOption(QStringLiteral("preTransformVertices"), options))
1593 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_PreTransformVertices);
1594
1595 if (checkBooleanOption(QStringLiteral("improveCacheLocality"), options))
1596 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_ImproveCacheLocality);
1597
1598 if (checkBooleanOption(QStringLiteral("removeRedundantMaterials"), options))
1599 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_RemoveRedundantMaterials);
1600
1601 if (checkBooleanOption(QStringLiteral("fixInfacingNormals"), options))
1602 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_FixInfacingNormals);
1603
1604 if (checkBooleanOption(QStringLiteral("findDegenerates"), options))
1605 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_FindDegenerates);
1606
1607 if (checkBooleanOption(QStringLiteral("findInvalidData"), options))
1608 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_FindInvalidData);
1609
1610 if (checkBooleanOption(QStringLiteral("transformUVCoordinates"), options))
1611 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_TransformUVCoords);
1612
1613 if (checkBooleanOption(QStringLiteral("findInstances"), options))
1614 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_FindInstances);
1615
1616 if (checkBooleanOption(QStringLiteral("optimizeMeshes"), options))
1617 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_OptimizeMeshes);
1618
1619 if (checkBooleanOption(QStringLiteral("optimizeGraph"), options))
1620 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_OptimizeGraph);
1621
1622 if (checkBooleanOption(QStringLiteral("dropNormals"), options))
1623 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_DropNormals);
1624
1625 aiComponent removeComponents = aiComponent(0);
1626
1627 if (checkBooleanOption(QStringLiteral("removeComponentNormals"), options))
1628 removeComponents = aiComponent(removeComponents | aiComponent_NORMALS);
1629
1630 if (checkBooleanOption(QStringLiteral("removeComponentTangentsAndBitangents"), options))
1631 removeComponents = aiComponent(removeComponents | aiComponent_TANGENTS_AND_BITANGENTS);
1632
1633 if (checkBooleanOption(QStringLiteral("removeComponentColors"), options))
1634 removeComponents = aiComponent(removeComponents | aiComponent_COLORS);
1635
1636 if (checkBooleanOption(QStringLiteral("removeComponentUVs"), options))
1637 removeComponents = aiComponent(removeComponents | aiComponent_TEXCOORDS);
1638
1639 if (checkBooleanOption(QStringLiteral("removeComponentBoneWeights"), options))
1640 removeComponents = aiComponent(removeComponents | aiComponent_BONEWEIGHTS);
1641
1642 if (checkBooleanOption(QStringLiteral("removeComponentAnimations"), options))
1643 removeComponents = aiComponent(removeComponents | aiComponent_ANIMATIONS);
1644
1645 if (checkBooleanOption(QStringLiteral("removeComponentTextures"), options))
1646 removeComponents = aiComponent(removeComponents | aiComponent_TEXTURES);
1647
1648 if (removeComponents != aiComponent(0)) {
1649 postProcessSteps = aiPostProcessSteps(postProcessSteps | aiProcess_RemoveComponent);
1650 importer->SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, removeComponents);
1651 }
1652
1653 bool preservePivots = checkBooleanOption(QStringLiteral("fbxPreservePivots"), options);
1654 importer->SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, preservePivots);
1655
1656 return postProcessSteps;
1657}
1658
1659static SceneInfo::Options processSceneOptions(const QJsonObject &optionsObject) {
1660 SceneInfo::Options sceneOptions;
1661
1662 // Setup import settings based given options
1663 // You can either pass the whole options object, or just the "options" object
1664 // so get the right scope.
1665 QJsonObject options = optionsObject;
1666
1667 if (auto it = options.constFind("options"), end = options.constEnd(); it != end)
1668 options = it->toObject();
1669
1670 if (options.isEmpty())
1671 return sceneOptions;
1672
1673 if (checkBooleanOption(QStringLiteral("globalScale"), options)) {
1674 sceneOptions.globalScaleValue = getRealOption(QStringLiteral("globalScaleValue"), options);
1675 if (sceneOptions.globalScaleValue == 0.0)
1676 sceneOptions.globalScaleValue = 1.0;
1677 }
1678
1679 sceneOptions.designStudioWorkarounds = checkBooleanOption(QStringLiteral("designStudioWorkarounds"), options);
1680 sceneOptions.useFloatJointIndices = checkBooleanOption(QStringLiteral("useFloatJointIndices"), options);
1681 sceneOptions.forceMipMapGeneration = checkBooleanOption(QStringLiteral("generateMipMaps"), options);
1682 sceneOptions.binaryKeyframes = checkBooleanOption(QStringLiteral("useBinaryKeyframes"), options);
1683 sceneOptions.generateMeshLODs = checkBooleanOption(QStringLiteral("generateMeshLevelsOfDetail"), options);
1684 if (sceneOptions.generateMeshLODs) {
1685 bool recalculateLODNormals = checkBooleanOption(QStringLiteral("recalculateLodNormals"), options);
1686 if (recalculateLODNormals) {
1687 qreal mergeAngle = getRealOption(QStringLiteral("recalculateLodNormalsMergeAngle"), options);
1688 sceneOptions.lodNormalMergeAngle = qBound(0.0, mergeAngle, 270.0);
1689 qreal splitAngle = getRealOption(QStringLiteral("recalculateLodNormalsSplitAngle"), options);
1690 sceneOptions.lodNormalSplitAngle = qBound(0.0, splitAngle, 270.0);
1691 } else {
1692 sceneOptions.lodNormalMergeAngle = 0.0;
1693 sceneOptions.lodNormalSplitAngle = 0.0;
1694 }
1695 }
1696 return sceneOptions;
1697}
1698
1699static QString importImp(const QUrl &url, const QJsonObject &options, QSSGSceneDesc::Scene &targetScene)
1700{
1701 auto filePath = url.path();
1702
1703 const bool maybeLocalFile = QQmlFile::isLocalFile(url);
1704 if (maybeLocalFile && !QFileInfo::exists(filePath))
1705 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1706
1707 auto sourceFile = QFileInfo(filePath);
1708 if (!sourceFile.exists())
1709 return QLatin1String("File not found");
1710 targetScene.sourceDir = sourceFile.path();
1711
1712 std::unique_ptr<Assimp::Importer> importer(new Assimp::Importer());
1713
1714 // Setup import from Options
1715 aiPostProcessSteps postProcessSteps;
1716 if (options.isEmpty())
1717 postProcessSteps = aiPostProcessSteps(demonPostProcessPresets);
1718 else
1719 postProcessSteps = processOptions(options, importer);
1720
1721 // Remove primitives that are not Triangles
1722 importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
1723 importer->SetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 1);
1724
1725 if (filePath.startsWith(":"))
1726 importer->SetIOHandler(new ResourceIOSystem);
1727#ifdef Q_OS_WASM
1728 // Always use QFile-based IO on wasm, so that URL schemes like
1729 // weblocalfile: are handled by Qt's file engine abstraction.
1730 else
1731 importer->SetIOHandler(new ResourceIOSystem);
1732#endif
1733
1734 auto sourceScene = importer->ReadFile(filePath.toStdString(), postProcessSteps);
1735 if (!sourceScene) {
1736 // Scene failed to load, use logger to get the reason
1737 return QString::fromLocal8Bit(importer->GetErrorString());
1738 }
1739
1740 // For simplicity, and convenience, we'll just use the file path as the id.
1741 // DO NOT USE it for anything else, once the scene is created there's no
1742 // real connection to the source asset file.
1743 targetScene.id = sourceFile.canonicalFilePath();
1744
1745 // Assuming consistent type usage
1746 using It = decltype(sourceScene->mNumMeshes);
1747
1748 // Before we can start processing the scene we start my mapping out the nodes
1749 // we can tell the type of.
1750 const auto &srcRootNode = *sourceScene->mRootNode;
1751 NodeMap nodeMap;
1752 // We need to know which nodes are animated so we can map _our_ animation data to
1753 // the target node (in Assimp this is string based mapping).
1754 AnimationNodeMap animatingNodes;
1755 {
1756 if (sourceScene->HasLights()) {
1757 for (It i = 0, end = sourceScene->mNumLights; i != end; ++i) {
1758 const auto &type = *sourceScene->mLights[i];
1759 if (auto node = srcRootNode.FindNode(type.mName))
1760 nodeMap[node] = { i, NodeInfo::Type::Light };
1761 }
1762 }
1763
1764 if (sourceScene->HasCameras()) {
1765 for (It i = 0, end = sourceScene->mNumCameras; i != end; ++i) {
1766 const auto &srcCam = *sourceScene->mCameras[i];
1767 if (auto node = srcRootNode.FindNode(srcCam.mName))
1768 nodeMap[node] = { i, NodeInfo::Type::Camera };
1769 }
1770 }
1771
1772 if (sourceScene->HasAnimations()) {
1773 for (It i = 0, end = sourceScene->mNumAnimations; i != end; ++i) {
1774 const auto &srcAnim = *sourceScene->mAnimations[i];
1775 const auto channelCount = srcAnim.mNumChannels;
1776 for (It cIdx = 0; cIdx != channelCount; ++cIdx) {
1777 const auto &srcChannel = srcAnim.mChannels[cIdx];
1778 const auto &nodeName = srcChannel->mNodeName;
1779 if (nodeName.length > 0) {
1780 // We'll update this once we've created the node!
1781 QByteArray name(nodeName.C_Str(), qsizetype(nodeName.length));
1782 if (!animatingNodes.contains(name))
1783 animatingNodes.insert(name, nullptr);
1784 }
1785 }
1786 const auto morphChannelCount = srcAnim.mNumMorphMeshChannels;
1787 for (It cIdx = 0; cIdx != morphChannelCount; ++cIdx) {
1788 const auto &srcChannel = srcAnim.mMorphMeshChannels[cIdx];
1789 const auto &nodeName = srcChannel->mName;
1790 if (nodeName.length > 0) {
1791 const auto morphKeys = srcChannel->mKeys;
1792 const auto numMorphTargets = morphKeys[0].mNumValuesAndWeights;
1793 // MorphTarget's animations are stored with a name,
1794 // "<nodeName> + '_morph' + <targetNumber>"
1795 for (It j = 0; j < numMorphTargets; ++j) {
1796 QString morphTargetName(nodeName.C_Str());
1797 morphTargetName += QStringLiteral("_morph") + QString::number(j);
1798 animatingNodes.insert(morphTargetName.toUtf8(), nullptr);
1799 }
1800 }
1801 }
1802 }
1803 }
1804 }
1805
1806 // We'll use these to ensure we don't re-create resources.
1807 const auto materialCount = sourceScene->mNumMaterials;
1808 SceneInfo::MaterialMap materials;
1809 materials.reserve(materialCount);
1810
1811 const auto meshCount = sourceScene->mNumMeshes;
1812 SceneInfo::MeshMap meshes;
1813 meshes.reserve(meshCount);
1814 SceneInfo::Mesh2SkinMap mesh2skin;
1815 mesh2skin.reserve(meshCount);
1816
1817 const auto embeddedTextureCount = sourceScene->mNumTextures;
1818 SceneInfo::EmbeddedTextureMap embeddedTextures;
1819
1820 SceneInfo::SkinMap skins;
1821
1822 for (It i = 0; i != materialCount; ++i)
1823 materials.push_back({sourceScene->mMaterials[i], nullptr});
1824
1825 for (It i = 0; i != meshCount; ++i) {
1826 meshes.push_back({sourceScene->mMeshes[i], nullptr});
1827 if (sourceScene->mMeshes[i]->HasBones()) {
1828 mesh2skin.push_back(skins.size());
1829 const auto boneCount = sourceScene->mMeshes[i]->mNumBones;
1830 auto bones = sourceScene->mMeshes[i]->mBones;
1831 skins.push_back(SceneInfo::skinData{ bones, boneCount, nullptr });
1832
1833 // For skinning, we need to get the joints list and their target nodes.
1834 // It is also done by the string based mapping and many of them will
1835 // be animated. So we will use existing AnimationNodeMap for the data.
1836 for (It j = 0; j != boneCount; ++j) {
1837 const auto &nodeName = bones[j]->mName;
1838 if (nodeName.length > 0) {
1839 animatingNodes.insert(QByteArray{ nodeName.C_Str(),
1840 qsizetype(nodeName.length) },
1841 nullptr);
1842 }
1843 }
1844 } else {
1845 mesh2skin.push_back(-1);
1846 }
1847 }
1848
1849 for (It i = 0; i != embeddedTextureCount; ++i)
1850 embeddedTextures.push_back(nullptr);
1851
1852 SceneInfo::TextureMap textureMap;
1853
1854 if (!targetScene.root) {
1855 auto root = new QSSGSceneDesc::Node(QByteArrayLiteral("Root"), QSSGSceneDesc::Node::Type::Transform, QSSGSceneDesc::Node::RuntimeType::Node);
1856 QSSGSceneDesc::addNode(targetScene, *root);
1857 }
1858
1859 // Get Options
1860 auto opt = processSceneOptions(options);
1861 // check if the asset is GLTF format
1862 const auto extension = sourceFile.suffix().toLower();
1863 if (extension == QStringLiteral("gltf") || extension == QStringLiteral("glb"))
1864 opt.gltfMode = true;
1865 else if (extension == QStringLiteral("fbx"))
1866 opt.fbxMode = true;
1867
1868 SceneInfo sceneInfo { *sourceScene, materials, meshes, embeddedTextures,
1869 textureMap, skins, mesh2skin, sourceFile.dir(), opt };
1870
1871 if (!qFuzzyCompare(opt.globalScaleValue, 1.0f) && !qFuzzyCompare(opt.globalScaleValue, 0.0f)) {
1872 const auto gscale = opt.globalScaleValue;
1873 QSSGSceneDesc::setProperty(*targetScene.root, "scale", &QQuick3DNode::setScale, QVector3D { gscale, gscale, gscale });
1874 }
1875
1876 // Now lets go through the scene
1877 if (sourceScene->mRootNode)
1878 processNode(sceneInfo, *sourceScene->mRootNode, *targetScene.root, nodeMap, animatingNodes);
1879 // skins
1880 for (It i = 0, endI = skins.size(); i != endI; ++i) {
1881 const auto &skin = skins[i];
1882
1883 // It is possible that an asset has a unused mesh with a skin
1884 if (!skin.node)
1885 continue;
1886
1887 QList<QMatrix4x4> inverseBindPoses;
1888 QVarLengthArray<QSSGSceneDesc::Node *> joints;
1889 joints.reserve(skin.mNumBones);
1890 for (It j = 0, endJ = skin.mNumBones; j != endJ; ++j) {
1891 const auto &bone = *skin.mBones[j];
1892 const auto &nodeName = bone.mName;
1893 if (nodeName.length > 0) {
1894 auto targetNode = animatingNodes.value(QByteArray{ nodeName.C_Str(), qsizetype(nodeName.length) });
1895 joints.push_back(targetNode);
1896 const auto &osMat = bone.mOffsetMatrix;
1897 auto pose = QMatrix4x4(osMat[0][0], osMat[0][1], osMat[0][2], osMat[0][3],
1898 osMat[1][0], osMat[1][1], osMat[1][2], osMat[1][3],
1899 osMat[2][0], osMat[2][1], osMat[2][2], osMat[2][3],
1900 osMat[3][0], osMat[3][1], osMat[3][2], osMat[3][3]);
1901 inverseBindPoses.push_back(pose);
1902 }
1903 }
1904 QSSGSceneDesc::setProperty(*skin.node, "joints", &QQuick3DSkin::joints, joints);
1905 QSSGSceneDesc::setProperty(*skin.node, "inverseBindPoses", &QQuick3DSkin::setInverseBindPoses, inverseBindPoses);
1906 }
1907
1908 static const auto fuzzyComparePos = [](const aiVectorKey *pos, const aiVectorKey *prev){
1909 if (!prev)
1910 return false;
1911 return qFuzzyCompare(pos->mValue.x, prev->mValue.x)
1912 && qFuzzyCompare(pos->mValue.y, prev->mValue.y)
1913 && qFuzzyCompare(pos->mValue.z, prev->mValue.z);
1914 };
1915
1916 static const auto fuzzyCompareRot = [](const aiQuatKey *rot, const aiQuatKey *prev){
1917 if (!prev)
1918 return false;
1919 return qFuzzyCompare(rot->mValue.x, prev->mValue.x)
1920 && qFuzzyCompare(rot->mValue.y, prev->mValue.y)
1921 && qFuzzyCompare(rot->mValue.z, prev->mValue.z)
1922 && qFuzzyCompare(rot->mValue.w, prev->mValue.w);
1923 };
1924
1925 static const auto createAnimation = [](QSSGSceneDesc::Scene &targetScene, const aiAnimation &srcAnim, const AnimationNodeMap &animatingNodes) {
1926 using namespace QSSGSceneDesc;
1927 Animation targetAnimation;
1928 auto &channels = targetAnimation.channels;
1929 qreal freq = qFuzzyIsNull(srcAnim.mTicksPerSecond) ? 1.0
1930 : 1000.0 / srcAnim.mTicksPerSecond;
1931 targetAnimation.framesPerSecond = srcAnim.mTicksPerSecond;
1932 targetAnimation.name = fromAiString(srcAnim.mName);
1933 // Process property channels
1934 for (It i = 0, end = srcAnim.mNumChannels; i != end; ++i) {
1935 const auto &srcChannel = *srcAnim.mChannels[i];
1936
1937 const auto &nodeName = srcChannel.mNodeName;
1938 if (nodeName.length > 0) {
1939 const auto aNodeEnd = animatingNodes.cend();
1940 const auto aNodeIt = animatingNodes.constFind(QByteArray{ nodeName.C_Str(), qsizetype(nodeName.length) });
1941 if (aNodeIt != aNodeEnd && aNodeIt.value() != nullptr) {
1942 auto targetNode = aNodeIt.value();
1943 // Target propert[y|ies]
1944
1945 const auto currentPropertyValue = [targetNode](const char *propertyName) -> QVariant {
1946 for (const auto *p : std::as_const(targetNode->properties)) {
1947 if (!qstrcmp(propertyName, p->name))
1948 return p->value;
1949 }
1950 return {};
1951 };
1952
1953 { // Position
1954 const auto posKeyEnd = srcChannel.mNumPositionKeys;
1955 Animation::Channel targetChannel;
1956 targetChannel.targetProperty = Animation::Channel::TargetProperty::Position;
1957 targetChannel.target = targetNode;
1958 const aiVectorKey *prevPos = nullptr;
1959 for (It posKeyIdx = 0; posKeyIdx != posKeyEnd; ++posKeyIdx) {
1960 const auto &posKey = srcChannel.mPositionKeys[posKeyIdx];
1961 if (fuzzyComparePos(&posKey, prevPos))
1962 continue;
1963 targetChannel.keys.push_back(new Animation::KeyPosition(toAnimationKey(posKey, freq)));
1964 prevPos = &posKey;
1965 }
1966
1967 const auto isUnchanged = [&targetChannel, currentPropertyValue]() {
1968 if (targetChannel.keys.count() != 1)
1969 return false;
1970 auto currentPos = currentPropertyValue("position").value<QVector3D>();
1971 return qFuzzyCompare(targetChannel.keys[0]->value.toVector3D(), currentPos);
1972 };
1973 if (!targetChannel.keys.isEmpty()) {
1974 if (!isUnchanged()) {
1975 channels.push_back(new Animation::Channel(targetChannel));
1976 float endTime = float(srcChannel.mPositionKeys[posKeyEnd - 1].mTime) * freq;
1977 if (targetAnimation.length < endTime)
1978 targetAnimation.length = endTime;
1979 } else {
1980 // the keys will not be used.
1981 qDeleteAll(targetChannel.keys);
1982 }
1983 }
1984 }
1985
1986 { // Rotation
1987 const auto rotKeyEnd = srcChannel.mNumRotationKeys;
1988 Animation::Channel targetChannel;
1989 targetChannel.targetProperty = Animation::Channel::TargetProperty::Rotation;
1990 targetChannel.target = targetNode;
1991 const aiQuatKey *prevRot = nullptr;
1992 for (It rotKeyIdx = 0; rotKeyIdx != rotKeyEnd; ++rotKeyIdx) {
1993 const auto &rotKey = srcChannel.mRotationKeys[rotKeyIdx];
1994 if (fuzzyCompareRot(&rotKey, prevRot))
1995 continue;
1996 targetChannel.keys.push_back(new Animation::KeyPosition(toAnimationKey(rotKey, freq)));
1997 prevRot = &rotKey;
1998 }
1999
2000 const auto isUnchanged = [&targetChannel, currentPropertyValue]() {
2001 if (targetChannel.keys.count() != 1)
2002 return false;
2003 auto currentVal = currentPropertyValue("rotation");
2004 QQuaternion rot = currentVal.isValid() ? currentVal.value<QQuaternion>() : QQuaternion{};
2005 return qFuzzyCompare(QQuaternion(targetChannel.keys[0]->value), rot);
2006 };
2007 if (!targetChannel.keys.isEmpty()) {
2008 if (!isUnchanged()) {
2009 channels.push_back(new Animation::Channel(targetChannel));
2010 float endTime = float(srcChannel.mRotationKeys[rotKeyEnd - 1].mTime) * freq;
2011 if (targetAnimation.length < endTime)
2012 targetAnimation.length = endTime;
2013 } else {
2014 // the keys will not be used.
2015 qDeleteAll(targetChannel.keys);
2016 }
2017 }
2018 }
2019
2020 { // Scale
2021 const auto scaleKeyEnd = srcChannel.mNumScalingKeys;
2022 Animation::Channel targetChannel;
2023 targetChannel.targetProperty = Animation::Channel::TargetProperty::Scale;
2024 targetChannel.target = targetNode;
2025 const aiVectorKey *prevScale = nullptr;
2026 for (It scaleKeyIdx = 0; scaleKeyIdx != scaleKeyEnd; ++scaleKeyIdx) {
2027 const auto &scaleKey = srcChannel.mScalingKeys[scaleKeyIdx];
2028 if (fuzzyComparePos(&scaleKey, prevScale))
2029 continue;
2030 targetChannel.keys.push_back(new Animation::KeyPosition(toAnimationKey(scaleKey, freq)));
2031 prevScale = &scaleKey;
2032 }
2033
2034 const auto isUnchanged = [&targetChannel, currentPropertyValue]() {
2035 if (targetChannel.keys.count() != 1)
2036 return false;
2037 auto currentVal = currentPropertyValue("scale");
2038 QVector3D scale = currentVal.isValid() ? currentVal.value<QVector3D>() : QVector3D{ 1, 1, 1 };
2039 return qFuzzyCompare(targetChannel.keys[0]->value.toVector3D(), scale);
2040 };
2041
2042 if (!targetChannel.keys.isEmpty()) {
2043 if (!isUnchanged()) {
2044 channels.push_back(new Animation::Channel(targetChannel));
2045 float endTime = float(srcChannel.mScalingKeys[scaleKeyEnd - 1].mTime) * freq;
2046 if (targetAnimation.length < endTime)
2047 targetAnimation.length = endTime;
2048 } else {
2049 // the keys will not be used.
2050 qDeleteAll(targetChannel.keys);
2051 }
2052 }
2053 }
2054 }
2055 }
2056 }
2057 // Morphing Animations
2058 for (It i = 0, end = srcAnim.mNumMorphMeshChannels; i != end; ++i) {
2059 const auto &srcMorphChannel = *srcAnim.mMorphMeshChannels[i];
2060 const QString nodeName(srcMorphChannel.mName.C_Str());
2061 const auto *morphKeys = srcMorphChannel.mKeys;
2062 const auto numMorphTargets = qMin(morphKeys[0].mNumValuesAndWeights, 8U);
2063 for (It targetId = 0; targetId != numMorphTargets; ++targetId) {
2064 QString morphTargetName = nodeName + QStringLiteral("_morph") + QString::number(targetId);
2065 const auto aNodeEnd = animatingNodes.cend();
2066 const auto aNodeIt = animatingNodes.constFind(morphTargetName.toUtf8());
2067 if (aNodeIt != aNodeEnd && aNodeIt.value() != nullptr) {
2068 auto targetNode = aNodeIt.value();
2069 const auto weightKeyEnd = srcMorphChannel.mNumKeys;
2070 Animation::Channel targetChannel;
2071 targetChannel.targetProperty = Animation::Channel::TargetProperty::Weight;
2072 targetChannel.target = targetNode;
2073 for (It wId = 0; wId != weightKeyEnd; ++wId) {
2074 const auto &weightKey = srcMorphChannel.mKeys[wId];
2075 const auto animationKey = new Animation::KeyPosition(toAnimationKey(weightKey, freq, targetId));
2076 targetChannel.keys.push_back(animationKey);
2077 }
2078 if (!targetChannel.keys.isEmpty()) {
2079 channels.push_back(new Animation::Channel(targetChannel));
2080 float endTime = float(srcMorphChannel.mKeys[weightKeyEnd - 1].mTime) * freq;
2081 if (targetAnimation.length < endTime)
2082 targetAnimation.length = endTime;
2083 }
2084 }
2085 }
2086 }
2087
2088 // If we have data we need to make it persistent.
2089 if (!targetAnimation.channels.isEmpty())
2090 targetScene.animations.push_back(new Animation(targetAnimation));
2091 };
2092
2093 // All scene nodes should now be created (and ready), so let's go through the animation data.
2094 if (sourceScene->HasAnimations()) {
2095 const auto animationCount = sourceScene->mNumAnimations;
2096 targetScene.animations.reserve(animationCount);
2097 for (It i = 0, end = animationCount; i != end; ++i) {
2098 const auto &srcAnim = *sourceScene->mAnimations[i];
2099 createAnimation(targetScene, srcAnim, animatingNodes);
2100 }
2101 }
2102
2103 // TODO, FIX: Editing the scene after the import ought to be done by QSSGAssetImportManager
2104 // and not by the asset import plugin. However, the asset import module cannot use
2105 // the asset utils module because that would cause a circular dependency. This
2106 // needs a deeper architectural fix.
2107
2108 QSSGQmlUtilities::applyEdit(&targetScene, options);
2109
2110 return QString();
2111}
2112
2113////////////////////////
2114
2115QString AssimpImporter::import(const QUrl &url, const QJsonObject &options, QSSGSceneDesc::Scene &scene)
2116{
2117 // We'll simply use assimp to load the scene and then translate the Aassimp scene
2118 // into our own format.
2119 return importImp(url, options, scene);
2120}
2121
2122QString AssimpImporter::import(const QString &sourceFile, const QDir &savePath, const QJsonObject &options, QStringList *generatedFiles)
2123{
2124 QString errorString;
2125
2126 QSSGSceneDesc::Scene scene;
2127
2128 // Load scene data
2129 auto sourceUrl = QUrl::fromLocalFile(sourceFile);
2130 errorString = importImp(sourceUrl, options, scene);
2131
2132 if (!errorString.isEmpty())
2133 return errorString;
2134
2135 // Write out QML + Resources
2136 QFileInfo sourceFileInfo(sourceFile);
2137
2138 QString targetFileName = savePath.absolutePath() + QDir::separator() +
2139 QSSGQmlUtilities::qmlComponentName(sourceFileInfo.completeBaseName()) +
2140 QStringLiteral(".qml");
2141 QFile targetFile(targetFileName);
2142 if (!targetFile.open(QIODevice::WriteOnly)) {
2143 errorString += QString("Could not write to file: ") + targetFileName;
2144 } else {
2145 QTextStream output(&targetFile);
2146 QSSGQmlUtilities::writeQml(scene, output, savePath, options);
2147 if (generatedFiles)
2148 generatedFiles->append(targetFileName);
2149 }
2150 scene.cleanup();
2151
2152 return errorString;
2153}
2154
2155QT_END_NAMESPACE
#define AI_GLTF_FILTER_NEAREST_MIPMAP_NEAREST
static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiMeshMorphKey &key, qreal freq, uint morphId)
static Q_REQUIRED_RESULT QColor aiColorToQColor(const aiColor3D &color)
#define AI_GLTF_FILTER_NEAREST
static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiQuatKey &key, qreal freq)
#define AI_GLTF_FILTER_NEAREST_MIPMAP_LINEAR
static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiVectorKey &key, qreal freq)
static void processNode(const SceneInfo &sceneInfo, const aiNode &source, QSSGSceneDesc::Node &parent, const NodeMap &nodeMap, AnimationNodeMap &animationNodes)
static qreal getRealOption(const QString &optionName, const QJsonObject &options)
#define AI_GLTF_FILTER_LINEAR_MIPMAP_LINEAR
static QByteArray fromAiString(const aiString &string)
static void setModelProperties(QSSGSceneDesc::Model &target, const aiNode &source, const SceneInfo &sceneInfo)
static void setLightProperties(QSSGSceneDesc::Light &target, const aiLight &source, const aiNode &sourceNode, const SceneInfo &sceneInfo)
static void setMaterialProperties(QSSGSceneDesc::Material &target, const aiMaterial &source, const SceneInfo &sceneInfo, QSSGSceneDesc::Material::RuntimeType type)
bool operator==(const TextureInfo &a, const TextureInfo &b)
bool operator==(const TextureEntry &a, const TextureEntry &b)
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
#define AI_GLTF_FILTER_LINEAR
static aiPostProcessSteps processOptions(const QJsonObject &optionsObject, std::unique_ptr< Assimp::Importer > &importer)
static void setNodeProperties(QSSGSceneDesc::Node &target, const aiNode &source, const SceneInfo &sceneInfo, aiMatrix4x4 *transformCorrection)
static QString importImp(const QUrl &url, const QJsonObject &options, QSSGSceneDesc::Scene &targetScene)
static void setTextureProperties(QSSGSceneDesc::Texture &target, const TextureInfo &texInfo, const SceneInfo &sceneInfo)
size_t qHash(const TextureEntry &key, size_t seed)
static QSSGSceneDesc::Node * createSceneNode(const NodeInfo &nodeInfo, const aiNode &srcNode, QSSGSceneDesc::Node &parent, const SceneInfo &sceneInfo)
#define demonPostProcessPresets
Q_DECLARE_TYPEINFO(NodeInfo, Q_PRIMITIVE_TYPE)
static SceneInfo::Options processSceneOptions(const QJsonObject &optionsObject)
static void setCameraProperties(QSSGSceneDesc::Camera &target, const aiCamera &source, const aiNode &sourceNode, const SceneInfo &sceneInfo)
static bool checkBooleanOption(const QString &optionName, const QJsonObject &options)
#define AI_GLTF_FILTER_LINEAR_MIPMAP_NEAREST
ResourceIOStream(const char *pFile, const char *pMode)
size_t FileSize() const override
void Flush() override
size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override
size_t Tell() const override
size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override
aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override
char getOsSeparator() const override
void Close(Assimp::IOStream *pFile) override
bool Exists(const char *pFile) const override
Assimp::IOStream * Open(const char *pFile, const char *pMode) override
#define M_PI
Definition qmath.h:201
MorphAttributes attrib
QSSGSceneDesc::Skin * node
EmbeddedTextureMap & embeddedTextureMap
MaterialMap & materialMap
Mesh2SkinMap & mesh2skin
TextureMap & textureMap
const aiScene & scene
QSSGSceneDesc::Texture * texture
unsigned int magFilter
aiUVTransform transform
aiTextureMapping mapping
aiTextureMapMode modes[3]
unsigned int minFilter