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