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

source code of qtquick3d/src/plugins/assetimporters/assimp/assimpimporter_rt.cpp