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