Qt 6.x
The Qt SDK
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
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
30
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(color.r, color.g, color.b, 1.0f);
43}
44
45Q_REQUIRED_RESULT static inline QColor aiColorToQColor(const aiColor4D &color)
46{
47 return QColor::fromRgbF(color.r, color.g, color.b, 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
57{
59 size_t index;
61};
62
64
66
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
75{
76 aiTextureMapMode modes[3] {};
77 aiTextureMapping mapping = aiTextureMapping::aiTextureMapping_UV;
81 aiUVTransform transform;
82};
83
84bool operator==(const TextureInfo &a, const TextureInfo &b)
85{
86 return (a.mapping == b.mapping)
87 && (std::memcmp(a.modes, b.modes, sizeof(a.modes)) == 0)
88 && (a.minFilter == b.minFilter)
89 && (a.magFilter == b.magFilter)
90 && (a.uvIndex == b.uvIndex)
91 && isEqual(a.transform, b.transform);
92}
93
95{
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(&key.info.transform, sizeof(aiUVTransform), seed);
109
110 return qHash(key.name, seed) ^ infoKey;
111}
112
114{
115 return (a.name == b.name) && (a.info == b.info);
116}
117
119{
120 struct Options
121 {
122 bool gltfMode = false;
123 bool binaryKeyframes = false;
126 bool generateLightmapUV = false;
129 float globalScaleValue = 1.0;
130
131 bool generateMeshLODs = false;
134 };
135
140
141 struct skinData {
142 aiBone **mBones;
143 unsigned int mNumBones;
145 };
148
149 const aiScene &scene;
158};
159
161 const aiNode &source,
162 const SceneInfo &sceneInfo,
163 aiMatrix4x4 *transformCorrection)
164{
165 // objectName
166 if (target.name.isNull())
167 target.name = fromAiString(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(scaling, rotation, translation);
181
182 // translate
183 if (!sceneInfo.opt.designStudioWorkarounds) {
184 QSSGSceneDesc::setProperty(target, "position", &QQuick3DNode::setPosition, QVector3D { translation.x, translation.y, translation.z });
185 } else {
189 }
190
191
192 // rotation
193 const QQuaternion rot(rotation.w, rotation.x, rotation.y, rotation.z);
195
196 // scale
197 QSSGSceneDesc::setProperty(target, "scale", &QQuick3DNode::setScale, 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.
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.
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:
231 case aiTextureMapMode_Clamp:
233 case aiTextureMapMode_Mirror:
235 default:
236 break;
237 }
238
240 };
241
242 // mapping mode U
243 QSSGSceneDesc::setProperty(target, "tilingModeHorizontal", &QQuick3DTexture::setHorizontalTiling, asQtTilingMode(texInfo.modes[0]));
244
245 // mapping mode V
246 QSSGSceneDesc::setProperty(target, "tilingModeVertical", &QQuick3DTexture::setVerticalTiling, asQtTilingMode(texInfo.modes[1]));
247
248 const bool applyUvTransform = !isEqual(texInfo.transform, 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(rotation);
259 float posU = transform.mTranslation.x;
260 float posV = transform.mTranslation.y;
261 if (sceneInfo.opt.gltfMode) {
262 const float rcos = std::cos(rotation);
263 const float rsin = std::sin(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);
267 } else {
270 }
271
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
291
292 // minFilter
293 if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST) {
295 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR) {
297 } else if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST_MIPMAP_NEAREST) {
300 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR_MIPMAP_NEAREST) {
303 } else if (texInfo.minFilter == AI_GLTF_FILTER_NEAREST_MIPMAP_LINEAR) {
306 } else if (texInfo.minFilter == AI_GLTF_FILTER_LINEAR_MIPMAP_LINEAR) {
309 }
311
312 // mipFilter
313 generateMipMaps = (mipFilter != QQuick3DTexture::Filter::None);
314
315 if (generateMipMaps) {
318 }
319}
320
322{
323 if (target.name.isNull()) {
324 aiString materialName = source.GetName();
325 target.name = fromAiString(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(textureType, index, &texturePath, &texInfo.mapping, &texInfo.uvIndex, nullptr, nullptr, texInfo.modes) == aiReturn_SUCCESS) {
335 if (texturePath.length > 0) {
336 aiUVTransform transform;
337 if (material.Get(AI_MATKEY_UVTRANSFORM(textureType, index), transform) == aiReturn_SUCCESS)
338 texInfo.transform = transform;
339
340 material.Get(AI_MATKEY_UVWSRC(textureType, index), texInfo.uvIndex);
341 material.Get(AI_MATKEY_GLTF_MAPPINGFILTER_MIN(textureType, index), texInfo.minFilter);
342 material.Get(AI_MATKEY_GLTF_MAPPINGFILTER_MAG(textureType, index), 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(TextureEntry{texName, 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(TextureEntry{fromAiString(texturePath), texInfo, tex});
358 setTextureProperties(*tex, texInfo, sceneInfo); // both
359
360 auto aEmbeddedTex = srcScene.GetEmbeddedTextureAndIndex(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;
379 QSSGSceneDesc::addNode(*tex, *textureData);
380 embeddedTextures[embeddedTexId] = textureData;
381 }
382
383 if (textureData)
384 QSSGSceneDesc::setProperty(*tex, "textureData", &QQuick3DTexture::setTextureData, textureData);
385 } else {
386 auto relativePath = QString::fromUtf8(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("\\","/");
390 const auto path = sceneInfo.workingDir.absoluteFilePath(relativePath);
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, baseColorFactor);
406 if (result == aiReturn_SUCCESS) {
408
409 } else {
410 // Also try diffuse color as a fallback
411 aiColor3D diffuseColor;
412 result = source.Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);
413 if (result == aiReturn_SUCCESS)
415 }
416 }
417
418 if (auto baseColorTexture = createTextureNode(source, AI_MATKEY_BASE_COLOR_TEXTURE)) {
421 } else if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
422 // Also try to legacy diffuse texture as an alternative
424 }
425
426 if (auto metalicRoughnessTexture = createTextureNode(source, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE)) {
427 QSSGSceneDesc::setProperty(target, "metalnessMap", &QQuick3DPrincipledMaterial::setMetalnessMap, metalicRoughnessTexture);
429 QSSGSceneDesc::setProperty(target, "roughnessMap", &QQuick3DPrincipledMaterial::setRoughnessMap, metalicRoughnessTexture);
431 }
432
433 {
434 ai_real metallicFactor;
435 result = source.Get(AI_MATKEY_METALLIC_FACTOR, metallicFactor);
436 if (result == aiReturn_SUCCESS)
438 }
439
440 {
441 ai_real roughnessFactor;
442 result = source.Get(AI_MATKEY_ROUGHNESS_FACTOR, roughnessFactor);
443 if (result == aiReturn_SUCCESS)
444 QSSGSceneDesc::setProperty(target, "roughness", &QQuick3DPrincipledMaterial::setRoughness, float(roughnessFactor));
445 }
446
447 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0)) {
449 {
450 ai_real normalScale;
451 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
452 if (result == aiReturn_SUCCESS)
454 }
455 }
456
457 // Occlusion Textures are not implimented (yet)
458 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
461 {
462 ai_real occlusionAmount;
463 result = source.Get(AI_MATKEY_GLTF_TEXTURE_STRENGTH(aiTextureType_LIGHTMAP, 0), occlusionAmount);
464 if (result == aiReturn_SUCCESS)
465 QSSGSceneDesc::setProperty(target, "occlusionAmount", &QQuick3DPrincipledMaterial::setOcclusionAmount, float(occlusionAmount));
466 }
467 }
468
469 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
471
472 {
473 aiColor3D emissiveColorFactor;
474 result = source.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColorFactor);
475 if (result == aiReturn_SUCCESS)
476 QSSGSceneDesc::setProperty(target, "emissiveFactor", &QQuick3DPrincipledMaterial::setEmissiveFactor, QVector3D { emissiveColorFactor.r, emissiveColorFactor.g, emissiveColorFactor.b });
477 }
478
479 {
480 bool isDoubleSided;
481 result = source.Get(AI_MATKEY_TWOSIDED, isDoubleSided);
482 if (result == aiReturn_SUCCESS && isDoubleSided)
484 }
485
486 {
487 aiString alphaMode;
488 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
489 if (result == aiReturn_SUCCESS) {
491 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
493 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
495 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
497
500 }
501 }
502
503 {
504 ai_real alphaCutoff;
505 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
506 if (result == aiReturn_SUCCESS)
508 }
509
510 {
511 int shadingModel = 0;
512 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
513 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
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, clearcoatFactor);
524 if (result == aiReturn_SUCCESS)
526 "clearcoatAmount",
527 &QQuick3DPrincipledMaterial::setClearcoatAmount,
528 float(clearcoatFactor));
529 }
530
531 // roughness
532 {
533 ai_real clearcoatRoughnessFactor = 0.0f;
534 result = source.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoatRoughnessFactor);
535 if (result == aiReturn_SUCCESS)
537 "clearcoatRoughnessAmount",
538 &QQuick3DPrincipledMaterial::setClearcoatRoughnessAmount,
539 float(clearcoatRoughnessFactor));
540 }
541
542 // texture
543 if (auto clearcoatTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_TEXTURE))
544 QSSGSceneDesc::setProperty(target, "clearcoatMap", &QQuick3DPrincipledMaterial::setClearcoatMap, clearcoatTexture);
545
546 // roughness texture
547 if (auto clearcoatRoughnessTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE))
549 "clearcoatRoughnessMap",
550 &QQuick3DPrincipledMaterial::setClearcoatRoughnessMap,
551 clearcoatRoughnessTexture);
552
553 // normal texture
554 if (auto clearcoatNormalTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE))
555 QSSGSceneDesc::setProperty(target, "clearcoatNormalMap", &QQuick3DPrincipledMaterial::setClearcoatNormalMap, 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, transmissionFactor);
564 if (result == aiReturn_SUCCESS)
566 "transmissionFactor",
567 &QQuick3DPrincipledMaterial::setTransmissionFactor,
568 float(transmissionFactor));
569 }
570
571 // texture
572 {
573 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
575 "transmissionMap",
576 &QQuick3DPrincipledMaterial::setTransmissionMap,
577 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, thicknessFactor);
588 if (result == aiReturn_SUCCESS)
589 QSSGSceneDesc::setProperty(target, "thicknessFactor", &QQuick3DPrincipledMaterial::setThicknessFactor, float(thicknessFactor));
590 }
591
592 // thicknessMap
593 {
594 if (auto thicknessImage = createTextureNode(source, AI_MATKEY_VOLUME_THICKNESS_TEXTURE))
595 QSSGSceneDesc::setProperty(target, "thicknessMap", &QQuick3DPrincipledMaterial::setThicknessMap, thicknessImage);
596 }
597
598 // attenuationDistance
599 {
600 ai_real attenuationDistance = 0.0f;
601 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuationDistance);
602 if (result == aiReturn_SUCCESS)
604 "attenuationDistance",
605 &QQuick3DPrincipledMaterial::setAttenuationDistance,
606 float(attenuationDistance));
607 }
608
609 // attenuationColor
610 {
611 aiColor3D attenuationColor;
612 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_COLOR, attenuationColor);
613 if (result == aiReturn_SUCCESS)
615 "attenuationColor",
616 &QQuick3DPrincipledMaterial::setAttenuationColor,
617 aiColorToQColor(attenuationColor));
618 }
619 }
620
621
622 // KHR_materials_ior
623 {
624 ai_real ior = 0.0f;
625 result = source.Get(AI_MATKEY_REFRACTI, ior);
626 if (result == aiReturn_SUCCESS)
628 "indexOfRefraction",
629 &QQuick3DPrincipledMaterial::setIndexOfRefraction,
630 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, shadingModel);
637 // lighting
638 if (result == aiReturn_SUCCESS && (shadingModel == aiShadingMode_NoShading))
640
641 if (auto diffuseMapTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
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, diffuseColor);
648 if (result == aiReturn_SUCCESS)
650 }
651
652 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
654
655 // specularReflectionMap
656 if (auto specularTexture = createTextureNode(source, aiTextureType_SPECULAR, 0))
658
659 // opacity AI_MATKEY_OPACITY
660 ai_real opacity;
661 result = material->Get(AI_MATKEY_OPACITY, opacity);
662 if (result == aiReturn_SUCCESS)
664
665 // opacityMap aiTextureType_OPACITY 0
666 if (auto opacityTexture = createTextureNode(source, aiTextureType_OPACITY, 0))
668
669 // bumpMap aiTextureType_HEIGHT 0
670 if (auto bumpTexture = createTextureNode(source, aiTextureType_HEIGHT, 0)) {
672 // bumpAmount AI_MATKEY_BUMPSCALING
673 ai_real bumpAmount;
674 result = material->Get(AI_MATKEY_BUMPSCALING, bumpAmount);
675 if (result == aiReturn_SUCCESS)
677 }
678
679 // normalMap aiTextureType_NORMALS 0
680 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0))
682 } else if (type == QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial) {
683 {
684 aiColor4D albedoFactor;
685 result = source.Get(AI_MATKEY_COLOR_DIFFUSE, albedoFactor);
686 if (result == aiReturn_SUCCESS)
688 }
689
690 if (auto albedoTexture = createTextureNode(source, aiTextureType_DIFFUSE, 0)) {
693 }
694
695 if (auto specularGlossinessTexture = createTextureNode(source, aiTextureType_SPECULAR, 0)) {
696 QSSGSceneDesc::setProperty(target, "specularMap", &QQuick3DSpecularGlossyMaterial::setSpecularMap, specularGlossinessTexture);
697 QSSGSceneDesc::setProperty(target, "glossinessMap", &QQuick3DSpecularGlossyMaterial::setGlossinessMap, specularGlossinessTexture);
699 }
700
701 {
702 aiColor4D specularColorFactor;
703 result = source.Get(AI_MATKEY_COLOR_SPECULAR, specularColorFactor);
704 if (result == aiReturn_SUCCESS)
706 }
707
708 {
709 ai_real glossinessFactor;
710 result = source.Get(AI_MATKEY_GLOSSINESS_FACTOR, glossinessFactor);
711 if (result == aiReturn_SUCCESS)
713 }
714
715 if (auto normalTexture = createTextureNode(source, aiTextureType_NORMALS, 0)) {
717 {
718 ai_real normalScale;
719 result = source.Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale);
720 if (result == aiReturn_SUCCESS)
722 }
723 }
724
725 // Occlusion Textures are not implimented (yet)
726 if (auto occlusionTexture = createTextureNode(source, aiTextureType_LIGHTMAP, 0)) {
729 {
730 ai_real occlusionAmount;
731 result = source.Get(AI_MATKEY_GLTF_TEXTURE_STRENGTH(aiTextureType_LIGHTMAP, 0), occlusionAmount);
732 if (result == aiReturn_SUCCESS)
734 }
735 }
736
737 if (auto emissiveTexture = createTextureNode(source, aiTextureType_EMISSIVE, 0))
739
740 {
741 aiColor3D emissiveColorFactor;
742 result = source.Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColorFactor);
743 if (result == aiReturn_SUCCESS)
744 QSSGSceneDesc::setProperty(target, "emissiveFactor", &QQuick3DSpecularGlossyMaterial::setEmissiveFactor, QVector3D { emissiveColorFactor.r, emissiveColorFactor.g, emissiveColorFactor.b });
745 }
746
747 {
748 bool isDoubleSided;
749 result = source.Get(AI_MATKEY_TWOSIDED, isDoubleSided);
750 if (result == aiReturn_SUCCESS && isDoubleSided)
752 }
753
754 {
755 aiString alphaMode;
756 result = source.Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode);
757 if (result == aiReturn_SUCCESS) {
759 if (QByteArrayView(alphaMode.C_Str()) == "OPAQUE")
761 else if (QByteArrayView(alphaMode.C_Str()) == "MASK")
763 else if (QByteArrayView(alphaMode.C_Str()) == "BLEND")
765
768 }
769 }
770
771 {
772 ai_real alphaCutoff;
773 result = source.Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff);
774 if (result == aiReturn_SUCCESS)
776 }
777
778 {
779 int shadingModel = 0;
780 result = source.Get(AI_MATKEY_SHADING_MODEL, shadingModel);
781 if (result == aiReturn_SUCCESS && shadingModel == aiShadingMode_Unlit)
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, clearcoatFactor);
792 if (result == aiReturn_SUCCESS)
794 "clearcoatAmount",
796 float(clearcoatFactor));
797 }
798
799 // roughness
800 {
801 ai_real clearcoatRoughnessFactor = 0.0f;
802 result = source.Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoatRoughnessFactor);
803 if (result == aiReturn_SUCCESS)
805 "clearcoatRoughnessAmount",
807 float(clearcoatRoughnessFactor));
808 }
809
810 // texture
811 if (auto clearcoatTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_TEXTURE))
813
814 // roughness texture
815 if (auto clearcoatRoughnessTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_ROUGHNESS_TEXTURE))
817 "clearcoatRoughnessMap",
819 clearcoatRoughnessTexture);
820
821 // normal texture
822 if (auto clearcoatNormalTexture = createTextureNode(source, AI_MATKEY_CLEARCOAT_NORMAL_TEXTURE))
823 QSSGSceneDesc::setProperty(target, "clearcoatNormalMap", &QQuick3DSpecularGlossyMaterial::setClearcoatNormalMap, 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, transmissionFactor);
832 if (result == aiReturn_SUCCESS)
834 "transmissionFactor",
836 float(transmissionFactor));
837 }
838
839 // texture
840 {
841 if (auto transmissionImage = createTextureNode(source, AI_MATKEY_TRANSMISSION_TEXTURE))
843 "transmissionMap",
845 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, thicknessFactor);
856 if (result == aiReturn_SUCCESS)
858 }
859
860 // thicknessMap
861 {
862 if (auto thicknessImage = createTextureNode(source, AI_MATKEY_VOLUME_THICKNESS_TEXTURE))
864 }
865
866 // attenuationDistance
867 {
868 ai_real attenuationDistance = 0.0f;
869 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuationDistance);
870 if (result == aiReturn_SUCCESS)
872 "attenuationDistance",
874 float(attenuationDistance));
875 }
876
877 // attenuationColor
878 {
879 aiColor3D attenuationColor;
880 result = source.Get(AI_MATKEY_VOLUME_ATTENUATION_COLOR, attenuationColor);
881 if (result == aiReturn_SUCCESS)
883 "attenuationColor",
885 aiColorToQColor(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(aiVector3D(0, 0, -1), source.mLookAt, lookAtCorrection);
907 correctionMatrix *= lookAtCorrection;
908 needsCorrection = true;
909 upQuick3D *= lookAtCorrection;
910 }
911 if (source.mUp != upQuick3D) {
912 aiMatrix4x4 upCorrection;
913 aiMatrix4x4::FromToMatrix(upQuick3D, source.mUp, upCorrection);
914 correctionMatrix = upCorrection * correctionMatrix;
915 needsCorrection = true;
916 }
917
918 setNodeProperties(target, sourceNode, sceneInfo, needsCorrection ? &correctionMatrix : nullptr);
919
920 // clipNear and clipFar
921 if (target.runtimeType == Node::RuntimeType::PerspectiveCamera) {
924 } else { //OrthographicCamera
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(source.mHorizontalFOV);
935
936 // isFieldOfViewHorizontal
939 } else { //OrthographicCamera
940 const float width = source.mOrthographicWidth * 2.0f;
941 const float height = width / source.mAspect;
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(aiVector3D(0, 0, -1), source.mDirection, correctionMatrix);
966 needsCorrection = true;
967 }
968 }
969
970 // lightType
971 static const auto asQtLightType = [](aiLightSourceType type) {
972 switch (type) {
973 case aiLightSource_AMBIENT:
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, sourceNode, sceneInfo, 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(qMax(1.0f, source.mColorDiffuse.r),
997 qMax(source.mColorDiffuse.g, source.mColorDiffuse.b));
998
999 // ambientColor
1000 if (source.mType == aiLightSource_AMBIENT) {
1001 brightness = qMax(qMax(brightness, source.mColorAmbient.r),
1002 qMax(source.mColorAmbient.g, source.mColorAmbient.b));
1003
1004 // We only want ambient light color if it is explicit
1005 const QColor ambientColor = QColor::fromRgbF(source.mColorAmbient.r / brightness,
1006 source.mColorAmbient.g / brightness,
1007 source.mColorAmbient.b / brightness);
1009 }
1010
1011 // diffuseColor
1012 const QColor diffuseColor = QColor::fromRgbF(source.mColorDiffuse.r / brightness,
1013 source.mColorDiffuse.g / brightness,
1014 source.mColorDiffuse.b / brightness);
1016
1017 // describe brightness here
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(source.mAttenuationConstant);
1026
1027 if (isSpot) {
1028 if (hasAttConstant)
1029 QSSGSceneDesc::setProperty(target, "constantFade", &QQuick3DSpotLight::setConstantFade, source.mAttenuationConstant);
1030 QSSGSceneDesc::setProperty(target, "linearFade", &QQuick3DSpotLight::setLinearFade, source.mAttenuationLinear * 100.0f);
1031 QSSGSceneDesc::setProperty(target, "quadraticFade", &QQuick3DSpotLight::setQuadraticFade, source.mAttenuationQuadratic * 10000.0f);
1034 } else {
1035 if (hasAttConstant)
1036 QSSGSceneDesc::setProperty(target, "constantFade", &QQuick3DPointLight::setConstantFade, source.mAttenuationConstant);
1037 QSSGSceneDesc::setProperty(target, "linearFade", &QQuick3DPointLight::setLinearFade, source.mAttenuationLinear * 100.0f);
1038 QSSGSceneDesc::setProperty(target, "quadraticFade", &QQuick3DPointLight::setQuadraticFade, 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;
1058
1060{
1061 QVector<MorphProperty> targets;
1062 const quint32 numMorphTargets = qMin(8U, 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())
1069 if (animMesh->HasNormals())
1071 if (animMesh->HasTangentsAndBitangents()) {
1074 }
1075 targets.push_back(qMakePair(mTarget, 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, 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
1097 materials.reserve(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, glossinessFactor);
1112 if (result == aiReturn_SUCCESS)
1113 currentMaterialType = QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial;
1114
1115 targetMat = new QSSGSceneDesc::Material(currentMaterialType);
1116 QSSGSceneDesc::addNode(target, *targetMat);
1117 setMaterialProperties(*targetMat, *sourceMat, sceneInfo, 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(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(&mesh);
1146 }
1147 };
1148
1149 const auto createMeshNode = [&](const aiString &name) {
1150 auto meshData = AssimpUtils::generateMeshData(srcScene,
1151 meshes,
1152 sceneInfo.opt.useFloatJointIndices,
1153 sceneInfo.opt.generateMeshLODs,
1154 sceneInfo.opt.lodNormalMergeAngle,
1155 sceneInfo.opt.lodNormalSplitAngle,
1156 errorString);
1157 meshStorage.push_back(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(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(target, *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(target, *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)
1190
1191 if (skinIdx != -1) {
1192 auto &skin = skinMap[skinIdx];
1193 skin.node = new QSSGSceneDesc::Skin;
1194 QSSGSceneDesc::setProperty(target, "skin", &QQuick3DModel::setSkin, skin.node);
1195 QSSGSceneDesc::addNode(target, *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).
1204}
1205
1207 const aiNode &srcNode,
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, *targetType);
1220 setCameraProperties(*targetType, srcType, 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, *targetType);
1230 setLightProperties(*targetType, srcType, srcNode, sceneInfo);
1231 node = targetType;
1232 }
1233 break;
1234 case QSSGSceneDesc::Node::Type::Model:
1235 {
1236 auto target = new QSSGSceneDesc::Model;
1238 setModelProperties(*target, srcNode, sceneInfo);
1239 node = target;
1240 }
1241 break;
1242 case QSSGSceneDesc::Node::Type::Joint:
1243 {
1244 auto target = new QSSGSceneDesc::Joint;
1246 setNodeProperties(*target, srcNode, sceneInfo, nullptr);
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);
1255 // TODO: arguments for correction
1256 setNodeProperties(*node, srcNode, sceneInfo, 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 { 0, QSSGSceneDesc::Node::Type::Model }, source, parent, sceneInfo);
1282 if (!morphProps.isEmpty()) {
1283 const QString nodeName(source.mName.C_Str());
1285 morphTargets.reserve(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(*node, *morphNode);
1291 QSSGSceneDesc::setProperty(*morphNode, "weight", &QQuick3DMorphTarget::setWeight, morphProp.second);
1292 QSSGSceneDesc::setProperty(*morphNode, "attributes", &QQuick3DMorphTarget::setAttributes, morphProp.first);
1293 morphTargets.push_back(morphNode);
1294
1295 if (!animationNodes.isEmpty()) {
1296 QString morphTargetName = nodeName + QStringLiteral("_morph") + QString::number(i);
1297 const auto aNodeIt = animationNodes.find(morphTargetName.toUtf8());
1298 if (aNodeIt != animationNodes.end() && aNodeIt.value() == nullptr)
1299 *aNodeIt = morphNode;
1300 }
1301 }
1302 QSSGSceneDesc::setProperty(*node, "morphTargets", &QQuick3DModel::morphTargets, morphTargets);
1303 }
1304 }
1305
1306 if (!node) {
1307 NodeInfo nodeInfo{ 0, QSSGSceneDesc::Node::Type::Transform };
1308 if (auto it = nodeMap.constFind(&source); it != nodeMap.constEnd())
1309 nodeInfo = (*it);
1310 node = createSceneNode(nodeInfo, 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(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.mChildren + i), *node, nodeMap, animationNodes);
1330}
1331
1334 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ key.mValue.x, key.mValue.y, key.mValue.z, 0.0f }, float(key.mTime * freq), flag };
1335}
1336
1339 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ key.mValue.x, key.mValue.y, key.mValue.z, key.mValue.w }, float(key.mTime * freq), flag };
1340}
1341
1342static QSSGSceneDesc::Animation::KeyPosition toAnimationKey(const aiMeshMorphKey &key, qreal freq, uint morphId) {
1344 return QSSGSceneDesc::Animation::KeyPosition { QVector4D{ float(key.mWeights[morphId]), 0.0f, 0.0f, 0.0f }, float(key.mTime * freq), flag };
1345}
1346
1347static bool checkBooleanOption(const QString &optionName, const QJsonObject &options)
1348{
1349 const auto it = options.constFind(optionName);
1350 const auto end = options.constEnd();
1352 if (it != end) {
1353 if (it->isObject())
1354 value = it->toObject().value("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(optionName);
1364 const auto end = options.constEnd();
1366 if (it != end) {
1367 if (it->isObject())
1368 value = it->toObject().value("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("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, removeComponents);
1480 }
1481
1482 bool preservePivots = checkBooleanOption(QStringLiteral("fbxPreservePivots"), options);
1483 importer->SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, preservePivots);
1484
1485 return postProcessSteps;
1486}
1487
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("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(0.0, mergeAngle, 270.0);
1525 qreal splitAngle = getRealOption(QStringLiteral("recalculateLodNormalsSplitAngle"), options);
1526 sceneOptions.lodNormalSplitAngle = qBound(0.0, splitAngle, 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(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(options, importer);
1556
1557 // Remove primitives that are not Triangles
1558 importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE);
1559 importer->SetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 1);
1560
1561 auto sourceScene = importer->ReadFile(filePath.toStdString(), postProcessSteps);
1562 if (!sourceScene) {
1563 // Scene failed to load, use logger to get the reason
1564 return QString::fromLocal8Bit(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(type.mName))
1587 nodeMap[node] = { i, 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(srcCam.mName))
1595 nodeMap[node] = { i, 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(name))
1610 animatingNodes.insert(name, 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(morphKeys[0].mNumValuesAndWeights, 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(morphTargetName.toUtf8(), 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(materialCount);
1636
1637 const auto meshCount = sourceScene->mNumMeshes;
1638 SceneInfo::MeshMap meshes;
1639 meshes.reserve(meshCount);
1640 SceneInfo::Mesh2SkinMap mesh2skin;
1641 mesh2skin.reserve(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({sourceScene->mMaterials[i], nullptr});
1650
1651 for (It i = 0; i != meshCount; ++i) {
1652 meshes.push_back({sourceScene->mMeshes[i], nullptr});
1653 if (sourceScene->mMeshes[i]->HasBones()) {
1654 mesh2skin.push_back(skins.size());
1655 const auto boneCount = sourceScene->mMeshes[i]->mNumBones;
1656 auto bones = sourceScene->mMeshes[i]->mBones;
1657 skins.push_back(SceneInfo::skinData{ bones, boneCount, 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(QByteArray{ nodeName.C_Str(),
1666 qsizetype(nodeName.length) },
1667 nullptr);
1668 }
1669 }
1670 } else {
1671 mesh2skin.push_back(-1);
1672 }
1673 }
1674
1675 for (It i = 0; i != embeddedTextureCount; ++i)
1676 embeddedTextures.push_back(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(targetScene, *root);
1683 }
1684
1685 // Get Options
1686 auto opt = processSceneOptions(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 { *sourceScene, materials, meshes, embeddedTextures,
1691 textureMap, skins, mesh2skin, sourceFile.dir(), opt };
1692
1693 if (!qFuzzyCompare(opt.globalScaleValue, 1.0f) && !qFuzzyCompare(opt.globalScaleValue, 0.0f)) {
1694 const auto gscale = opt.globalScaleValue;
1695 QSSGSceneDesc::setProperty(*targetScene.root, "scale", &QQuick3DNode::setScale, QVector3D { gscale, gscale, gscale });
1696 }
1697
1698 // Now lets go through the scene
1699 if (sourceScene->mRootNode)
1700 processNode(sceneInfo, *sourceScene->mRootNode, *targetScene.root, nodeMap, 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;
1711 joints.reserve(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(QByteArray{ nodeName.C_Str(), qsizetype(nodeName.length) });
1717 joints.push_back(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(pose);
1724 }
1725 }
1726 QSSGSceneDesc::setProperty(*skin.node, "joints", &QQuick3DSkin::joints, joints);
1727 QSSGSceneDesc::setProperty(*skin.node, "inverseBindPoses", &QQuick3DSkin::setInverseBindPoses, inverseBindPoses);
1728 }
1729
1730 static const auto fuzzyComparePos = [](const aiVectorKey *pos, const aiVectorKey *prev){
1731 if (!prev)
1732 return false;
1733 return qFuzzyCompare(pos->mValue.x, prev->mValue.x)
1734 && qFuzzyCompare(pos->mValue.y, prev->mValue.y)
1735 && qFuzzyCompare(pos->mValue.z, 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(rot->mValue.x, prev->mValue.x)
1742 && qFuzzyCompare(rot->mValue.y, prev->mValue.y)
1743 && qFuzzyCompare(rot->mValue.z, prev->mValue.z)
1744 && qFuzzyCompare(rot->mValue.w, 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(srcAnim.mTicksPerSecond) ? 1.0
1752 : 1000.0 / srcAnim.mTicksPerSecond;
1753 targetAnimation.framesPerSecond = srcAnim.mTicksPerSecond;
1754 targetAnimation.name = fromAiString(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(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(propertyName, 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(new Animation::KeyPosition(toAnimationKey(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(targetChannel.keys[0]->value.toVector3D(), currentPos);
1794 };
1795 if (!targetChannel.keys.isEmpty()) {
1796 if (!isUnchanged()) {
1797 channels.push_back(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(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(new Animation::KeyPosition(toAnimationKey(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(QQuaternion(targetChannel.keys[0]->value), rot);
1828 };
1829 if (!targetChannel.keys.isEmpty()) {
1830 if (!isUnchanged()) {
1831 channels.push_back(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(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(new Animation::KeyPosition(toAnimationKey(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(targetChannel.keys[0]->value.toVector3D(), scale);
1862 };
1863
1864 if (!targetChannel.keys.isEmpty()) {
1865 if (!isUnchanged()) {
1866 channels.push_back(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(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(morphKeys[0].mNumValuesAndWeights, 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(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(weightKey, freq, targetId));
1898 targetChannel.keys.push_back(animationKey);
1899 }
1900 if (!targetChannel.keys.isEmpty()) {
1901 channels.push_back(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(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(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(&targetScene, options);
1931
1932 return QString();
1933}
1934
1936
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, scene);
1942}
1943
1944QString AssimpImporter::import(const QString &sourceFile, const QDir &savePath, const QJsonObject &options, QStringList *generatedFiles)
1945{
1946 QString errorString;
1947
1949
1950 // Load scene data
1951 auto sourceUrl = QUrl::fromLocalFile(sourceFile);
1952 errorString = importImp(sourceUrl, options, 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() +
1962 QStringLiteral(".qml");
1963 QFile targetFile(targetFileName);
1964 if (!targetFile.open(QIODevice::WriteOnly)) {
1965 errorString += QString("Could not write to file: ") + targetFileName;
1966 } else {
1967 QTextStream output(&targetFile);
1968 QSSGQmlUtilities::writeQml(scene, output, savePath, options);
1969 if (generatedFiles)
1970 generatedFiles->append(targetFileName);
1971 }
1972 scene.cleanup();
1973
1974 return errorString;
1975}
1976
#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
static aiPostProcessSteps processOptions(const QJsonObject &optionsObject, std::unique_ptr< Assimp::Importer > &importer)
static void setNodeProperties(QSSGSceneDesc::Node &target, const aiNode &source, const SceneInfo &sceneInfo, aiMatrix4x4 *transformCorrection)
static QString importImp(const QUrl &url, const QJsonObject &options, QSSGSceneDesc::Scene &targetScene)
static void setTextureProperties(QSSGSceneDesc::Texture &target, const TextureInfo &texInfo, const SceneInfo &sceneInfo)
size_t qHash(const TextureEntry &key, size_t seed)
static QSSGSceneDesc::Node * createSceneNode(const NodeInfo &nodeInfo, const aiNode &srcNode, QSSGSceneDesc::Node &parent, const SceneInfo &sceneInfo)
#define demonPostProcessPresets
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
qsizetype length() const noexcept
Same as size().
Definition qbytearray.h:479
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:19
static QChar separator()
Returns the native directory separator: "/" under Unix and "\\" under Windows.
Definition qdir.h:206
QString absolutePath() const
Returns the absolute path (a path that starts with "/" or with a drive specification),...
Definition qdir.cpp:667
QString absoluteFilePath(const QString &fileName) const
Returns the absolute path name of a file in the directory.
Definition qdir.cpp:809
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
QString completeBaseName() const
Returns the complete base name of the file without the path.
bool exists() const
Returns true if the file exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
const T & value() const noexcept
Returns the current item's value.
Definition qhash.h:1154
T & value() const noexcept
Returns a modifiable reference to the current item's value.
Definition qhash.h:1111
\inmodule QtCore
Definition qhash.h:818
const_iterator constFind(const Key &key) const noexcept
Definition qhash.h:1279
const_iterator constEnd() const noexcept
Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the ...
Definition qhash.h:1209
iterator find(const Key &key)
Returns an iterator pointing to the item with the key in the hash.
Definition qhash.h:1258
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
T value(const Key &key) const noexcept
Definition qhash.h:1044
iterator end() noexcept
Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item after the last ...
Definition qhash.h:1206
const_iterator cend() const noexcept
Definition qhash.h:1208
bool isEmpty() const noexcept
Returns true if the hash contains no items; otherwise returns false.
Definition qhash.h:926
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
\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:24
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
T & first()
Definition qlist.h:628
void push_back(parameter_type t)
Definition qlist.h:672
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
T value(qsizetype i) const
Definition qlist.h:661
qsizetype count() const noexcept
Definition qlist.h:387
void reserve(qsizetype size)
Definition qlist.h:746
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:25
The QQuaternion class represents a quaternion consisting of a vector and scalar.
Definition qquaternion.h:21
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 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)
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)
Definition qset.h:18
const_iterator cend() const noexcept
Definition qset.h:142
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:127
static QString fromLocal8Bit(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5788
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
QString first(qsizetype n) const
Definition qstring.h:337
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
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:7822
QByteArray toUtf8() const &
Definition qstring.h:563
\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:3354
bool isLocalFile() const
Definition qurl.cpp:3431
QString scheme() const
Returns the scheme of the URL.
Definition qurl.cpp:1983
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3411
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2465
constexpr size_type size() const noexcept
void push_back(const T &t)
void reserve(qsizetype sz)
\inmodule QtCore
Definition qvariant.h:64
T value() const &
Definition qvariant.h:511
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:531
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
void extension()
[6]
Definition dialogs.cpp:230
object setProperty("down", true)
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
std::pair< T1, T2 > QPair
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:287
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:303
size_t qHashBits(const void *p, size_t size, size_t seed) noexcept
Definition qhash.cpp:924
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
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 width
GLenum type
GLenum target
GLbitfield flags
GLenum GLuint texture
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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
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)
@ Q_PRIMITIVE_TYPE
Definition qtypeinfo.h:144
#define Q_DECLARE_TYPEINFO(TYPE, FLAGS)
Definition qtypeinfo.h:163
unsigned int quint32
Definition qtypes.h:45
short qint16
Definition qtypes.h:42
unsigned short quint16
Definition qtypes.h:43
size_t quintptr
Definition qtypes.h:72
int qint32
Definition qtypes.h:44
ptrdiff_t qsizetype
Definition qtypes.h:70
unsigned int uint
Definition qtypes.h:29
double qreal
Definition qtypes.h:92
unsigned char quint8
Definition qtypes.h:41
QT_BEGIN_NAMESPACE typedef uchar * output
QUrl url("example.com")
[constructor-url-reference]
QByteArray imageData
[15]
QGraphicsScene scene
[0]
QSSGSceneDesc::Skin * node
Mesh2SkinMap & mesh2skin
const aiScene & scene
TextureMap & textureMap
EmbeddedTextureMap & embeddedTextureMap
MaterialMap & materialMap
unsigned int magFilter
aiUVTransform transform
aiTextureMapping mapping
aiTextureMapMode modes[3]
unsigned int minFilter
Definition moc.h:24
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent