Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
4#include "assimpimporter.h"
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
33
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{
62 size_t index;
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;
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
117{
118 return (a.name == b.name) && (a.info == b.info);
119}
120
122{
123 struct Options
124 {
125 bool gltfMode = false;
126 bool fbxMode = false;
127 bool binaryKeyframes = false;
130 bool generateLightmapUV = false;
132
134 float globalScaleValue = 1.0;
135
136 bool generateMeshLODs = false;
139 };
140
141 using MaterialMap = QVarLengthArray<QPair<const aiMaterial *, QSSGSceneDesc::Material *>>;
142 using MeshMap = QVarLengthArray<QPair<const aiMesh *, QSSGSceneDesc::Mesh *>>;
143 using EmbeddedTextureMap = QVarLengthArray<QSSGSceneDesc::TextureData *>;
144 using TextureMap = QSet<TextureEntry>;
145
146 struct skinData {
147 aiBone **mBones;
148 unsigned int mNumBones;
150 };
151 using SkinMap = QVarLengthArray<skinData>;
152 using Mesh2SkinMap = QVarLengthArray<qint16>;
153
154 const aiScene &scene;
163};
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);
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{
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
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 {
311 }
312
313
314 // rotation
315 const QQuaternion rot(rotation.w, rotation.x, rotation.y, rotation.z);
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.
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.
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:
353 case aiTextureMapMode_Clamp:
355 case aiTextureMapMode_Mirror:
357 default:
358 break;
359 }
360
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);
389 } else {
392 }
393
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
413
414 // minFilter
415 if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST) {
417 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR) {
419 } else if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST_MIPMAP_NEAREST) {
422 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR_MIPMAP_NEAREST) {
425 } else if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST_MIPMAP_LINEAR) {
428 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR_MIPMAP_LINEAR) {
431 }
433
434 // mipFilter
435 generateMipMaps = (mipFilter != QQuick3DTexture::Filter::None);
436
437 if (generateMipMaps) {
440 }
441}
442
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});
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;
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);
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) {
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)
537 }
538 }
539
540 if (auto baseColorTexture = createTextureNode(source, AI_MATKEY_BASE_COLOR_TEXTURE)) {
543 } else if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
544 // Also try to legacy diffuse texture as an alternative
546 }
547
548 if (auto metalicRoughnessTexture = createTextureNode(source, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE)) {
549 QSSGSceneDesc::setProperty(target, "metalnessMap", &QQuick3DPrincipledMaterial::setMetalnessMap, metalicRoughnessTexture);
551 QSSGSceneDesc::setProperty(target, "roughnessMap", &QQuick3DPrincipledMaterial::setRoughnessMap, metalicRoughnessTexture);
553 }
554
555 {
556 ai_real metallicFactor;
557 result = source.Get(AI_MATKEY_METALLIC_FACTOR, metallicFactor);
558 if (result == aiReturn_SUCCESS)
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)) {
571 {
572 ai_real normalScale;
573 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
574 if (result == aiReturn_SUCCESS)
576 }
577 }
578
579 // Occlusion Textures are not implimented (yet)
580 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
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))
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)
606 }
607
608 {
609 aiString alphaMode;
610 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
611 if (result == aiReturn_SUCCESS) {
613 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
615 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
617 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
619
622 // If the mode is mask, we also need to force OpaquePrePassDepthDraw mode
625 }
626 }
627 }
628
629 {
630 ai_real alphaCutoff;
631 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
632 if (result == aiReturn_SUCCESS)
634 }
635
636 {
637 int shadingModel = 0;
638 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
639 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
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)
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)
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))
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)
692 "transmissionFactor",
693 &QQuick3DPrincipledMaterial::setTransmissionFactor,
694 float(transmissionFactor));
695 }
696
697 // texture
698 {
699 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
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)
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)
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)
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))
766
767 if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
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)
776 }
777
778 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
780
781 // specularReflectionMap
782 if (auto specularTexture = createTextureNode(source, aiTextureType_SPECULAR, 0))
784
785 // opacity AI_MATKEY_OPACITY
786 ai_real opacity;
787 result = material->Get(AI_MATKEY_OPACITY, opacity);
788 if (result == aiReturn_SUCCESS)
790
791 // opacityMap aiTextureType_OPACITY 0
792 if (auto opacityTexture = createTextureNode(source, aiTextureType_OPACITY, 0))
794
795 // bumpMap aiTextureType_HEIGHT 0
796 if (auto bumpTexture = createTextureNode(source, aiTextureType_HEIGHT, 0)) {
798 // bumpAmount AI_MATKEY_BUMPSCALING
799 ai_real bumpAmount;
800 result = material->Get(AI_MATKEY_BUMPSCALING, bumpAmount);
801 if (result == aiReturn_SUCCESS)
803 }
804
805 // normalMap aiTextureType_NORMALS 0
806 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0))
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)
814 }
815
816 if (auto albedoTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
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);
825 }
826
827 {
828 aiColor4D specularColorFactor;
829 result = source.Get(AI_MATKEY_COLOR_SPECULAR, specularColorFactor);
830 if (result == aiReturn_SUCCESS)
832 }
833
834 {
835 ai_real glossinessFactor;
836 result = source.Get(AI_MATKEY_GLOSSINESS_FACTOR, glossinessFactor);
837 if (result == aiReturn_SUCCESS)
839 }
840
841 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0)) {
843 {
844 ai_real normalScale;
845 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
846 if (result == aiReturn_SUCCESS)
848 }
849 }
850
851 // Occlusion Textures are not implimented (yet)
852 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
855 {
856 ai_real occlusionAmount;
857 result = source.Get(AI_MATKEY_GLTF_TEXTURE_STRENGTH(aiTextureType_LIGHTMAP, 0), occlusionAmount);
858 if (result == aiReturn_SUCCESS)
860 }
861 }
862
863 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
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)
878 }
879
880 {
881 aiString alphaMode;
882 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
883 if (result == aiReturn_SUCCESS) {
885 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
887 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
889 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
891
894 // If the mode is mask, we also need to force OpaquePrePassDepthDraw mode
897 }
898 }
899 }
900
901 {
902 ai_real alphaCutoff;
903 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
904 if (result == aiReturn_SUCCESS)
906 }
907
908 {
909 int shadingModel = 0;
910 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
911 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
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)
924 "clearcoatAmount",
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)
935 "clearcoatRoughnessAmount",
937 float(clearcoatRoughnessFactor));
938 }
939
940 // texture
941 if (auto clearcoatTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_TEXTURE))
943
944 // roughness texture
945 if (auto clearcoatRoughnessTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE))
947 "clearcoatRoughnessMap",
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)
964 "transmissionFactor",
966 float(transmissionFactor));
967 }
968
969 // texture
970 {
971 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
973 "transmissionMap",
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)
988 }
989
990 // thicknessMap
991 {
992 if (auto thicknessImage = createTextureNode(source, AI_MATKEY_VOLUME_THICKNESS_TEXTURE))
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)
1002 "attenuationDistance",
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)
1013 "attenuationColor",
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);
1065 } else { //OrthographicCamera
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);
1076
1077 // isFieldOfViewHorizontal
1080 } else { //OrthographicCamera
1081 const float width = source.mOrthographicWidth * 2.0f;
1082 const float height = width / source.mAspect;
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);
1150 }
1151
1152 // diffuseColor
1153 const QColor diffuseColor = QColor::fromRgbF(source.mColorDiffuse.r / brightness,
1154 source.mColorDiffuse.g / brightness,
1155 source.mColorDiffuse.b / brightness);
1157
1158 // describe brightness here
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);
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
1200static QVector<MorphProperty> getMorphTargetProperties(const aiMesh &mesh)
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())
1210 if (animMesh->HasNormals())
1212 if (animMesh->HasTangentsAndBitangents()) {
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,
1293 sceneInfo.opt.useFloatJointIndices,
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)
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).
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;
1379 setModelProperties(*target, srcNode, sceneInfo);
1380 node = target;
1381 }
1382 break;
1383 case QSSGSceneDesc::Node::Type::Joint:
1384 {
1385 auto target = new QSSGSceneDesc::Joint;
1387 setNodeProperties(*target, srcNode, sceneInfo, nullptr);
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
1475 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ key.mValue.x, key.mValue.y, key.mValue.z, 0.0f }, float(key.mTime * freq), flag };
1476}
1477
1482
1483static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiMeshMorphKey &key, qreal freq, uint morphId) {
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();
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();
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
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))
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
2084
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
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
#define AI_GLTF_FILTER_NEAREST_MIPMAP_NEAREST
QPair< MorphAttributes, float > MorphProperty
static Q_REQUIRED_RESULT QColor aiColorToQColor(const aiColor3D &color)
#define AI_GLTF_FILTER_NEAREST
#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)
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
QQuick3DMorphTarget::MorphTargetAttributes MorphAttributes
#define AI_GLTF_FILTER_LINEAR
QHash< const aiNode *, NodeInfo > NodeMap
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)
QHash< QByteArray, QSSGSceneDesc::Node * > AnimationNodeMap
static QSSGSceneDesc::Node * createSceneNode(const NodeInfo &nodeInfo, const aiNode &srcNode, QSSGSceneDesc::Node &parent, const SceneInfo &sceneInfo)
#define demonPostProcessPresets
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
QString import(const QString &sourceFile, const QDir &savePath, const QJsonObject &options, QStringList *generatedFiles) override
\inmodule QtCore
Definition qbytearray.h:57
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
static QColor fromRgbF(float r, float g, float b, float a=1.0)
Static convenience function that returns a QColor constructed from the RGB color values,...
Definition qcolor.cpp:2427
\inmodule QtCore
Definition qdir.h:20
static QChar separator()
Returns the native directory separator: "/" under Unix and "\\" under Windows.
Definition qdir.h:209
qint64 pos() const override
\reimp
bool seek(qint64 offset) override
For random-access devices, this function sets the current position to pos, returning true on success,...
bool exists() const
Returns true if the file system entry this QFileInfo refers to exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qfile.cpp:351
qint64 size() const override
\reimp
Definition qfile.cpp:1179
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtCore\reentrant
Definition qjsonobject.h:20
const_iterator constFind(const QString &key) const
Returns a const iterator pointing to the item with key key in the map.
const_iterator constEnd() const
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
bool isEmpty() const
Returns true if the object is empty.
\inmodule QtCore\reentrant
Definition qjsonvalue.h:25
Definition qlist.h:75
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
void push_back(parameter_type t)
Definition qlist.h:675
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
static bool isLocalFile(const QString &url)
Returns true if url is a local file that can be opened with \l{QFile}.
Definition qqmlfile.cpp:644
static QString urlToLocalFileOrQrc(const QString &)
If url is a local file returns a path suitable for passing to \l{QFile}.
Definition qqmlfile.cpp:742
The QQuaternion class represents a quaternion consisting of a vector and scalar.
void setBrightness(float brightness)
void setColor(const QColor &color)
void setAmbientColor(const QColor &ambientColor)
void setLighting(QQuick3DDefaultMaterial::Lighting lighting)
void setBumpMap(QQuick3DTexture *bumpMap)
void setEmissiveMap(QQuick3DTexture *emissiveMap)
void setBumpAmount(float bumpAmount)
void setOpacityMap(QQuick3DTexture *opacityMap)
void setNormalMap(QQuick3DTexture *normalMap)
void setDiffuseMap(QQuick3DTexture *diffuseMap)
void setSpecularMap(QQuick3DTexture *specularMap)
void setDiffuseColor(QColor diffuseColor)
void setIndex(qint32 index)
void setDepthDrawMode(QQuick3DMaterial::DepthDrawMode depthDrawMode)
void setCullMode(QQuick3DMaterial::CullMode cullMode)
QQmlListProperty< QQuick3DMaterial > materials
\qmlproperty List<QtQuick3D::Material> Model::materials
QQmlListProperty< QQuick3DMorphTarget > morphTargets
\qmlproperty List<QtQuick3D::MorphTarget> Model::morphTargets
void setSource(const QUrl &source)
void setAttributes(QQuick3DMorphTarget::MorphTargetAttributes attributes)
void setWeight(float castsShadows)
void setRotation(const QQuaternion &rotation)
void setScale(const QVector3D &scale)
void setPosition(const QVector3D &position)
void setY(float y)
void setX(float x)
void setZ(float z)
void setVerticalMagnification(float horizontalMagnification)
void setHorizontalMagnification(float horizontalMagnification)
void setFieldOfViewOrientation(QQuick3DPerspectiveCamera::FieldOfViewOrientation fieldOfViewOrientation)
void setConstantFade(float constantFade)
void setQuadraticFade(float quadraticFade)
void setLinearFade(float linearFade)
void setOcclusionMap(QQuick3DTexture *occlusionMap)
void setLighting(QQuick3DPrincipledMaterial::Lighting lighting)
void setBaseColorMap(QQuick3DTexture *baseColorMap)
void setMetalnessMap(QQuick3DTexture *metalnessMap)
void setNormalStrength(float normalStrength)
void setMetalnessChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setEmissiveMap(QQuick3DTexture *emissiveMap)
void setRoughnessChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setAlphaMode(QQuick3DPrincipledMaterial::AlphaMode alphaMode)
void setOcclusionChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setRoughnessMap(QQuick3DTexture *roughnessMap)
void setNormalMap(QQuick3DTexture *normalMap)
void setEmissiveFactor(QVector3D emissiveFactor)
void setOcclusionAmount(float occlusionAmount)
void setOpacityChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setMetalness(float metalnessAmount)
void setInverseBindPoses(const QList< QMatrix4x4 > &poses)
QQmlListProperty< QQuick3DNode > joints
\qmlproperty List<QtQuick3D::Node> Skin::joints
void setEmissiveFactor(const QVector3D &emissiveFactor)
void setAlphaMode(QQuick3DSpecularGlossyMaterial::AlphaMode alphaMode)
void setClearcoatMap(QQuick3DTexture *newClearcoatMap)
void setOcclusionMap(QQuick3DTexture *occlusionMap)
void setClearcoatAmount(float newClearcoatAmount)
void setNormalMap(QQuick3DTexture *normalMap)
void setOpacityChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setGlossinessMap(QQuick3DTexture *glossinessMap)
void setTransmissionMap(QQuick3DTexture *newTransmissionMap)
void setEmissiveMap(QQuick3DTexture *emissiveMap)
void setThicknessMap(QQuick3DTexture *newThicknessMap)
void setThicknessFactor(float newThicknessFactor)
void setOcclusionChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setAttenuationColor(const QColor &newAttenuationColor)
void setClearcoatRoughnessMap(QQuick3DTexture *newClearcoatRoughnessMap)
void setGlossinessChannel(QQuick3DMaterial::TextureChannelMapping channel)
void setAlbedoMap(QQuick3DTexture *albedoMap)
void setTransmissionFactor(float newTransmissionFactor)
void setSpecularMap(QQuick3DTexture *specularMap)
void setAttenuationDistance(float newAttenuationDistance)
void setClearcoatRoughnessAmount(float newClearcoatRoughnessAmount)
void setLighting(QQuick3DSpecularGlossyMaterial::Lighting lighting)
void setClearcoatNormalMap(QQuick3DTexture *newClearcoatNormalMap)
void setConstantFade(float constantFade)
void setInnerConeAngle(float innerConeAngle)
void setConeAngle(float coneAngle)
void setLinearFade(float linearFade)
void setQuadraticFade(float quadraticFade)
void setPivotV(float pivotV)
void setScaleV(float scaleV)
void setHorizontalTiling(QQuick3DTexture::TilingMode tilingModeHorizontal)
Q_REVISION(6, 7) void setDepthTiling(QQuick3DTexture void setRotationUV(float rotationUV)
void setTextureData(QQuick3DTextureData *textureData)
void setMipFilter(QQuick3DTexture::Filter mipFilter)
void setVerticalTiling(QQuick3DTexture::TilingMode tilingModeVertical)
void setGenerateMipmaps(bool generateMipmaps)
void setIndexUV(int indexUV)
void setPositionU(float positionU)
void setPositionV(float positionV)
void setSource(const QUrl &source)
void setMagFilter(QQuick3DTexture::Filter magFilter)
void setMappingMode(QQuick3DTexture::MappingMode mappingMode)
void setMinFilter(QQuick3DTexture::Filter minFilter)
void setScaleU(float scaleU)
const_iterator cend() const noexcept
Definition qset.h:142
const_iterator constEnd() const noexcept
Definition qset.h:143
const_iterator constFind(const T &value) const
Definition qset.h:161
\inmodule QtCore
Definition qsize.h:25
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromStdString(const std::string &s)
Definition qstring.h:1447
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5949
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:8084
\inmodule QtCore
\inmodule QtCore
Definition qurl.h:94
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3368
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2468
\inmodule QtCore
Definition qvariant.h:65
T value() const &
Definition qvariant.h:516
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:536
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:330
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:878
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
void extension()
[6]
Definition dialogs.cpp:230
qDeleteAll(list.begin(), list.end())
QSet< QString >::iterator it
QStyleOptionButton opt
QSSGMesh::Mesh generateMeshData(const aiScene &scene, const MeshList &meshes, bool useFloatJointIndices, bool generateLevelsOfDetail, float normalMergeAngle, float normalSplitAngle, QString &errorString)
void applyEdit(QSSGSceneDesc::Scene *scene, const QJsonObject &changes)
static void writeQml(const QSSGSceneDesc::Node &transform, OutputContext &output)
QString qmlComponentName(const QString &name)
static void setProperty(QSSGSceneDesc::Node &node, const char *name, Setter setter, T &&value)
Q_QUICK3DASSETUTILS_EXPORT void addNode(Node &parent, Node &node)
Combined button and popup list for selecting options.
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
#define Q_FALLTHROUGH()
#define Q_REQUIRED_RESULT
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
size_t qHashBits(const void *p, size_t size, size_t seed) noexcept
Definition qhash.cpp:1089
return ret
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
#define M_PI
Definition qmath.h:209
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLenum mode
GLuint64 key
GLint GLsizei GLsizei height
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLint GLsizei width
GLuint color
[2]
GLenum type
GLenum target
GLbitfield flags
GLenum GLuint texture
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLuint sourceTexture
GLuint name
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLuint GLenum GLenum transform
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLenum GLenum GLenum GLenum mapping
GLfloat GLfloat p
[1]
GLenum GLenum GLenum GLenum GLenum scale
QT_BEGIN_NAMESPACE constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
Definition qrandom.cpp:196
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
#define Q_UNIMPLEMENTED()
#define Q_UNUSED(x)
@ Q_PRIMITIVE_TYPE
Definition qtypeinfo.h:157
#define Q_DECLARE_TYPEINFO(TYPE, FLAGS)
Definition qtypeinfo.h:180
unsigned int quint32
Definition qtypes.h:50
short qint16
Definition qtypes.h:47
unsigned short quint16
Definition qtypes.h:48
size_t quintptr
Definition qtypes.h:167
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
double qreal
Definition qtypes.h:187
unsigned char quint8
Definition qtypes.h:46
ReturnedValue read(const char *data)
QT_BEGIN_NAMESPACE typedef uchar * output
QFile file
[0]
QUrl url("example.com")
[constructor-url-reference]
QByteArray imageData
[15]
QGraphicsScene scene
[0]
args<< 1<< 2;QJSValue threeAgain=fun.call(args);QString fileName="helloworld.qs";QFile scriptFile(fileName);if(!scriptFile.open(QIODevice::ReadOnly)) QTextStream stream(&scriptFile);QString contents=stream.readAll();scriptFile.close();myEngine.evaluate(contents, fileName);myEngine.globalObject().setProperty("myNumber", 123);...QJSValue myNumberPlusOne=myEngine.evaluate("myNumber + 1");QJSValue result=myEngine.evaluate(...);if(result.isError()) qDebug()<< "Uncaught exception at line"<< result.property("lineNumber").toInt()<< ":"<< result.toString();QPushButton *button=new QPushButton;QJSValue scriptButton=myEngine.newQObject(button);myEngine.globalObject().setProperty("button", scriptButton);myEngine.evaluate("button.checkable = true");qDebug()<< scriptButton.property("checkable").toBool();scriptButton.property("show").call();QJSEngine engine;QObject *myQObject=new QObject();myQObject- setProperty)("dynamicProperty", 3)
QSSGSceneDesc::Skin * node
Mesh2SkinMap & mesh2skin
QVarLengthArray< QPair< const aiMaterial *, QSSGSceneDesc::Material * > > MaterialMap
const aiScene & scene
QVarLengthArray< skinData > SkinMap
QVarLengthArray< QSSGSceneDesc::TextureData * > EmbeddedTextureMap
TextureMap & textureMap
QSet< TextureEntry > TextureMap
QVarLengthArray< QPair< const aiMesh *, QSSGSceneDesc::Mesh * > > MeshMap
EmbeddedTextureMap & embeddedTextureMap
QVarLengthArray< qint16 > Mesh2SkinMap
MaterialMap & materialMap
unsigned int magFilter
aiUVTransform transform
aiTextureMapMode modes[3]
unsigned int minFilter
Definition moc.h:23