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