8#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
9#include <QtQuick3DGltf/private/qssggltfparser_p.h>
11#include <QtQuick3D/private/qquick3dmodel_p.h>
12#include <QtQuick3D/private/qquick3dmorphtarget_p.h>
13#include <QtQuick3D/private/qquick3dnode_p.h>
14#include <QtQuick3D/private/qquick3dorthographiccamera_p.h>
15#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
16#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
17#include <QtQuick3D/private/qquick3dskin_p.h>
18#include <QtQuick3D/private/qquick3dspecularglossymaterial_p.h>
19#include <QtQuick3D/private/qquick3dtexture_p.h>
21#include <QtQuick3DGltf/private/qssggltfaccessorreader_p.h>
22#include <QtQuick3DGltf/private/qssggltfresourceresolver_p.h>
23#include <QtQuick3DUtils/private/qssgutils_p.h>
25#if QT_CONFIG(concurrent)
26#include <QtConcurrent/qtconcurrentmap.h>
28#include <QtCore/qdir.h>
29#include <QtCore/qmath.h>
30#include <QtGui/qimagereader.h>
34using namespace QSSGGltf;
38bool checkBooleanOption(QLatin1StringView optionName,
const QJsonObject &options,
bool defaultValue =
false)
40 const auto opt = options.constFind(optionName);
41 if (opt == options.constEnd())
43 const auto value = opt->toObject().value(QLatin1String(
"value"));
44 return value.toBool(defaultValue);
47float getRealOption(QLatin1StringView optionName,
const QJsonObject &options,
float defaultValue)
49 const auto opt = options.constFind(optionName);
50 if (opt == options.constEnd())
52 const auto value = opt->toObject().value(QLatin1String(
"value"));
53 return float(value.toDouble(defaultValue));
56QString getStringOption(QLatin1StringView optionName,
const QJsonObject &options)
58 const auto opt = options.constFind(optionName);
59 if (opt == options.constEnd())
61 return opt->toObject().value(QLatin1String(
"value")).toString();
64void decomposeMatrix(
const QMatrix4x4 &matrix, QVector3D &translation, QQuaternion &rotation, QVector3D &scale)
66 translation = matrix.column(3).toVector3D();
68 QVector3D columns[3] = { matrix.column(0).toVector3D(),
69 matrix.column(1).toVector3D(),
70 matrix.column(2).toVector3D() };
71 scale = QVector3D(columns[0].length(), columns[1].length(), columns[2].length());
74 if (matrix.determinant() < 0.0f)
75 scale.setX(-scale.x());
77 for (
int i = 0; i < 3; ++i) {
78 const float axisScale = i == 0 ? scale.x() : (i == 1 ? scale.y() : scale.z());
79 if (!qFuzzyIsNull(axisScale))
80 columns[i] /= axisScale;
83 const float rotationValues[9] = {
84 columns[0].x(), columns[1].x(), columns[2].x(),
85 columns[0].y(), columns[1].y(), columns[2].y(),
86 columns[0].z(), columns[1].z(), columns[2].z()
88 rotation = QQuaternion::fromRotationMatrix(QMatrix3x3(rotationValues)).normalized();
94 static const bool supported = QImageReader::supportedImageFormats().contains(QByteArrayLiteral(
"webp"));
103 QJsonObject options = optionsObject;
104 if (
auto it = options.constFind(QLatin1String(
"options")); it != options.constEnd())
105 options = it->toObject();
108 result.designStudioWorkarounds = checkBooleanOption(QLatin1StringView(
"designStudioWorkarounds"), options);
109 result.joinIdenticalVertices = checkBooleanOption(QLatin1StringView(
"joinIdenticalVertices"), options,
true);
110 result.useFloatJointIndices = checkBooleanOption(QLatin1StringView(
"useFloatJointIndices"), options);
111 result.generateSmoothNormals = checkBooleanOption(QLatin1StringView(
"generateSmoothNormals"), options);
112 result.forceTangentGeneration = checkBooleanOption(QLatin1StringView(
"calculateTangentSpace"), options);
113 result.generateMipMaps = checkBooleanOption(QLatin1StringView(
"generateMipMaps"), options);
114 result.animationSampleRate = getRealOption(QLatin1StringView(
"animationSampleRate"), options, 30.0f);
115 result.materialVariant = getStringOption(QLatin1StringView(
"materialVariant"), options);
117 result.generateMeshLODs = checkBooleanOption(QLatin1StringView(
"generateMeshLevelsOfDetail"), options);
119 if (checkBooleanOption(QLatin1StringView(
"recalculateLodNormals"), options)) {
120 const float mergeAngle =
121 getRealOption(QLatin1StringView(
"recalculateLodNormalsMergeAngle"), options, 60.0f);
123 const float splitAngle =
124 getRealOption(QLatin1StringView(
"recalculateLodNormalsSplitAngle"), options, 25.0f);
132 if (checkBooleanOption(QLatin1StringView(
"globalScale"), options))
133 result.globalScaleValue = getRealOption(QLatin1StringView(
"globalScaleValue"), options, 1.0f);
139 const QFileInfo &sourceFile, QSSGSceneDesc::Scene &targetScene)
141 m_document = &document;
142 m_scene = &targetScene;
143 m_options = parseOptions(optionsObject);
144 if (!m_options.materialVariant.isEmpty()) {
145 m_options.materialVariantIndex =
int(document.materialVariants.indexOf(m_options.materialVariant));
146 if (m_options.materialVariantIndex < 0) {
148 qCWarning(lcQuick3DGltf) <<
"Asset has no material variant" << m_options.materialVariant
149 <<
"- available variants:" << document.materialVariants;
153 if (m_options.materialVariant.isEmpty())
154 targetScene.materialVariants = document.materialVariants;
157 m_materialMap.clear();
158 m_textureMap.clear();
159 m_textureDataMap.clear();
161 m_meshUsedPrimitives.clear();
162 m_meshMorphInfo.clear();
163 m_morphTargetMap.clear();
164 m_builtMeshes.clear();
166 targetScene.sourceDir = sourceFile.path();
168 targetScene.id = sourceFile.canonicalFilePath();
170 if (!targetScene.root) {
171 auto root =
new QSSGSceneDesc::Node(QByteArrayLiteral(
"Root"), QSSGSceneDesc::Node::Type::Transform,
172 QSSGSceneDesc::Node::RuntimeType::Node);
173 QSSGSceneDesc::addNode(targetScene, *root);
176 if (!qFuzzyCompare(m_options.globalScaleValue, 1.0f) && !qFuzzyIsNull(m_options.globalScaleValue)) {
177 const float gscale = m_options.globalScaleValue;
178 QSSGSceneDesc::setProperty(*targetScene.root,
"scale", &QQuick3DNode::setScale,
179 QVector3D { gscale, gscale, gscale });
182 int sceneIndex = document.scene;
183 if (sceneIndex < 0 && !document.scenes.isEmpty()) {
184 qCWarning(lcQuick3DGltf) <<
"Document has no default scene, using scene 0";
188 return QStringLiteral(
"Document contains no scene");
190 if (document.extensionsRequired.contains(QLatin1String(
"EXT_texture_webp")) && !isWebpSupported())
191 return QStringLiteral(
"Asset requires EXT_texture_webp, but no WebP image plugin is available");
195 QList<
int> meshIndexes(document.meshes.size());
196 std::iota(meshIndexes.begin(), meshIndexes.end(), 0);
197 const auto buildOneMesh = [&](
int meshIndex) {
199 result.mesh = GltfMeshBuilder::buildMesh(document, document.meshes.at(meshIndex), m_options,
200 &result.usedPrimitives, &result.morphInfo);
203#if QT_CONFIG(concurrent)
204 QList<BuiltMesh> built = QtConcurrent::blockingMapped(meshIndexes, buildOneMesh);
206 QList<BuiltMesh> built;
207 built.reserve(meshIndexes.size());
208 for (
const int meshIndex : meshIndexes)
209 built.append(buildOneMesh(meshIndex));
211 for (qsizetype meshIndex = 0; meshIndex < built.size(); ++meshIndex)
212 m_builtMeshes.insert(
int(meshIndex), std::move(built[meshIndex]));
215 for (
const int nodeIndex : document.scenes.at(sceneIndex).nodes)
216 processNode(nodeIndex, *targetScene.root);
227 for (
auto it = m_skinMap.constBegin(); it != m_skinMap.constEnd(); ++it) {
228 const Skin &source = m_document->skins.at(it.key());
229 QSSGSceneDesc::Skin *skinNode = it.value();
231 QVarLengthArray<QSSGSceneDesc::Node *> joints;
232 QList<QMatrix4x4> inverseBindPoses;
233 joints.reserve(source.joints.size());
235 const QList<
float> ibmData = QSSGGltfAccessorReader::readAsFloats(*m_document, source.inverseBindMatrices);
236 const bool hasBindMatrices = ibmData.size() >= source.joints.size() * 16;
238 for (qsizetype jointIndex = 0; jointIndex < source.joints.size(); ++jointIndex) {
239 QSSGSceneDesc::Node *jointNode = m_nodeMap.value(source.joints.at(jointIndex),
nullptr);
241 qCWarning(lcQuick3DGltf) <<
"Skin" << it.key() <<
"joint" << jointIndex
242 <<
"references a node outside the scene";
245 joints.push_back(jointNode);
246 if (hasBindMatrices) {
248 inverseBindPoses.append(QMatrix4x4(ibmData.constData() + jointIndex * 16).transposed());
250 inverseBindPoses.append(QMatrix4x4());
254 QSSGSceneDesc::setProperty(*skinNode,
"joints", &QQuick3DSkin::joints, joints);
255 QSSGSceneDesc::setProperty(*skinNode,
"inverseBindPoses", &QQuick3DSkin::setInverseBindPoses, inverseBindPoses);
261 using QSSGAnimation = QSSGSceneDesc::Animation;
263 const auto makeKey = [](
float timeMs,
const QVector4D &value, QSSGAnimation::KeyPosition::ValueType valueType) {
264 const auto flag = quint16(quint16(QSSGAnimation::KeyPosition::KeyType::Time) | quint16(valueType));
265 return new QSSGAnimation::KeyPosition { value, timeMs, flag };
268 for (qsizetype animationIndex = 0; animationIndex < m_document->animations.size(); ++animationIndex) {
269 const Animation &sourceAnimation = m_document->animations.at(animationIndex);
271 QSSGAnimation targetAnimation;
272 targetAnimation.name = sourceAnimation.name.isEmpty()
273 ? QByteArrayLiteral(
"animation") + QByteArray::number(animationIndex)
274 : sourceAnimation.name.toUtf8();
276 targetAnimation.framesPerSecond = 1000.0f;
278 for (
const AnimationChannel &channel : sourceAnimation.channels) {
279 QSSGSceneDesc::Node *targetNode = m_nodeMap.value(channel.targetNode,
nullptr);
283 if (channel.path == AnimationChannel::Path::Weights) {
285 const QList<QSSGSceneDesc::MorphTarget *> morphTargets = m_morphTargetMap.value(channel.targetNode);
286 if (morphTargets.isEmpty())
288 const Node &sourceNode = m_document->nodes.at(channel.targetNode);
289 int sourceTargetCount = 0;
290 for (
const MeshPrimitive &primitive : m_document->meshes.at(sourceNode.mesh).primitives)
291 sourceTargetCount = qMax(sourceTargetCount,
int(primitive.targets.size()));
292 if (sourceTargetCount == 0)
295 const AnimationSampler &weightSampler = sourceAnimation.samplers.at(channel.sampler);
297 const bool isCubic = weightSampler.interpolation == AnimationSampler::Interpolation::CubicSpline;
299 qCWarning(lcQuick3DGltf) <<
"Cubic spline morph weight animation is not supported, using linear";
300 const bool isStep = weightSampler.interpolation == AnimationSampler::Interpolation::Step;
301 const int valuesPerKey = isCubic ? 3 : 1;
302 const QList<
float> times = QSSGGltfAccessorReader::readAsFloats(*m_document, weightSampler.input);
303 const QList<
float> values = QSSGGltfAccessorReader::readAsFloats(*m_document, weightSampler.output);
304 const qsizetype keyCount =
305 qMin(qsizetype(times.size()), values.size() / (sourceTargetCount * valuesPerKey));
307 for (qsizetype targetIndex = 0; targetIndex < morphTargets.size(); ++targetIndex) {
308 auto *weightChannel =
new QSSGAnimation::Channel;
309 weightChannel->target = morphTargets.at(targetIndex);
310 weightChannel->targetProperty = QSSGAnimation::Channel::TargetProperty::Weight;
311 for (qsizetype key = 0; key < keyCount; ++key) {
312 const qsizetype block = key * valuesPerKey + (isCubic ? 1 : 0);
313 const QVector4D weight(values.value(block * sourceTargetCount + targetIndex), 0.0f, 0.0f, 0.0f);
314 weightChannel->keys.push_back(makeKey(times.at(key) * 1000.0f, weight,
315 QSSGAnimation::KeyPosition::ValueType::Number));
316 if (isStep && key + 1 < keyCount) {
317 const float holdTime = times.at(key + 1) * 1000.0f - 1.0f;
318 if (holdTime > times.at(key) * 1000.0f) {
319 weightChannel->keys.push_back(makeKey(holdTime, weight,
320 QSSGAnimation::KeyPosition::ValueType::Number));
324 if (weightChannel->keys.isEmpty()) {
325 delete weightChannel;
328 targetAnimation.length = qMax(targetAnimation.length, weightChannel->keys.last()->time);
329 targetAnimation.channels.push_back(weightChannel);
334 const AnimationSampler &sampler = sourceAnimation.samplers.at(channel.sampler);
335 const QList<
float> times = QSSGGltfAccessorReader::readAsFloats(*m_document, sampler.input);
336 const QList<
float> values = QSSGGltfAccessorReader::readAsFloats(*m_document, sampler.output);
340 const bool isRotation = channel.path == AnimationChannel::Path::Rotation;
341 const int components = isRotation ? 4 : 3;
342 const auto valueType = isRotation ? QSSGAnimation::KeyPosition::ValueType::Quaternion
343 : QSSGAnimation::KeyPosition::ValueType::Vec3;
346 const int valuesPerKey = sampler.interpolation == AnimationSampler::Interpolation::CubicSpline ? 3 : 1;
347 const qsizetype keyCount = qMin(qsizetype(times.size()), values.size() / (components * valuesPerKey));
351 const auto valueAt = [&](qsizetype key,
int part = 0) {
353 const qsizetype base = (key * valuesPerKey + (valuesPerKey == 3 ? part : 0)) * components;
354 return QVector4D(values.value(base), values.value(base + 1), values.value(base + 2),
355 components == 4 ? values.value(base + 3) : 0.0f);
358 QVector4D previousValue;
359 const auto fixQuaternionContinuity = [&](QVector4D value) {
360 if (isRotation && QVector4D::dotProduct(value, previousValue) < 0.0f)
362 previousValue = value;
366 auto *targetChannel =
new QSSGAnimation::Channel;
367 targetChannel->target = targetNode;
368 targetChannel->targetProperty = channel.path == AnimationChannel::Path::Translation
369 ? QSSGAnimation::Channel::TargetProperty::Position
370 : (isRotation ? QSSGAnimation::Channel::TargetProperty::Rotation
371 : QSSGAnimation::Channel::TargetProperty::Scale);
373 switch (sampler.interpolation) {
374 case AnimationSampler::Interpolation::Linear:
375 for (qsizetype key = 0; key < keyCount; ++key)
376 targetChannel->keys.push_back(makeKey(times.at(key) * 1000.0f,
377 fixQuaternionContinuity(valueAt(key, 1)), valueType));
379 case AnimationSampler::Interpolation::Step:
381 for (qsizetype key = 0; key < keyCount; ++key) {
382 const QVector4D value = fixQuaternionContinuity(valueAt(key, 1));
383 targetChannel->keys.push_back(makeKey(times.at(key) * 1000.0f, value, valueType));
384 if (key + 1 < keyCount) {
385 const float holdTime = times.at(key + 1) * 1000.0f - 1.0f;
386 if (holdTime > times.at(key) * 1000.0f)
387 targetChannel->keys.push_back(makeKey(holdTime, value, valueType));
391 case AnimationSampler::Interpolation::CubicSpline: {
393 constexpr qsizetype maxBakedKeys = 100000;
394 const float sampleRate = qMax(1.0f, m_options.animationSampleRate);
395 const float startTime = times.first();
396 const float endTime = times.last();
397 if (!qIsFinite(startTime) || !qIsFinite(endTime) || endTime < startTime) {
398 qCWarning(lcQuick3DGltf) <<
"Ignoring cubic spline animation channel with invalid key times";
399 delete targetChannel;
402 const double idealSamples = std::ceil(
double(endTime - startTime) *
double(sampleRate));
403 const qsizetype sampleCount =
404 qMax(qsizetype(1), qsizetype(qMin(idealSamples,
double(maxBakedKeys)))) + 1;
405 if (idealSamples >
double(maxBakedKeys)) {
406 qCWarning(lcQuick3DGltf) <<
"Cubic spline animation channel is" << (endTime - startTime)
407 <<
"seconds long; baking only" << maxBakedKeys <<
"keys";
409 qsizetype segment = 0;
410 for (qsizetype sample = 0; sample < sampleCount; ++sample) {
411 const float t = qMin(startTime + sample / sampleRate, endTime);
412 while (segment + 2 < keyCount && t >= times.at(segment + 1))
414 const float t0 = times.at(segment);
415 const float t1 = times.at(qMin(segment + 1, keyCount - 1));
416 const float dt = t1 - t0;
418 if (dt <= 0.0f || segment + 1 >= keyCount) {
419 value = valueAt(segment, 1);
422 const float u = (t - t0) / dt;
423 const float u2 = u * u, u3 = u2 * u;
424 const QVector4D p0 = valueAt(segment, 1);
425 const QVector4D m0 = valueAt(segment, 2) * dt;
426 const QVector4D p1 = valueAt(segment + 1, 1);
427 const QVector4D m1 = valueAt(segment + 1, 0) * dt;
428 value = (2 * u3 - 3 * u2 + 1) * p0 + (u3 - 2 * u2 + u) * m0
429 + (-2 * u3 + 3 * u2) * p1 + (u3 - u2) * m1;
433 targetChannel->keys.push_back(makeKey(t * 1000.0f, fixQuaternionContinuity(value), valueType));
439 if (targetChannel->keys.isEmpty()) {
440 delete targetChannel;
444 targetAnimation.length = qMax(targetAnimation.length, targetChannel->keys.last()->time);
445 targetAnimation.channels.push_back(targetChannel);
448 if (!targetAnimation.channels.isEmpty())
449 m_scene->animations.push_back(
new QSSGAnimation(targetAnimation));
460QSSGSceneDesc::Instancing *
GltfSceneConverter::convertInstancing(
const QSSGGltf::Node &source,
461 QSSGSceneDesc::Node &owner)
463 const QList<
float> translations = QSSGGltfAccessorReader::readAsFloats(*m_document, source.instanceTranslation);
464 const QList<
float> rotations = QSSGGltfAccessorReader::readAsFloats(*m_document, source.instanceRotation);
465 const QList<
float> scales = QSSGGltfAccessorReader::readAsFloats(*m_document, source.instanceScale);
467 const auto typeMatches = [
this](
int accessor, QSSGGltf::Accessor::Type expected,
const char *what) {
470 const QSSGGltf::Accessor::Type actual = m_document->accessors.at(accessor).type;
471 if (actual == expected)
473 qCWarning(lcQuick3DGltf) <<
"Ignoring instancing attribute" << what <<
"with unexpected accessor type";
476 const bool haveTranslation = typeMatches(source.instanceTranslation, QSSGGltf::Accessor::Type::Vec3,
478 const bool haveRotation = typeMatches(source.instanceRotation, QSSGGltf::Accessor::Type::Vec4,
"ROTATION");
479 const bool haveScale = typeMatches(source.instanceScale, QSSGGltf::Accessor::Type::Vec3,
"SCALE");
482 qsizetype count = -1;
483 const auto considerCount = [&count](qsizetype attributeCount) {
484 if (attributeCount >= 0)
485 count = count < 0 ? attributeCount : qMin(count, attributeCount);
487 if (source.instanceTranslation >= 0 && haveTranslation)
488 considerCount(translations.size() / 3);
489 if (source.instanceRotation >= 0 && haveRotation)
490 considerCount(rotations.size() / 4);
491 if (source.instanceScale >= 0 && haveScale)
492 considerCount(scales.size() / 3);
497 table.reserve(count * qsizetype(
sizeof(QQuick3DInstancing::InstanceTableEntry)));
498 for (qsizetype i = 0; i < count; ++i) {
500 QVector3D scale { 1.0f, 1.0f, 1.0f };
501 QQuaternion rotation;
502 if (source.instanceTranslation >= 0 && haveTranslation)
503 position = QVector3D(translations.at(i * 3), translations.at(i * 3 + 1), translations.at(i * 3 + 2));
504 if (source.instanceRotation >= 0 && haveRotation) {
506 rotation = QQuaternion(rotations.at(i * 4 + 3), rotations.at(i * 4), rotations.at(i * 4 + 1),
507 rotations.at(i * 4 + 2));
509 if (source.instanceScale >= 0 && haveScale)
510 scale = QVector3D(scales.at(i * 3), scales.at(i * 3 + 1), scales.at(i * 3 + 2));
511 const auto entry = InstanceTableEntryBuilder::calculateTableEntryFromQuaternion(position, scale, rotation,
512 QColor(Qt::white), {});
513 table.append(
reinterpret_cast<
const char *>(&entry),
sizeof(entry));
516 auto *instancing =
new QSSGSceneDesc::Instancing;
517 if (!source.name.isEmpty())
518 instancing->name = source.name.toUtf8();
519 QSSGSceneDesc::addNode(owner, *instancing);
520 instancing->instanceData = table;
521 instancing->instanceCount = count;
527 const Node &source = m_document->nodes.at(nodeIndex);
529 if (
int(source.camera >= 0) +
int(source.mesh >= 0) +
int(source.light >= 0) > 1) {
530 qCWarning(lcQuick3DGltf) <<
"Node" << nodeIndex <<
"has more than one of a camera, mesh and light;"
531 <<
"only its" << (source.camera >= 0 ?
"camera" :
"mesh") <<
"is imported";
534 QSSGSceneDesc::Node *target = createSceneNode(source);
538 QSSGSceneDesc::Node *instancedModel =
nullptr;
539 if (source.hasInstancing) {
540 if (target->nodeType == QSSGSceneDesc::Node::Type::Model) {
541 instancedModel = target;
542 target =
new QSSGSceneDesc::Node(QSSGSceneDesc::Node::Type::Transform,
543 QSSGSceneDesc::Node::RuntimeType::Node);
545 qCWarning(lcQuick3DGltf) <<
"Ignoring instancing on node" << nodeIndex <<
"without a mesh";
549 QSSGSceneDesc::addNode(parent, *target);
550 m_nodeMap.insert(nodeIndex, target);
552 if (instancedModel) {
553 QSSGSceneDesc::addNode(*target, *instancedModel);
554 setModelProperties(
static_cast<QSSGSceneDesc::Model &>(*instancedModel), source, nodeIndex);
555 if (QSSGSceneDesc::Instancing *instancing = convertInstancing(source, *instancedModel)) {
556 QSSGSceneDesc::setProperty(*instancedModel,
"instancing", &QQuick3DModel::setInstancing, instancing);
557 QSSGSceneDesc::setProperty(*instancedModel,
"instanceRoot", &QQuick3DModel::setInstanceRoot,
561 setNodeProperties(*target, source);
562 for (
const int child : source.children)
563 processNode(child, *target);
568 if (source.camera >= 0 && target->nodeType == QSSGSceneDesc::Node::Type::Camera)
569 setCameraProperties(
static_cast<QSSGSceneDesc::Camera &>(*target), m_document->cameras.at(source.camera));
570 else if (target->nodeType == QSSGSceneDesc::Node::Type::Model)
571 setModelProperties(
static_cast<QSSGSceneDesc::Model &>(*target), source, nodeIndex);
572 else if (source.light >= 0 && target->nodeType == QSSGSceneDesc::Node::Type::Light)
573 setLightProperties(
static_cast<QSSGSceneDesc::Light &>(*target), m_document->lights.at(source.light));
575 setNodeProperties(*target, source);
577 for (
const int child : source.children)
578 processNode(child, *target);
583 if (node.camera >= 0) {
584 const Camera &camera = m_document->cameras.at(node.camera);
585 const auto runtimeType = camera.type == Camera::Type::Orthographic
586 ? QSSGSceneDesc::Node::RuntimeType::OrthographicCamera
587 : QSSGSceneDesc::Node::RuntimeType::PerspectiveCamera;
588 return new QSSGSceneDesc::Camera(runtimeType);
592 return new QSSGSceneDesc::Model();
594 if (node.light >= 0) {
595 const Light &light = m_document->lights.at(node.light);
596 auto runtimeType = QSSGSceneDesc::Node::RuntimeType::DirectionalLight;
597 if (light.type == Light::Type::Point)
598 runtimeType = QSSGSceneDesc::Node::RuntimeType::PointLight;
599 else if (light.type == Light::Type::Spot)
600 runtimeType = QSSGSceneDesc::Node::RuntimeType::SpotLight;
601 return new QSSGSceneDesc::Light(runtimeType);
604 return new QSSGSceneDesc::Node(QSSGSceneDesc::Node::Type::Transform, QSSGSceneDesc::Node::RuntimeType::Node);
607void GltfSceneConverter::setNodeProperties(QSSGSceneDesc::Node &target,
const QSSGGltf::Node &source)
609 if (target.name.isNull()) {
610 if (!source.name.isEmpty())
611 target.name = source.name.toUtf8();
613 target.name = QSSGQmlUtilities::getQmlElementName(target);
616 QVector3D translation = source.translation;
617 QQuaternion rotation = source.rotation;
618 QVector3D scale = source.scale;
619 if (source.hasMatrix)
620 decomposeMatrix(source.matrix, translation, rotation, scale);
622 if (!m_options.designStudioWorkarounds) {
623 QSSGSceneDesc::setProperty(target,
"position", &QQuick3DNode::setPosition, translation);
625 QSSGSceneDesc::setProperty(target,
"x", &QQuick3DNode::setX, translation.x());
626 QSSGSceneDesc::setProperty(target,
"y", &QQuick3DNode::setY, translation.y());
627 QSSGSceneDesc::setProperty(target,
"z", &QQuick3DNode::setZ, translation.z());
630 QSSGSceneDesc::setProperty(target,
"rotation", &QQuick3DNode::setRotation, rotation);
631 QSSGSceneDesc::setProperty(target,
"scale", &QQuick3DNode::setScale, scale);
635 QSSGSceneDesc::setProperty(target,
"visible", &QQuick3DNode::setVisible,
false);
638void GltfSceneConverter::setModelProperties(QSSGSceneDesc::Model &target,
const QSSGGltf::Node &source,
int nodeIndex)
640 const Mesh &mesh = m_document->meshes.at(source.mesh);
643 QSSGSceneDesc::Mesh *meshNode = m_meshMap.value(source.mesh,
nullptr);
644 QList<
int> usedPrimitives;
646 BuiltMesh &builtMesh = m_builtMeshes[source.mesh];
647 const QList<
int> &builtUsedPrimitives = builtMesh.usedPrimitives;
649 usedPrimitives = builtUsedPrimitives;
650 if (!builtMesh.mesh.isValid()) {
651 qCWarning(lcQuick3DGltf) <<
"No usable geometry in mesh" << source.mesh;
654 m_scene->meshStorage.push_back(std::move(builtMesh.mesh));
655 const auto idx = m_scene->meshStorage.size() - 1;
657 QByteArray meshName = mesh.name.toUtf8();
658 if (meshName.isEmpty())
659 meshName = QByteArrayLiteral(
"mesh") + QByteArray::number(source.mesh);
660 meshNode =
new QSSGSceneDesc::Mesh(meshName, idx);
661 QSSGSceneDesc::addNode(target, *meshNode);
662 m_meshMap.insert(source.mesh, meshNode);
663 m_meshUsedPrimitives.insert(source.mesh, usedPrimitives);
664 m_meshMorphInfo.insert(source.mesh, morphInfo);
666 usedPrimitives = m_meshUsedPrimitives.value(source.mesh);
669 QSSGSceneDesc::setProperty(target,
"source", &QQuick3DModel::setSource, QVariant::fromValue(meshNode));
671 if (source.skin >= 0) {
673 QSSGSceneDesc::Skin *skinNode = m_skinMap.value(source.skin,
nullptr);
675 skinNode =
new QSSGSceneDesc::Skin;
676 QSSGSceneDesc::addNode(target, *skinNode);
677 m_skinMap.insert(source.skin, skinNode);
679 QSSGSceneDesc::setProperty(target,
"skin", &QQuick3DModel::setSkin, skinNode);
684 if (morphInfo
.count > 0) {
685 QQuick3DMorphTarget::MorphTargetAttributes attributes;
686 if (morphInfo.hasPositions)
687 attributes |= QQuick3DMorphTarget::MorphTargetAttribute::Position;
688 if (morphInfo.hasNormals)
689 attributes |= QQuick3DMorphTarget::MorphTargetAttribute::Normal;
690 if (morphInfo.hasTangents)
691 attributes |= QQuick3DMorphTarget::MorphTargetAttribute::Tangent;
693 const QList<
float> &weights = source.weights.isEmpty() ? mesh.weights : source.weights;
694 QVarLengthArray<QSSGSceneDesc::MorphTarget *> morphTargets;
695 QList<QSSGSceneDesc::MorphTarget *> morphTargetList;
696 morphTargets.reserve(morphInfo
.count);
697 for (
int targetIndex = 0; targetIndex < morphInfo
.count; ++targetIndex) {
698 auto *morphNode =
new QSSGSceneDesc::MorphTarget;
699 QSSGSceneDesc::addNode(target, *morphNode);
700 QSSGSceneDesc::setProperty(*morphNode,
"weight", &QQuick3DMorphTarget::setWeight,
701 weights.value(targetIndex, 0.0f));
702 QSSGSceneDesc::setProperty(*morphNode,
"attributes", &QQuick3DMorphTarget::setAttributes, attributes);
703 morphTargets.push_back(morphNode);
704 morphTargetList.append(morphNode);
706 QSSGSceneDesc::setProperty(target,
"morphTargets", &QQuick3DModel::morphTargets, morphTargets);
707 m_morphTargetMap.insert(nodeIndex, morphTargetList);
711 QVarLengthArray<QSSGSceneDesc::Material *> materials;
712 materials.reserve(usedPrimitives.size());
713 for (
const int primitiveIndex : std::as_const(usedPrimitives)) {
714 materials.push_back(ensureMaterial(
715 mesh.primitives.at(primitiveIndex).effectiveMaterial(m_options.materialVariantIndex), target));
720 const bool modelVaries = std::any_of(usedPrimitives.cbegin(), usedPrimitives.cend(),
721 [&mesh](
int primitiveIndex) {
722 return !mesh.primitives.at(primitiveIndex).variantMappings.isEmpty();
724 if (modelVaries && m_options.materialVariant.isEmpty()) {
725 for (QSSGSceneDesc::Material *material : std::as_const(materials))
726 target.defaultMaterials.append(material);
727 for (
int variant = 0; variant < m_document->materialVariants.size(); ++variant) {
728 QList<QSSGSceneDesc::Node *> variantList;
729 variantList.reserve(usedPrimitives.size());
730 for (
const int primitiveIndex : std::as_const(usedPrimitives))
731 variantList.append(ensureMaterial(mesh.primitives.at(primitiveIndex).effectiveMaterial(variant), target));
732 target.variantMaterials.append(variantList);
736 if (!materials.isEmpty())
737 QSSGSceneDesc::setProperty(target,
"materials", &QQuick3DModel::materials, materials);
740QSSGSceneDesc::Material *
GltfSceneConverter::ensureMaterial(
int materialIndex, QSSGSceneDesc::Node &owner)
742 if (
auto *material = m_materialMap.value(materialIndex,
nullptr))
746 auto runtimeType = QSSGSceneDesc::Material::RuntimeType::PrincipledMaterial;
747 if (materialIndex >= 0 && m_document->materials.at(materialIndex).specularGlossiness)
748 runtimeType = QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial;
750 auto *material =
new QSSGSceneDesc::Material(runtimeType);
751 QSSGSceneDesc::addNode(owner, *material);
753 if (materialIndex >= 0) {
754 setMaterialProperties(*material, m_document->materials.at(materialIndex));
757 material->name = QByteArrayLiteral(
"defaultMaterial");
758 QSSGSceneDesc::setProperty(*material,
"metalness", &QQuick3DPrincipledMaterial::setMetalness, 1.0f);
759 QSSGSceneDesc::setProperty(*material,
"roughness", &QQuick3DPrincipledMaterial::setRoughness, 1.0f);
762 m_materialMap.insert(materialIndex, material);
766void GltfSceneConverter::setMaterialProperties(QSSGSceneDesc::Material &target,
const QSSGGltf::Material &source)
768 using QSSGSceneDesc::setProperty;
770 if (target.name.isNull()) {
771 if (!source.name.isEmpty())
772 target.name = source.name.toUtf8();
774 target.name = QSSGQmlUtilities::getQmlElementName(target);
777 if (target.runtimeType == QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial) {
778 setSpecularGlossyProperties(target, source);
783 const QColor baseColor = QSSGUtils::color::linearTosRGB(source.baseColorFactor);
784 setProperty(target,
"baseColor", &QQuick3DPrincipledMaterial::setBaseColor, baseColor);
786 if (
auto *baseColorTexture = ensureTexture(source.baseColorTexture, target)) {
787 setProperty(target,
"baseColorMap", &QQuick3DPrincipledMaterial::setBaseColorMap, baseColorTexture);
788 setProperty(target,
"opacityChannel", &QQuick3DPrincipledMaterial::setOpacityChannel,
789 QQuick3DPrincipledMaterial::TextureChannelMapping::A);
792 setProperty(target,
"metalness", &QQuick3DPrincipledMaterial::setMetalness, source.metallicFactor);
793 setProperty(target,
"roughness", &QQuick3DPrincipledMaterial::setRoughness, source.roughnessFactor);
795 if (
auto *metallicRoughnessTexture = ensureTexture(source.metallicRoughnessTexture, target)) {
796 setProperty(target,
"metalnessMap", &QQuick3DPrincipledMaterial::setMetalnessMap, metallicRoughnessTexture);
797 setProperty(target,
"metalnessChannel", &QQuick3DPrincipledMaterial::setMetalnessChannel,
798 QQuick3DPrincipledMaterial::TextureChannelMapping::B);
799 setProperty(target,
"roughnessMap", &QQuick3DPrincipledMaterial::setRoughnessMap, metallicRoughnessTexture);
800 setProperty(target,
"roughnessChannel", &QQuick3DPrincipledMaterial::setRoughnessChannel,
801 QQuick3DPrincipledMaterial::TextureChannelMapping::G);
804 if (
auto *normalTexture = ensureTexture(source.normalTexture, target)) {
805 setProperty(target,
"normalMap", &QQuick3DPrincipledMaterial::setNormalMap, normalTexture);
806 setProperty(target,
"normalStrength", &QQuick3DPrincipledMaterial::setNormalStrength,
807 source.normalTexture.scaleOrStrength);
810 if (
auto *occlusionTexture = ensureTexture(source.occlusionTexture, target)) {
811 setProperty(target,
"occlusionMap", &QQuick3DPrincipledMaterial::setOcclusionMap, occlusionTexture);
812 setProperty(target,
"occlusionChannel", &QQuick3DPrincipledMaterial::setOcclusionChannel,
813 QQuick3DPrincipledMaterial::TextureChannelMapping::R);
814 setProperty(target,
"occlusionAmount", &QQuick3DPrincipledMaterial::setOcclusionAmount,
815 source.occlusionTexture.scaleOrStrength);
818 if (
auto *emissiveTexture = ensureTexture(source.emissiveTexture, target))
819 setProperty(target,
"emissiveMap", &QQuick3DPrincipledMaterial::setEmissiveMap, emissiveTexture);
821 const QVector3D emissiveFactor = source.emissiveFactor * source.emissiveStrength.value_or(1.0f);
822 if (!emissiveFactor.isNull())
823 setProperty(target,
"emissiveFactor", &QQuick3DPrincipledMaterial::setEmissiveFactor, emissiveFactor);
825 if (source.doubleSided) {
826 setProperty(target,
"cullMode", &QQuick3DPrincipledMaterial::setCullMode,
827 QQuick3DPrincipledMaterial::CullMode::NoCulling);
830 if (source.alphaMode == QSSGGltf::Material::AlphaMode::Mask) {
831 setProperty(target,
"alphaMode", &QQuick3DPrincipledMaterial::setAlphaMode,
832 QQuick3DPrincipledMaterial::AlphaMode::Mask);
833 setProperty(target,
"alphaCutoff", &QQuick3DPrincipledMaterial::setAlphaCutoff, source.alphaCutoff);
835 setProperty(target,
"depthDrawMode", &QQuick3DPrincipledMaterial::setDepthDrawMode,
836 QQuick3DMaterial::OpaquePrePassDepthDraw);
837 }
else if (source.alphaMode == QSSGGltf::Material::AlphaMode::Blend) {
838 setProperty(target,
"alphaMode", &QQuick3DPrincipledMaterial::setAlphaMode,
839 QQuick3DPrincipledMaterial::AlphaMode::Blend);
841 setProperty(target,
"alphaMode", &QQuick3DPrincipledMaterial::setAlphaMode,
842 QQuick3DPrincipledMaterial::AlphaMode::Opaque);
847 setProperty(target,
"lighting", &QQuick3DPrincipledMaterial::setLighting,
848 QQuick3DPrincipledMaterial::Lighting::NoLighting);
852 if (source.clearcoat) {
853 const QSSGGltf::Material::Clearcoat &clearcoat = *source.clearcoat;
854 setProperty(target,
"clearcoatAmount", &QQuick3DPrincipledMaterial::setClearcoatAmount,
855 clearcoat.clearcoatFactor);
856 setProperty(target,
"clearcoatRoughnessAmount", &QQuick3DPrincipledMaterial::setClearcoatRoughnessAmount,
857 clearcoat.clearcoatRoughnessFactor);
858 if (
auto *clearcoatTexture = ensureTexture(clearcoat.clearcoatTexture, target))
859 setProperty(target,
"clearcoatMap", &QQuick3DPrincipledMaterial::setClearcoatMap, clearcoatTexture);
860 if (
auto *clearcoatRoughnessTexture = ensureTexture(clearcoat.clearcoatRoughnessTexture, target)) {
861 setProperty(target,
"clearcoatRoughnessMap", &QQuick3DPrincipledMaterial::setClearcoatRoughnessMap,
862 clearcoatRoughnessTexture);
864 if (
auto *clearcoatNormalTexture = ensureTexture(clearcoat.clearcoatNormalTexture, target)) {
865 setProperty(target,
"clearcoatNormalMap", &QQuick3DPrincipledMaterial::setClearcoatNormalMap,
866 clearcoatNormalTexture);
867 setProperty(target,
"clearcoatNormalStrength", &QQuick3DPrincipledMaterial::setClearcoatNormalStrength,
868 clearcoat.clearcoatNormalTexture.scaleOrStrength);
873 if (source.transmission) {
874 setProperty(target,
"transmissionFactor", &QQuick3DPrincipledMaterial::setTransmissionFactor,
875 source.transmission->transmissionFactor);
876 if (
auto *transmissionTexture = ensureTexture(source.transmission->transmissionTexture, target))
877 setProperty(target,
"transmissionMap",
878 &QQuick3DPrincipledMaterial::setTransmissionMap, transmissionTexture);
883 const QSSGGltf::Material::Volume &volume = *source.volume;
884 setProperty(target,
"thicknessFactor", &QQuick3DPrincipledMaterial::setThicknessFactor, volume.thicknessFactor);
885 if (
auto *thicknessTexture = ensureTexture(volume.thicknessTexture, target))
886 setProperty(target,
"thicknessMap", &QQuick3DPrincipledMaterial::setThicknessMap, thicknessTexture);
887 if (volume.attenuationDistance > 0.0f) {
888 setProperty(target,
"attenuationDistance", &QQuick3DPrincipledMaterial::setAttenuationDistance,
889 volume.attenuationDistance);
891 setProperty(target,
"attenuationColor", &QQuick3DPrincipledMaterial::setAttenuationColor,
892 QColor::fromRgbF(volume.attenuationColor.x(), volume.attenuationColor.y(),
893 volume.attenuationColor.z()));
898 setProperty(target,
"indexOfRefraction", &QQuick3DPrincipledMaterial::setIndexOfRefraction, *source.ior);
901 if (source.specular) {
902 const QSSGGltf::Material::Specular &specular = *source.specular;
903 setProperty(target,
"specularAmount", &QQuick3DPrincipledMaterial::setSpecularAmount, specular.specularFactor);
904 if (
auto *specularColorTexture = ensureTexture(specular.specularColorTexture, target))
905 setProperty(target,
"specularMap", &QQuick3DPrincipledMaterial::setSpecularMap, specularColorTexture);
906 if (specular.specularColorFactor != QVector3D(1.0f, 1.0f, 1.0f))
907 qCWarning(lcQuick3DGltf) <<
"KHR_materials_specular specularColorFactor is not supported";
908 if (specular.specularTexture.isSet())
909 qCWarning(lcQuick3DGltf) <<
"KHR_materials_specular specularTexture is not supported";
914 const QSSGGltf::Material::Sheen &sheen = *source.sheen;
917 &QQuick3DPrincipledMaterial::setSheenColor,
918 QSSGUtils::color::linearTosRGB(QVector4D(sheen.sheenColorFactor, 1.0f)));
919 setProperty(target,
"sheenRoughness", &QQuick3DPrincipledMaterial::setSheenRoughness, sheen.sheenRoughnessFactor);
920 if (
auto *sheenColorTexture = ensureTexture(sheen.sheenColorTexture, target))
921 setProperty(target,
"sheenColorMap", &QQuick3DPrincipledMaterial::setSheenColorMap, sheenColorTexture);
923 if (
auto *sheenRoughnessTexture = ensureTexture(sheen.sheenRoughnessTexture, target)) {
924 setProperty(target,
"sheenRoughnessMap", &QQuick3DPrincipledMaterial::setSheenRoughnessMap, sheenRoughnessTexture);
929 if (source.anisotropy) {
930 const QSSGGltf::Material::Anisotropy &anisotropy = *source.anisotropy;
931 setProperty(target,
"anisotropyStrength", &QQuick3DPrincipledMaterial::setAnisotropyStrength, anisotropy.anisotropyStrength);
934 "anisotropyRotation",
935 &QQuick3DPrincipledMaterial::setAnisotropyRotation,
936 qRadiansToDegrees(anisotropy.anisotropyRotation));
937 if (
auto *anisotropyTexture = ensureTexture(anisotropy.anisotropyTexture, target))
938 setProperty(target,
"anisotropyMap", &QQuick3DPrincipledMaterial::setAnisotropyMap, anisotropyTexture);
942 if (source.iridescence) {
943 const QSSGGltf::Material::Iridescence &iridescence = *source.iridescence;
944 setProperty(target,
"iridescenceFactor", &QQuick3DPrincipledMaterial::setIridescenceFactor, iridescence.iridescenceFactor);
946 "iridescenceIndexOfRefraction",
947 &QQuick3DPrincipledMaterial::setIridescenceIndexOfRefraction,
948 iridescence.iridescenceIor);
950 "iridescenceThicknessMinimum",
951 &QQuick3DPrincipledMaterial::setIridescenceThicknessMinimum,
952 iridescence.iridescenceThicknessMinimum);
954 "iridescenceThicknessMaximum",
955 &QQuick3DPrincipledMaterial::setIridescenceThicknessMaximum,
956 iridescence.iridescenceThicknessMaximum);
958 if (
auto *iridescenceTexture = ensureTexture(iridescence.iridescenceTexture, target))
959 setProperty(target,
"iridescenceMap", &QQuick3DPrincipledMaterial::setIridescenceMap, iridescenceTexture);
960 if (
auto *thicknessTexture = ensureTexture(iridescence.iridescenceThicknessTexture, target)) {
961 setProperty(target,
"iridescenceThicknessMap", &QQuick3DPrincipledMaterial::setIridescenceThicknessMap, thicknessTexture);
966 if (source.dispersion)
967 setProperty(target,
"dispersion", &QQuick3DPrincipledMaterial::setDispersion, *source.dispersion);
970void GltfSceneConverter::setSpecularGlossyProperties(QSSGSceneDesc::Material &target,
const QSSGGltf::Material &source)
972 using QSSGSceneDesc::setProperty;
974 const QSSGGltf::Material::SpecularGlossiness &specularGlossiness = *source.specularGlossiness;
976 const QColor albedo = QSSGUtils::color::linearTosRGB(specularGlossiness.diffuseFactor);
977 setProperty(target,
"albedoColor", &QQuick3DSpecularGlossyMaterial::setAlbedoColor, albedo);
979 if (
auto *albedoTexture = ensureTexture(specularGlossiness.diffuseTexture, target)) {
980 setProperty(target,
"albedoMap", &QQuick3DSpecularGlossyMaterial::setAlbedoMap, albedoTexture);
981 setProperty(target,
"opacityChannel", &QQuick3DSpecularGlossyMaterial::setOpacityChannel,
982 QQuick3DSpecularGlossyMaterial::TextureChannelMapping::A);
985 const QColor specularColor = QSSGUtils::color::linearTosRGB(
986 QVector4D(specularGlossiness.specularFactor, 1.0f));
987 setProperty(target,
"specularColor", &QQuick3DSpecularGlossyMaterial::setSpecularColor, specularColor);
988 setProperty(target,
"glossiness", &QQuick3DSpecularGlossyMaterial::setGlossiness,
989 specularGlossiness.glossinessFactor);
991 if (
auto *specularGlossinessTexture = ensureTexture(specularGlossiness.specularGlossinessTexture, target)) {
992 setProperty(target,
"specularMap", &QQuick3DSpecularGlossyMaterial::setSpecularMap, specularGlossinessTexture);
993 setProperty(target,
"glossinessMap",
994 &QQuick3DSpecularGlossyMaterial::setGlossinessMap, specularGlossinessTexture);
995 setProperty(target,
"glossinessChannel", &QQuick3DSpecularGlossyMaterial::setGlossinessChannel,
996 QQuick3DSpecularGlossyMaterial::TextureChannelMapping::A);
999 if (
auto *normalTexture = ensureTexture(source.normalTexture, target)) {
1000 setProperty(target,
"normalMap", &QQuick3DSpecularGlossyMaterial::setNormalMap, normalTexture);
1001 setProperty(target,
"normalStrength", &QQuick3DSpecularGlossyMaterial::setNormalStrength,
1002 source.normalTexture.scaleOrStrength);
1005 if (
auto *occlusionTexture = ensureTexture(source.occlusionTexture, target)) {
1006 setProperty(target,
"occlusionMap", &QQuick3DSpecularGlossyMaterial::setOcclusionMap, occlusionTexture);
1007 setProperty(target,
"occlusionChannel", &QQuick3DSpecularGlossyMaterial::setOcclusionChannel,
1008 QQuick3DSpecularGlossyMaterial::TextureChannelMapping::R);
1009 setProperty(target,
"occlusionAmount", &QQuick3DSpecularGlossyMaterial::setOcclusionAmount,
1010 source.occlusionTexture.scaleOrStrength);
1013 if (
auto *emissiveTexture = ensureTexture(source.emissiveTexture, target))
1014 setProperty(target,
"emissiveMap", &QQuick3DSpecularGlossyMaterial::setEmissiveMap, emissiveTexture);
1015 const QVector3D emissiveFactor = source.emissiveFactor * source.emissiveStrength.value_or(1.0f);
1016 if (!emissiveFactor.isNull())
1017 setProperty(target,
"emissiveFactor", &QQuick3DSpecularGlossyMaterial::setEmissiveFactor, emissiveFactor);
1019 if (source.doubleSided) {
1020 setProperty(target,
"cullMode", &QQuick3DSpecularGlossyMaterial::setCullMode,
1021 QQuick3DSpecularGlossyMaterial::CullMode::NoCulling);
1024 if (source.alphaMode == QSSGGltf::Material::AlphaMode::Mask) {
1025 setProperty(target,
"alphaMode", &QQuick3DSpecularGlossyMaterial::setAlphaMode,
1026 QQuick3DSpecularGlossyMaterial::AlphaMode::Mask);
1027 setProperty(target,
"alphaCutoff", &QQuick3DSpecularGlossyMaterial::setAlphaCutoff, source.alphaCutoff);
1028 setProperty(target,
"depthDrawMode", &QQuick3DSpecularGlossyMaterial::setDepthDrawMode,
1029 QQuick3DMaterial::OpaquePrePassDepthDraw);
1030 }
else if (source.alphaMode == QSSGGltf::Material::AlphaMode::Blend) {
1031 setProperty(target,
"alphaMode", &QQuick3DSpecularGlossyMaterial::setAlphaMode,
1032 QQuick3DSpecularGlossyMaterial::AlphaMode::Blend);
1034 setProperty(target,
"alphaMode", &QQuick3DSpecularGlossyMaterial::setAlphaMode,
1035 QQuick3DSpecularGlossyMaterial::AlphaMode::Opaque);
1039 setProperty(target,
"lighting", &QQuick3DSpecularGlossyMaterial::setLighting,
1040 QQuick3DSpecularGlossyMaterial::Lighting::NoLighting);
1044QSSGSceneDesc::Texture *
GltfSceneConverter::ensureTexture(
const QSSGGltf::TextureInfo &textureInfo,
1045 QSSGSceneDesc::Node &owner)
1047 using QSSGSceneDesc::setProperty;
1049 if (!textureInfo.isSet() || textureInfo.index >= m_document->textures.size())
1052 const QSSGGltf::Texture &texture = m_document->textures.at(textureInfo.index);
1054 const bool webpSupported = isWebpSupported();
1055 const int sourceImage = (texture.webpSource >= 0 && (webpSupported || texture.source < 0))
1056 ? texture.webpSource
1058 if (sourceImage >= 0 && sourceImage == texture.webpSource && !webpSupported) {
1059 qCWarning(lcQuick3DGltf) <<
"Texture" << textureInfo.index
1060 <<
"has only a WebP image, but no WebP image plugin is available";
1062 if (sourceImage < 0 || sourceImage >= m_document->images.size()) {
1063 qCWarning(lcQuick3DGltf) <<
"Texture" << textureInfo.index <<
"has no image source";
1067 const int texCoord = textureInfo.transform && textureInfo.transform->texCoord >= 0
1068 ? textureInfo.transform->texCoord
1069 : textureInfo.texCoord;
1073 key += QByteArray::number(textureInfo.index);
1074 key +=
':' + QByteArray::number(texCoord);
1075 if (textureInfo.transform) {
1076 const QSSGGltf::TextureTransform &transform = *textureInfo.transform;
1077 key +=
':' + QByteArray::number(transform.offset.x()) +
',' + QByteArray::number(transform.offset.y());
1078 key +=
':' + QByteArray::number(transform.scale.x()) +
',' + QByteArray::number(transform.scale.y());
1079 key +=
':' + QByteArray::number(transform.rotation);
1081 if (
auto *cached = m_textureMap.value(key,
nullptr))
1084 const QSSGGltf::Image &image = m_document->images.at(sourceImage);
1087 QByteArray textureName;
1088 if (!QSSGGltfResourceResolver::isDataUri(image.uri))
1089 textureName = image.uri.toUtf8();
1090 if (textureName.isEmpty())
1091 textureName = texture.name.isEmpty() ? image.name.toUtf8() : texture.name.toUtf8();
1093 auto *sceneTexture =
new QSSGSceneDesc::Texture(QSSGSceneDesc::Texture::RuntimeType::Image2D, textureName);
1094 QSSGSceneDesc::addNode(owner, *sceneTexture);
1095 m_textureMap.insert(key, sceneTexture);
1097 setProperty(*sceneTexture,
"mappingMode", &QQuick3DTexture::setMappingMode, QQuick3DTexture::MappingMode::UV);
1100 setProperty(*sceneTexture,
"indexUV", &QQuick3DTexture::setIndexUV, qMin(texCoord, 1));
1103 QSSGGltf::Sampler sampler;
1104 if (texture.sampler >= 0 && texture.sampler < m_document->samplers.size())
1105 sampler = m_document->samplers.at(texture.sampler);
1107 static const auto asQtTilingMode = [](
int mode) {
1109 case QSSGGltf::Sampler::ClampToEdge:
1110 return QQuick3DTexture::TilingMode::ClampToEdge;
1111 case QSSGGltf::Sampler::MirroredRepeat:
1112 return QQuick3DTexture::TilingMode::MirroredRepeat;
1113 case QSSGGltf::Sampler::Repeat:
1115 return QQuick3DTexture::TilingMode::Repeat;
1118 setProperty(*sceneTexture,
"tilingModeHorizontal",
1119 &QQuick3DTexture::setHorizontalTiling, asQtTilingMode(sampler.wrapS));
1120 setProperty(*sceneTexture,
"tilingModeVertical",
1121 &QQuick3DTexture::setVerticalTiling, asQtTilingMode(sampler.wrapT));
1123 setProperty(*sceneTexture,
"magFilter", &QQuick3DTexture::setMagFilter,
1124 sampler.magFilter == QSSGGltf::Sampler::Nearest ? QQuick3DTexture::Filter::Nearest
1125 : QQuick3DTexture::Filter::Linear);
1127 auto minFilter = QQuick3DTexture::Filter::Linear;
1128 auto mipFilter = m_options.generateMipMaps ? QQuick3DTexture::Filter::Linear : QQuick3DTexture::Filter::None;
1129 switch (sampler.minFilter) {
1130 case QSSGGltf::Sampler::Nearest:
1131 minFilter = QQuick3DTexture::Filter::Nearest;
1133 case QSSGGltf::Sampler::NearestMipMapNearest:
1134 minFilter = QQuick3DTexture::Filter::Nearest;
1135 mipFilter = QQuick3DTexture::Filter::Nearest;
1137 case QSSGGltf::Sampler::LinearMipMapNearest:
1138 mipFilter = QQuick3DTexture::Filter::Nearest;
1140 case QSSGGltf::Sampler::NearestMipMapLinear:
1141 minFilter = QQuick3DTexture::Filter::Nearest;
1142 mipFilter = QQuick3DTexture::Filter::Linear;
1144 case QSSGGltf::Sampler::LinearMipMapLinear:
1145 mipFilter = QQuick3DTexture::Filter::Linear;
1147 case QSSGGltf::Sampler::Linear:
1151 setProperty(*sceneTexture,
"minFilter", &QQuick3DTexture::setMinFilter, minFilter);
1152 if (mipFilter != QQuick3DTexture::Filter::None) {
1153 setProperty(*sceneTexture,
"generateMipmaps", &QQuick3DTexture::setGenerateMipmaps,
true);
1154 setProperty(*sceneTexture,
"mipFilter", &QQuick3DTexture::setMipFilter, mipFilter);
1158 if (textureInfo.transform) {
1159 const QSSGGltf::TextureTransform &transform = *textureInfo.transform;
1160 setProperty(*sceneTexture,
"pivotV", &QQuick3DTexture::setPivotV, 1.0f);
1161 setProperty(*sceneTexture,
"positionU", &QQuick3DTexture::setPositionU, transform.offset.x());
1162 setProperty(*sceneTexture,
"positionV", &QQuick3DTexture::setPositionV, -transform.offset.y());
1163 setProperty(*sceneTexture,
"rotationUV", &QQuick3DTexture::setRotationUV,
1164 float(qRadiansToDegrees(transform.rotation)));
1165 setProperty(*sceneTexture,
"scaleU", &QQuick3DTexture::setScaleU, transform.scale.x());
1166 setProperty(*sceneTexture,
"scaleV", &QQuick3DTexture::setScaleV, transform.scale.y());
1170 const bool isEmbedded = image.bufferView >= 0 || QSSGGltfResourceResolver::isDataUri(image.uri);
1172 QString relativePath = image.uri;
1173 relativePath.replace(QLatin1Char(
'\\'), QLatin1Char(
'/'));
1174 const QString resolved = QSSGGltfResourceResolver::resolveFilePath(relativePath, QString());
1175 if (resolved.isEmpty()) {
1176 qCWarning(lcQuick3DGltf) <<
"Ignoring image URI outside the asset directory:" << image.uri;
1177 return sceneTexture;
1179 const QString path = QDir(m_document->baseDir).absoluteFilePath(resolved);
1180 setProperty(*sceneTexture,
"source", &QQuick3DTexture::setSource, QUrl { path });
1181 return sceneTexture;
1184 QSSGSceneDesc::TextureData *textureData = m_textureDataMap.value(sourceImage,
nullptr);
1186 QByteArray imageData;
1187 QString mimeType = image.mimeType;
1188 if (image.bufferView >= 0) {
1189 const QSSGGltf::BufferView &view = m_document->bufferViews.at(image.bufferView);
1190 imageData = m_document->buffers.at(view.buffer).data.mid(view.byteOffset, view.byteLength);
1193 imageData = QSSGGltfResourceResolver::decodeDataUri(image.uri, &error);
1194 if (mimeType.isEmpty()) {
1195 const qsizetype header = image.uri.indexOf(QLatin1Char(
','));
1197 mimeType = image.uri.mid(5, header - 5).section(QLatin1Char(
';'), 0, 0);
1200 if (imageData.isEmpty()) {
1201 qCWarning(lcQuick3DGltf) <<
"Failed to load embedded image" << sourceImage;
1202 return sceneTexture;
1206 QByteArray format = mimeType.section(QLatin1Char(
'/'), 1, 1).trimmed().toLower().toLatin1();
1207 if (format == QByteArrayLiteral(
"jpeg"))
1208 format = QByteArrayLiteral(
"jpg");
1209 else if (format.isEmpty())
1210 format = QByteArrayLiteral(
"png");
1211 textureData =
new QSSGSceneDesc::TextureData(imageData, QSize(), format,
1212 quint8(QSSGSceneDesc::TextureData::Flags::Compressed));
1213 QSSGSceneDesc::addNode(*sceneTexture, *textureData);
1214 m_textureDataMap.insert(sourceImage, textureData);
1216 setProperty(*sceneTexture,
"textureData", &QQuick3DTexture::setTextureData, textureData);
1218 return sceneTexture;
1221void GltfSceneConverter::setLightProperties(QSSGSceneDesc::Light &target,
const QSSGGltf::Light &source)
1223 using QSSGSceneDesc::setProperty;
1226 const QVector3D scaledColor = source.color * source.intensity;
1227 const float brightness = qMax(qMax(1.0f, scaledColor.x()), qMax(scaledColor.y(), scaledColor.z()));
1228 const QColor color = QColor::fromRgbF(scaledColor.x() / brightness, scaledColor.y() / brightness,
1229 scaledColor.z() / brightness);
1230 setProperty(target,
"color", &QQuick3DAbstractLight::setColor, color);
1231 setProperty(target,
"brightness", &QQuick3DAbstractLight::setBrightness, brightness);
1234 if (source.type == Light::Type::Point) {
1235 setProperty(target,
"linearFade", &QQuick3DPointLight::setLinearFade, 0.0f);
1236 setProperty(target,
"quadraticFade", &QQuick3DPointLight::setQuadraticFade, 10000.0f);
1237 }
else if (source.type == Light::Type::Spot) {
1238 setProperty(target,
"linearFade", &QQuick3DSpotLight::setLinearFade, 0.0f);
1239 setProperty(target,
"quadraticFade", &QQuick3DSpotLight::setQuadraticFade, 10000.0f);
1241 setProperty(target,
"coneAngle", &QQuick3DSpotLight::setConeAngle,
1242 float(qRadiansToDegrees(source.outerConeAngle) * 2.0));
1243 setProperty(target,
"innerConeAngle", &QQuick3DSpotLight::setInnerConeAngle,
1244 float(qRadiansToDegrees(source.innerConeAngle) * 2.0));
1248void GltfSceneConverter::setCameraProperties(QSSGSceneDesc::Camera &target,
const QSSGGltf::Camera &source)
1250 using QSSGSceneDesc::setProperty;
1252 if (target.runtimeType == QSSGSceneDesc::Node::RuntimeType::PerspectiveCamera) {
1253 setProperty(target,
"clipNear", &QQuick3DPerspectiveCamera::setClipNear, source.znear);
1255 const float clipFar = source.zfar > 0.0f ? source.zfar : 10000.0f;
1256 setProperty(target,
"clipFar", &QQuick3DPerspectiveCamera::setClipFar, clipFar);
1257 setProperty(target,
"fieldOfView", &QQuick3DPerspectiveCamera::setFieldOfView,
1258 float(qRadiansToDegrees(source.yfov)));
1260 setProperty(target,
"clipNear", &QQuick3DOrthographicCamera::setClipNear, source.znear);
1262 const float clipFar = source.zfar > 0.0f ? source.zfar : 10000.0f;
1263 setProperty(target,
"clipFar", &QQuick3DOrthographicCamera::setClipFar, clipFar);
1265 setProperty(target,
"horizontalMagnification", &QQuick3DOrthographicCamera::setHorizontalMagnification,
1266 source.xmag * 2.0f);
1267 setProperty(target,
"verticalMagnification", &QQuick3DOrthographicCamera::setVerticalMagnification,
1268 source.ymag * 2.0f);
QString convert(const QSSGGltfDocument &document, const QJsonObject &optionsObject, const QFileInfo &sourceFile, QSSGSceneDesc::Scene &targetScene)
Combined button and popup list for selecting options.
float lodNormalSplitAngle
float lodNormalMergeAngle