337bool QSSGGltfParser::parse(
const QByteArray &data,
const QString &baseDir, QSSGGltfDocument *document)
340 m_errorMessage.clear();
341 *document = QSSGGltfDocument();
342 document->baseDir = baseDir;
344 QByteArray json = data;
348 if (data.size() >= 12 && qFromLittleEndian<quint32>(data.constData()) == GLB_MAGIC) {
349 const quint32 version = qFromLittleEndian<quint32>(data.constData() + 4);
351 return setError(QStringLiteral(
"Unsupported GLB container version %1").arg(version));
352 const quint32 length = qFromLittleEndian<quint32>(data.constData() + 8);
353 if (qint64(length) > data.size())
354 return setError(QStringLiteral(
"Truncated GLB file: header declares %1 bytes, got %2")
355 .arg(length).arg(data.size()));
359 while (offset + 8 <= qint64(length)) {
360 const quint32 chunkLength = qFromLittleEndian<quint32>(data.constData() + offset);
361 const quint32 chunkType = qFromLittleEndian<quint32>(data.constData() + offset + 4);
363 if (offset + qint64(chunkLength) > qint64(length))
364 return setError(QStringLiteral(
"Truncated GLB chunk at offset %1").arg(offset - 8));
366 if (chunkType == GLB_CHUNK_JSON && json.isEmpty())
367 json = data.mid(offset, chunkLength);
368 else if (chunkType == GLB_CHUNK_BIN && binChunk.isEmpty())
369 binChunk = data.mid(offset, chunkLength);
371 qCWarning(lcQuick3DGltf) <<
"Skipping unknown GLB chunk type" << Qt::hex << chunkType;
374 offset += chunkLength;
376 offset += 4 - (offset % 4);
379 return setError(QStringLiteral(
"GLB container has no JSON chunk"));
382 QJsonParseError jsonError;
383 const QJsonDocument jsonDocument = QJsonDocument::fromJson(json, &jsonError);
384 if (jsonDocument.isNull())
385 return setError(QStringLiteral(
"Invalid glTF JSON: %1").arg(jsonError.errorString()));
386 if (!jsonDocument.isObject())
387 return setError(QStringLiteral(
"Invalid glTF JSON: root is not an object"));
389 const QJsonObject root = jsonDocument.object();
393 const QJsonValue assetValue = root.value(QLatin1String(
"asset"));
394 if (!assetValue.isObject())
395 return setError(QStringLiteral(
"Not a glTF document: no asset object"));
396 const QJsonObject asset = assetValue.toObject();
397 document->asset.version = asset.value(QLatin1String(
"version")).toString();
398 document->asset.minVersion = asset.value(QLatin1String(
"minVersion")).toString();
399 document->asset.generator = asset.value(QLatin1String(
"generator")).toString();
400 document->asset.copyright = asset.value(QLatin1String(
"copyright")).toString();
402 const int major = document->asset.version.section(QLatin1Char(
'.'), 0, 0).toInt();
404 return setError(QStringLiteral(
"Unsupported glTF version '%1'").arg(document->asset.version));
409 if (!document->asset.minVersion.isEmpty()) {
410 const int minMajor = document->asset.minVersion.section(QLatin1Char(
'.'), 0, 0).toInt();
411 const int minMinor = document->asset.minVersion.section(QLatin1Char(
'.'), 1, 1).toInt();
412 if (minMajor != 2 || minMinor > 0)
413 return setError(QStringLiteral(
"Asset requires glTF version '%1' or higher")
414 .arg(document->asset.minVersion));
419 for (
const auto &value : root.value(QLatin1String(
"extensionsUsed")).toArray())
420 document->extensionsUsed.append(value.toString());
421 for (
const auto &value : root.value(QLatin1String(
"extensionsRequired")).toArray())
422 document->extensionsRequired.append(value.toString());
423 document->rootExtensions = root.value(QLatin1String(
"extensions")).toObject();
427 const QJsonValue variantsExt = document->rootExtensions.value(QLatin1String(
"KHR_materials_variants"));
428 if (variantsExt.isObject()) {
429 for (
const auto &value : variantsExt.toObject().value(QLatin1String(
"variants")).toArray())
430 document->materialVariants.append(value.toObject().value(QLatin1String(
"name")).toString());
434 const QStringList supported = supportedExtensions();
435 for (
const QString &required : std::as_const(document->extensionsRequired)) {
436 if (!supported.contains(required))
437 return setError(QStringLiteral(
"Asset requires unsupported glTF extension '%1'").arg(required));
439 for (
const QString &used : std::as_const(document->extensionsUsed)) {
440 if (!supported.contains(used))
441 qCWarning(lcQuick3DGltf) <<
"Ignoring unsupported glTF extension" << used;
445 for (
const auto &value : root.value(QLatin1String(
"buffers")).toArray()) {
446 const QJsonObject object = value.toObject();
448 buffer.uri = object.value(QLatin1String(
"uri")).toString();
449 buffer.byteLength = qint64(object.value(QLatin1String(
"byteLength")).toDouble());
450 if (buffer.byteLength < 0)
451 return setError(QStringLiteral(
"Buffer %1 has negative byte length").arg(document->buffers.size()));
452 buffer.meshoptFallback = object.value(QLatin1String(
"extensions")).toObject()
453 .value(QLatin1String(
"EXT_meshopt_compression")).toObject()
454 .value(QLatin1String(
"fallback")).toBool(
false);
455 if (buffer.meshoptFallback) {
456 document->buffers.append(buffer);
459 if (buffer.uri.isEmpty()) {
461 if (document->buffers.isEmpty() && !binChunk.isEmpty())
462 buffer.data = binChunk;
463 else if (binChunk.isEmpty())
465 QStringLiteral(
"Buffer %1 has no URI and there is no GLB BIN chunk")
466 .arg(document->buffers.size()));
468 return setError(QStringLiteral(
"Only the first buffer may refer to the GLB BIN chunk"));
470 QString resolveError;
471 buffer.data = QSSGGltfResourceResolver::loadUri(buffer.uri, baseDir, &resolveError);
475 if (!resolveError.isEmpty())
476 return setError(QStringLiteral(
"Failed to load buffer %1: %2")
477 .arg(document->buffers.size()).arg(resolveError));
479 if (buffer.data.size() < buffer.byteLength)
480 return setError(QStringLiteral(
"Buffer %1 is %2 bytes, expected at least %3")
481 .arg(document->buffers.size()).arg(buffer.data.size()).arg(buffer.byteLength));
482 document->buffers.append(buffer);
486 MeshoptDecodeState meshoptState;
487 for (
const auto &value : root.value(QLatin1String(
"bufferViews")).toArray()) {
488 const QJsonObject object = value.toObject();
490 view.buffer = object.value(QLatin1String(
"buffer")).toInt(-1);
491 view.byteOffset = qint64(object.value(QLatin1String(
"byteOffset")).toDouble(0));
492 view.byteLength = qint64(object.value(QLatin1String(
"byteLength")).toDouble());
493 view.byteStride = object.value(QLatin1String(
"byteStride")).toInt(0);
494 view.target = object.value(QLatin1String(
"target")).toInt(0);
495 view.name = object.value(QLatin1String(
"name")).toString();
497 const QJsonValue meshoptValue = object.value(QLatin1String(
"extensions")).toObject()
498 .value(QLatin1String(
"EXT_meshopt_compression"));
499 if (meshoptValue.isObject()) {
501 if (!decodeMeshoptBufferView(meshoptValue.toObject(), *document, view, meshoptState, &decodeError)) {
502 return setError(QStringLiteral(
"Buffer view %1: EXT_meshopt_compression: %2")
503 .arg(document->bufferViews.size()).arg(decodeError));
505 document->bufferViews.append(view);
509 if (view.buffer < 0 || view.buffer >= document->buffers.size())
511 QStringLiteral(
"Buffer view %1 references invalid buffer %2")
512 .arg(document->bufferViews.size()).arg(view.buffer));
513 if (document->buffers.at(view.buffer).meshoptFallback)
514 return setError(QStringLiteral(
"Buffer view %1 references unloaded fallback buffer %2")
515 .arg(document->bufferViews.size()).arg(view.buffer));
519 if (view.byteOffset < 0 || view.byteLength < 0 || view.byteStride < 0 || view.byteStride > 252)
520 return setError(QStringLiteral(
"Buffer view %1 has an invalid byte offset, length, or stride")
521 .arg(document->bufferViews.size()));
525 const qint64 bufferLength = document->buffers.at(view.buffer).byteLength;
526 if (view.byteLength > bufferLength || view.byteOffset > bufferLength - view.byteLength)
528 QStringLiteral(
"Buffer view %1 extends past the end of buffer %2")
529 .arg(document->bufferViews.size()).arg(view.buffer));
530 document->bufferViews.append(view);
534 for (
const auto &value : root.value(QLatin1String(
"accessors")).toArray()) {
535 const QJsonObject object = value.toObject();
537 accessor.bufferView = object.value(QLatin1String(
"bufferView")).toInt(-1);
538 accessor.byteOffset = qint64(object.value(QLatin1String(
"byteOffset")).toDouble(0));
539 const int componentType = object.value(QLatin1String(
"componentType")).toInt();
540 if (!isValidComponentType(componentType))
541 return setError(QStringLiteral(
"Accessor %1 has invalid component type %2")
542 .arg(document->accessors.size()).arg(componentType));
543 accessor.componentType = Accessor::ComponentType(componentType);
545 accessor.type = accessorTypeFromString(object.value(QLatin1String(
"type")).toString(), &typeOk);
547 return setError(QStringLiteral(
"Accessor %1 has invalid type '%2'")
548 .arg(document->accessors.size())
549 .arg(object.value(QLatin1String(
"type")).toString()));
558 constexpr qint64 maxAccessorElements = qint64(256) * 1024 * 1024;
559 const double rawCount = object.value(QLatin1String(
"count")).toDouble();
560 if (!(rawCount >= 0.0 && rawCount <=
double(maxAccessorElements)))
561 return setError(QStringLiteral(
"Accessor %1 has a negative or unreasonably large count")
562 .arg(document->accessors.size()));
563 accessor.count = qint64(rawCount);
564 if (accessor.byteOffset < 0)
565 return setError(QStringLiteral(
"Accessor %1 has a negative byte offset")
566 .arg(document->accessors.size()));
567 accessor.normalized = object.value(QLatin1String(
"normalized")).toBool(
false);
568 accessor.name = object.value(QLatin1String(
"name")).toString();
569 for (
const auto &m : object.value(QLatin1String(
"min")).toArray())
570 accessor.min.append(m.toDouble());
571 for (
const auto &m : object.value(QLatin1String(
"max")).toArray())
572 accessor.max.append(m.toDouble());
574 const QJsonValue sparseValue = object.value(QLatin1String(
"sparse"));
575 if (sparseValue.isObject()) {
576 const QJsonObject sparseObject = sparseValue.toObject();
577 Accessor::Sparse sparse;
580 const double rawSparseCount = sparseObject.value(QLatin1String(
"count")).toDouble();
581 if (!(rawSparseCount >= 0.0 && rawSparseCount <=
double(maxAccessorElements)))
582 return setError(QStringLiteral(
"Accessor %1 has a negative or unreasonably large sparse count")
583 .arg(document->accessors.size()));
584 sparse.count = qint64(rawSparseCount);
585 const QJsonObject indices = sparseObject.value(QLatin1String(
"indices")).toObject();
586 sparse.indicesBufferView = indices.value(QLatin1String(
"bufferView")).toInt(-1);
587 sparse.indicesByteOffset = qint64(indices.value(QLatin1String(
"byteOffset")).toDouble(0));
588 const int indicesComponentType = indices.value(QLatin1String(
"componentType")).toInt();
589 if (!isValidComponentType(indicesComponentType))
591 QStringLiteral(
"Accessor %1 sparse indices have invalid component type")
592 .arg(document->accessors.size()));
593 sparse.indicesComponentType = Accessor::ComponentType(indicesComponentType);
594 const QJsonObject values = sparseObject.value(QLatin1String(
"values")).toObject();
595 sparse.valuesBufferView = values.value(QLatin1String(
"bufferView")).toInt(-1);
596 sparse.valuesByteOffset = qint64(values.value(QLatin1String(
"byteOffset")).toDouble(0));
598 if (sparse.indicesBufferView < 0 || sparse.indicesBufferView >= document->bufferViews.size()
599 || sparse.valuesBufferView < 0 || sparse.valuesBufferView >= document->bufferViews.size()) {
600 return setError(QStringLiteral(
"Accessor %1 sparse data references an invalid buffer view")
601 .arg(document->accessors.size()));
603 if (sparse.count > accessor.count
604 || sparse.indicesByteOffset < 0 || sparse.valuesByteOffset < 0) {
605 return setError(QStringLiteral(
"Accessor %1 has an invalid sparse count or byte offset")
606 .arg(document->accessors.size()));
609 const BufferView &indicesView = document->bufferViews.at(sparse.indicesBufferView);
610 const BufferView &valuesView = document->bufferViews.at(sparse.valuesBufferView);
611 const qint64 indicesSpan =
612 sparse.count * Accessor::componentByteSize(sparse.indicesComponentType);
613 const qint64 valuesSpan = sparse.count * accessor.elementByteSize();
614 if (indicesSpan > indicesView.byteLength
615 || sparse.indicesByteOffset > indicesView.byteLength - indicesSpan
616 || valuesSpan > valuesView.byteLength
617 || sparse.valuesByteOffset > valuesView.byteLength - valuesSpan) {
619 QStringLiteral(
"Accessor %1 sparse data extends past the end of its buffer view")
620 .arg(document->accessors.size()));
622 accessor.sparse = sparse;
625 if (accessor.bufferView >= document->bufferViews.size())
627 QStringLiteral(
"Accessor %1 references invalid buffer view %2")
628 .arg(document->accessors.size()).arg(accessor.bufferView));
629 if (accessor.bufferView >= 0) {
630 const BufferView &view = document->bufferViews.at(accessor.bufferView);
631 const qint64 elementSize = accessor.elementByteSize();
632 const qint64 stride = view.byteStride > 0 ? view.byteStride : elementSize;
634 const qint64 span = (accessor.count - 1) * stride + elementSize;
635 if (accessor.count > 0
636 && (span > view.byteLength || accessor.byteOffset > view.byteLength - span))
637 return setError(QStringLiteral(
"Accessor %1 extends past the end of buffer view %2")
638 .arg(document->accessors.size())
639 .arg(accessor.bufferView));
645 constexpr qint64 maxSyntheticAccessorBytes = qint64(256) * 1024 * 1024;
646 if (accessor.count * accessor.elementByteSize() > maxSyntheticAccessorBytes)
647 return setError(QStringLiteral(
"Accessor %1 has no buffer view and an unreasonably large count")
648 .arg(document->accessors.size()));
650 document->accessors.append(accessor);
654 for (
const auto &value : root.value(QLatin1String(
"images")).toArray()) {
655 const QJsonObject object = value.toObject();
657 image.uri = object.value(QLatin1String(
"uri")).toString();
658 image.bufferView = object.value(QLatin1String(
"bufferView")).toInt(-1);
659 image.mimeType = object.value(QLatin1String(
"mimeType")).toString();
660 image.name = object.value(QLatin1String(
"name")).toString();
661 if (image.bufferView >= document->bufferViews.size())
662 return setError(QStringLiteral(
"Image %1 references invalid buffer view %2")
663 .arg(document->images.size()).arg(image.bufferView));
664 document->images.append(image);
668 for (
const auto &value : root.value(QLatin1String(
"samplers")).toArray()) {
669 const QJsonObject object = value.toObject();
671 sampler.magFilter = object.value(QLatin1String(
"magFilter")).toInt(0);
672 sampler.minFilter = object.value(QLatin1String(
"minFilter")).toInt(0);
673 sampler.wrapS = object.value(QLatin1String(
"wrapS")).toInt(Sampler::Repeat);
674 sampler.wrapT = object.value(QLatin1String(
"wrapT")).toInt(Sampler::Repeat);
675 sampler.name = object.value(QLatin1String(
"name")).toString();
676 document->samplers.append(sampler);
680 for (
const auto &value : root.value(QLatin1String(
"textures")).toArray()) {
681 const QJsonObject object = value.toObject();
683 texture.sampler = object.value(QLatin1String(
"sampler")).toInt(-1);
684 texture.source = object.value(QLatin1String(
"source")).toInt(-1);
685 texture.name = object.value(QLatin1String(
"name")).toString();
686 texture.extensions = object.value(QLatin1String(
"extensions")).toObject();
688 const QJsonValue webpExt = texture.extensions.value(QLatin1String(
"EXT_texture_webp"));
689 if (webpExt.isObject())
690 texture.webpSource = webpExt.toObject().value(QLatin1String(
"source")).toInt(-1);
692 if (texture.sampler >= document->samplers.size())
693 return setError(QStringLiteral(
"Texture %1 references invalid sampler %2")
694 .arg(document->textures.size()).arg(texture.sampler));
695 if (texture.source >= document->images.size() || texture.webpSource >= document->images.size())
696 return setError(QStringLiteral(
"Texture %1 references invalid image %2")
697 .arg(document->textures.size())
698 .arg(qMax(texture.source, texture.webpSource)));
699 document->textures.append(texture);
708 int badTextureIndex = -1;
709 const auto parseTexture = [&](
const QJsonObject &object,
710 const char *scaleOrStrengthKey =
nullptr) {
711 const TextureInfo info = parseTextureInfo(object, scaleOrStrengthKey);
712 if (info.index >= document->textures.size())
713 badTextureIndex = info.index;
717 for (
const auto &value : root.value(QLatin1String(
"materials")).toArray()) {
718 const QJsonObject object = value.toObject();
720 material.name = object.value(QLatin1String(
"name")).toString();
722 const QJsonValue pbrValue = object.value(QLatin1String(
"pbrMetallicRoughness"));
723 if (pbrValue.isObject()) {
724 const QJsonObject pbr = pbrValue.toObject();
725 material.baseColorFactor =
726 toVector4D(pbr.value(QLatin1String(
"baseColorFactor")).toArray(), material.baseColorFactor);
727 material.baseColorTexture = parseTexture(pbr.value(QLatin1String(
"baseColorTexture")).toObject());
728 material.metallicFactor =
float(pbr.value(QLatin1String(
"metallicFactor")).toDouble(1.0));
729 material.roughnessFactor =
float(pbr.value(QLatin1String(
"roughnessFactor")).toDouble(1.0));
730 material.metallicRoughnessTexture =
731 parseTexture(pbr.value(QLatin1String(
"metallicRoughnessTexture")).toObject());
734 material.normalTexture = parseTexture(object.value(QLatin1String(
"normalTexture")).toObject(),
"scale");
735 material.occlusionTexture =
736 parseTexture(object.value(QLatin1String(
"occlusionTexture")).toObject(),
"strength");
737 material.emissiveTexture = parseTexture(object.value(QLatin1String(
"emissiveTexture")).toObject());
738 material.emissiveFactor =
739 toVector3D(object.value(QLatin1String(
"emissiveFactor")).toArray(), material.emissiveFactor);
741 const QString alphaMode = object.value(QLatin1String(
"alphaMode")).toString();
742 if (alphaMode == QLatin1String(
"MASK"))
743 material.alphaMode = Material::AlphaMode::Mask;
744 else if (alphaMode == QLatin1String(
"BLEND"))
745 material.alphaMode = Material::AlphaMode::Blend;
746 material.alphaCutoff =
float(object.value(QLatin1String(
"alphaCutoff")).toDouble(0.5));
747 material.doubleSided = object.value(QLatin1String(
"doubleSided")).toBool(
false);
749 material.extensions = object.value(QLatin1String(
"extensions")).toObject();
750 const QJsonObject &ext = material.extensions;
752 material.unlit = ext.contains(QLatin1String(
"KHR_materials_unlit"));
754 const QJsonValue sgValue = ext.value(QLatin1String(
"KHR_materials_pbrSpecularGlossiness"));
755 if (sgValue.isObject()) {
756 const QJsonObject sg = sgValue.toObject();
757 Material::SpecularGlossiness specularGlossiness;
758 specularGlossiness.diffuseFactor = toVector4D(sg.value(QLatin1String(
"diffuseFactor")).toArray(),
759 specularGlossiness.diffuseFactor);
760 specularGlossiness.diffuseTexture = parseTexture(sg.value(QLatin1String(
"diffuseTexture")).toObject());
761 specularGlossiness.specularFactor = toVector3D(sg.value(QLatin1String(
"specularFactor")).toArray(),
762 specularGlossiness.specularFactor);
763 specularGlossiness.glossinessFactor =
float(sg.value(QLatin1String(
"glossinessFactor")).toDouble(1.0));
764 specularGlossiness.specularGlossinessTexture = parseTexture(
765 sg.value(QLatin1String(
"specularGlossinessTexture")).toObject());
766 material.specularGlossiness = specularGlossiness;
769 const QJsonValue ccValue = ext.value(QLatin1String(
"KHR_materials_clearcoat"));
770 if (ccValue.isObject()) {
771 const QJsonObject cc = ccValue.toObject();
772 Material::Clearcoat clearcoat;
773 clearcoat.clearcoatFactor =
float(cc.value(QLatin1String(
"clearcoatFactor")).toDouble(0.0));
774 clearcoat.clearcoatTexture = parseTexture(cc.value(QLatin1String(
"clearcoatTexture")).toObject());
775 clearcoat.clearcoatRoughnessFactor =
776 float(cc.value(QLatin1String(
"clearcoatRoughnessFactor")).toDouble(0.0));
777 clearcoat.clearcoatRoughnessTexture =
778 parseTexture(cc.value(QLatin1String(
"clearcoatRoughnessTexture")).toObject());
779 clearcoat.clearcoatNormalTexture =
780 parseTexture(cc.value(QLatin1String(
"clearcoatNormalTexture")).toObject(),
"scale");
781 material.clearcoat = clearcoat;
784 const QJsonValue trValue = ext.value(QLatin1String(
"KHR_materials_transmission"));
785 if (trValue.isObject()) {
786 const QJsonObject tr = trValue.toObject();
787 Material::Transmission transmission;
788 transmission.transmissionFactor =
float(tr.value(QLatin1String(
"transmissionFactor")).toDouble(0.0));
789 transmission.transmissionTexture =
790 parseTexture(tr.value(QLatin1String(
"transmissionTexture")).toObject());
791 material.transmission = transmission;
794 const QJsonValue volValue = ext.value(QLatin1String(
"KHR_materials_volume"));
795 if (volValue.isObject()) {
796 const QJsonObject vol = volValue.toObject();
797 Material::Volume volume;
798 volume.thicknessFactor =
float(vol.value(QLatin1String(
"thicknessFactor")).toDouble(0.0));
799 volume.thicknessTexture = parseTexture(vol.value(QLatin1String(
"thicknessTexture")).toObject());
800 volume.attenuationDistance =
float(vol.value(QLatin1String(
"attenuationDistance")).toDouble(0.0));
801 volume.attenuationColor =
802 toVector3D(vol.value(QLatin1String(
"attenuationColor")).toArray(), volume.attenuationColor);
803 material.volume = volume;
806 const QJsonValue iorValue = ext.value(QLatin1String(
"KHR_materials_ior"));
807 if (iorValue.isObject())
808 material.ior =
float(iorValue.toObject().value(QLatin1String(
"ior")).toDouble(1.5));
810 const QJsonValue esValue = ext.value(QLatin1String(
"KHR_materials_emissive_strength"));
811 if (esValue.isObject())
812 material.emissiveStrength =
813 float(esValue.toObject().value(QLatin1String(
"emissiveStrength")).toDouble(1.0));
815 const QJsonValue spValue = ext.value(QLatin1String(
"KHR_materials_specular"));
816 if (spValue.isObject()) {
817 const QJsonObject sp = spValue.toObject();
818 Material::Specular specular;
819 specular.specularFactor =
float(sp.value(QLatin1String(
"specularFactor")).toDouble(1.0));
820 specular.specularTexture = parseTexture(sp.value(QLatin1String(
"specularTexture")).toObject());
821 specular.specularColorFactor = toVector3D(sp.value(QLatin1String(
"specularColorFactor")).toArray(),
822 specular.specularColorFactor);
823 specular.specularColorTexture =
824 parseTexture(sp.value(QLatin1String(
"specularColorTexture")).toObject());
825 material.specular = specular;
828 const QJsonValue shValue = ext.value(QLatin1String(
"KHR_materials_sheen"));
829 if (shValue.isObject()) {
830 const QJsonObject sh = shValue.toObject();
831 Material::Sheen sheen;
832 sheen.sheenColorFactor = toVector3D(sh.value(QLatin1String(
"sheenColorFactor")).toArray(), sheen.sheenColorFactor);
833 sheen.sheenColorTexture = parseTexture(sh.value(QLatin1String(
"sheenColorTexture")).toObject());
834 sheen.sheenRoughnessFactor =
float(sh.value(QLatin1String(
"sheenRoughnessFactor")).toDouble(0.0));
835 sheen.sheenRoughnessTexture = parseTexture(sh.value(QLatin1String(
"sheenRoughnessTexture")).toObject());
836 material.sheen = sheen;
839 const QJsonValue anValue = ext.value(QLatin1String(
"KHR_materials_anisotropy"));
840 if (anValue.isObject()) {
841 const QJsonObject an = anValue.toObject();
842 Material::Anisotropy anisotropy;
843 anisotropy.anisotropyStrength =
float(an.value(QLatin1String(
"anisotropyStrength")).toDouble(0.0));
844 anisotropy.anisotropyRotation =
float(an.value(QLatin1String(
"anisotropyRotation")).toDouble(0.0));
845 anisotropy.anisotropyTexture = parseTexture(an.value(QLatin1String(
"anisotropyTexture")).toObject());
846 material.anisotropy = anisotropy;
849 const QJsonValue irValue = ext.value(QLatin1String(
"KHR_materials_iridescence"));
850 if (irValue.isObject()) {
851 const QJsonObject ir = irValue.toObject();
852 Material::Iridescence iridescence;
853 iridescence.iridescenceFactor =
float(ir.value(QLatin1String(
"iridescenceFactor")).toDouble(0.0));
854 iridescence.iridescenceTexture = parseTexture(ir.value(QLatin1String(
"iridescenceTexture")).toObject());
855 iridescence.iridescenceIor =
float(ir.value(QLatin1String(
"iridescenceIor")).toDouble(1.3));
856 iridescence.iridescenceThicknessMinimum =
float(ir.value(QLatin1String(
"iridescenceThicknessMinimum")).toDouble(100.0));
857 iridescence.iridescenceThicknessMaximum =
float(ir.value(QLatin1String(
"iridescenceThicknessMaximum")).toDouble(400.0));
858 iridescence.iridescenceThicknessTexture = parseTexture(
859 ir.value(QLatin1String(
"iridescenceThicknessTexture")).toObject());
860 material.iridescence = iridescence;
863 const QJsonValue dispValue = ext.value(QLatin1String(
"KHR_materials_dispersion"));
864 if (dispValue.isObject())
865 material.dispersion =
float(dispValue.toObject().value(QLatin1String(
"dispersion")).toDouble(0.0));
867 document->materials.append(material);
870 if (badTextureIndex >= 0)
871 return setError(QStringLiteral(
"Material references invalid texture %1").arg(badTextureIndex));
874 for (
const auto &value : root.value(QLatin1String(
"meshes")).toArray()) {
875 const QJsonObject object = value.toObject();
877 mesh.name = object.value(QLatin1String(
"name")).toString();
878 for (
const auto &w : object.value(QLatin1String(
"weights")).toArray())
879 mesh.weights.append(
float(w.toDouble()));
881 for (
const auto &primitiveValue : object.value(QLatin1String(
"primitives")).toArray()) {
882 const QJsonObject primitiveObject = primitiveValue.toObject();
883 MeshPrimitive primitive;
884 const QJsonObject attributes = primitiveObject.value(QLatin1String(
"attributes")).toObject();
885 for (
auto it = attributes.constBegin(); it != attributes.constEnd(); ++it)
886 primitive.attributes.insert(it.key().toUtf8(), it.value().toInt(-1));
887 primitive.indices = primitiveObject.value(QLatin1String(
"indices")).toInt(-1);
888 primitive.material = primitiveObject.value(QLatin1String(
"material")).toInt(-1);
889 primitive.mode = primitiveObject.value(QLatin1String(
"mode")).toInt(MeshPrimitive::Triangles);
890 for (
const auto &targetValue : primitiveObject.value(QLatin1String(
"targets")).toArray()) {
891 const QJsonObject targetObject = targetValue.toObject();
892 QHash<QByteArray,
int> target;
893 for (
auto it = targetObject.constBegin(); it != targetObject.constEnd(); ++it)
894 target.insert(it.key().toUtf8(), it.value().toInt(-1));
895 primitive.targets.append(target);
897 primitive.extensions = primitiveObject.value(QLatin1String(
"extensions")).toObject();
899 const QJsonValue variantsExt = primitive.extensions.value(QLatin1String(
"KHR_materials_variants"));
900 if (variantsExt.isObject()) {
901 for (
const auto &mappingValue : variantsExt.toObject().value(QLatin1String(
"mappings")).toArray()) {
902 const QJsonObject mappingObject = mappingValue.toObject();
903 MeshPrimitive::VariantMapping mapping;
904 mapping.material = mappingObject.value(QLatin1String(
"material")).toInt(-1);
905 if (mapping.material < 0 || mapping.material >= document->materials.size()) {
907 QStringLiteral(
"Mesh %1 primitive variant mapping references invalid material %2")
908 .arg(document->meshes.size())
909 .arg(mapping.material));
911 for (
const auto &variantValue : mappingObject.value(QLatin1String(
"variants")).toArray()) {
912 const int variant = variantValue.toInt(-1);
913 if (variant < 0 || variant >= document->materialVariants.size()) {
915 QStringLiteral(
"Mesh %1 primitive variant mapping references invalid variant %2")
916 .arg(document->meshes.size())
919 mapping.variants.append(variant);
921 primitive.variantMappings.append(mapping);
929 const auto checkAccessor = [&](
int accessor,
const char *what) {
930 if (accessor >= document->accessors.size())
931 return setError(QStringLiteral(
"Mesh %1 primitive references invalid %2 accessor %3")
932 .arg(document->meshes.size()).arg(QLatin1String(what)).arg(accessor));
935 for (
auto it = primitive.attributes.constBegin(); it != primitive.attributes.constEnd(); ++it) {
936 if (!checkAccessor(it.value(), it.key().constData()))
939 if (primitive.indices >= 0 && !checkAccessor(primitive.indices,
"index"))
941 if (primitive.material >= document->materials.size())
942 return setError(QStringLiteral(
"Mesh %1 primitive references invalid material %2")
943 .arg(document->meshes.size())
944 .arg(primitive.material));
946 mesh.primitives.append(primitive);
948 if (mesh.primitives.isEmpty())
949 qCWarning(lcQuick3DGltf) <<
"Mesh" << document->meshes.size() <<
"has no primitives";
950 document->meshes.append(mesh);
954 for (
const auto &value : root.value(QLatin1String(
"cameras")).toArray()) {
955 const QJsonObject object = value.toObject();
957 camera.name = object.value(QLatin1String(
"name")).toString();
958 const QString type = object.value(QLatin1String(
"type")).toString();
959 if (type == QLatin1String(
"orthographic")) {
960 camera.type = Camera::Type::Orthographic;
961 const QJsonObject ortho = object.value(QLatin1String(
"orthographic")).toObject();
962 camera.xmag =
float(ortho.value(QLatin1String(
"xmag")).toDouble());
963 camera.ymag =
float(ortho.value(QLatin1String(
"ymag")).toDouble());
964 camera.znear =
float(ortho.value(QLatin1String(
"znear")).toDouble());
965 camera.zfar =
float(ortho.value(QLatin1String(
"zfar")).toDouble());
967 camera.type = Camera::Type::Perspective;
968 const QJsonObject perspective = object.value(QLatin1String(
"perspective")).toObject();
969 camera.aspectRatio =
float(perspective.value(QLatin1String(
"aspectRatio")).toDouble(0.0));
970 camera.yfov =
float(perspective.value(QLatin1String(
"yfov")).toDouble());
971 camera.znear =
float(perspective.value(QLatin1String(
"znear")).toDouble());
972 camera.zfar =
float(perspective.value(QLatin1String(
"zfar")).toDouble(0.0));
974 document->cameras.append(camera);
979 const QJsonValue lightsExt = document->rootExtensions.value(QLatin1String(
"KHR_lights_punctual"));
980 if (lightsExt.isObject()) {
981 for (
const auto &value : lightsExt.toObject().value(QLatin1String(
"lights")).toArray()) {
982 const QJsonObject object = value.toObject();
984 light.name = object.value(QLatin1String(
"name")).toString();
985 const QString type = object.value(QLatin1String(
"type")).toString();
986 if (type == QLatin1String(
"point"))
987 light.type = Light::Type::Point;
988 else if (type == QLatin1String(
"spot"))
989 light.type = Light::Type::Spot;
991 light.type = Light::Type::Directional;
992 light.color = toVector3D(object.value(QLatin1String(
"color")).toArray(), light.color);
993 light.intensity =
float(object.value(QLatin1String(
"intensity")).toDouble(1.0));
994 light.range =
float(object.value(QLatin1String(
"range")).toDouble(0.0));
995 if (light.type == Light::Type::Spot) {
996 const QJsonObject spot = object.value(QLatin1String(
"spot")).toObject();
997 light.innerConeAngle =
float(spot.value(QLatin1String(
"innerConeAngle")).toDouble(0.0));
998 light.outerConeAngle =
float(spot.value(QLatin1String(
"outerConeAngle")).toDouble(M_PI_4));
1000 document->lights.append(light);
1006 for (
const auto &value : root.value(QLatin1String(
"nodes")).toArray()) {
1007 const QJsonObject object = value.toObject();
1009 node.name = object.value(QLatin1String(
"name")).toString();
1010 for (
const auto &child : object.value(QLatin1String(
"children")).toArray())
1011 node.children.append(child.toInt(-1));
1012 node.mesh = object.value(QLatin1String(
"mesh")).toInt(-1);
1013 node.skin = object.value(QLatin1String(
"skin")).toInt(-1);
1014 node.camera = object.value(QLatin1String(
"camera")).toInt(-1);
1016 const QJsonValue matrixValue = object.value(QLatin1String(
"matrix"));
1017 if (matrixValue.isArray()) {
1018 const QJsonArray m = matrixValue.toArray();
1019 if (m.size() == 16) {
1021 for (
int i = 0; i < 16; ++i)
1022 values[i] =
float(m.at(i).toDouble());
1024 node.matrix = QMatrix4x4(values).transposed();
1025 node.hasMatrix =
true;
1028 node.translation = toVector3D(object.value(QLatin1String(
"translation")).toArray(), node.translation);
1029 const QJsonArray rotation = object.value(QLatin1String(
"rotation")).toArray();
1030 if (rotation.size() == 4) {
1032 node.rotation = QQuaternion(
float(rotation.at(3).toDouble()),
float(rotation.at(0).toDouble()),
1033 float(rotation.at(1).toDouble()),
float(rotation.at(2).toDouble()));
1035 node.scale = toVector3D(object.value(QLatin1String(
"scale")).toArray(), node.scale);
1036 for (
const auto &w : object.value(QLatin1String(
"weights")).toArray())
1037 node.weights.append(
float(w.toDouble()));
1038 node.extensions = object.value(QLatin1String(
"extensions")).toObject();
1040 const QJsonValue lightExt = node.extensions.value(QLatin1String(
"KHR_lights_punctual"));
1041 if (lightExt.isObject())
1042 node.light = lightExt.toObject().value(QLatin1String(
"light")).toInt(-1);
1044 const QJsonValue visibilityExt = node.extensions.value(QLatin1String(
"KHR_node_visibility"));
1045 if (visibilityExt.isObject())
1046 node.visible = visibilityExt.toObject().value(QLatin1String(
"visible")).toBool(
true);
1048 const QJsonValue instancingExt = node.extensions.value(QLatin1String(
"EXT_mesh_gpu_instancing"));
1049 if (instancingExt.isObject()) {
1050 const QJsonObject instanceAttributes =
1051 instancingExt.toObject().value(QLatin1String(
"attributes")).toObject();
1052 for (
auto it = instanceAttributes.constBegin(); it != instanceAttributes.constEnd(); ++it) {
1053 const int accessor = it.value().toInt(-1);
1054 if (accessor < 0 || accessor >= document->accessors.size()) {
1055 return setError(QStringLiteral(
"Node %1 instance attribute %2 references invalid accessor %3")
1056 .arg(document->nodes.size()).arg(it.key()).arg(accessor));
1058 if (it.key() == QLatin1String(
"TRANSLATION"))
1059 node.instanceTranslation = accessor;
1060 else if (it.key() == QLatin1String(
"ROTATION"))
1061 node.instanceRotation = accessor;
1062 else if (it.key() == QLatin1String(
"SCALE"))
1063 node.instanceScale = accessor;
1065 qCWarning(lcQuick3DGltf) <<
"Ignoring unsupported instance attribute" << it.key();
1067 node.hasInstancing = node.instanceTranslation >= 0 || node.instanceRotation >= 0
1068 || node.instanceScale >= 0;
1071 if (node.mesh >= document->meshes.size())
1072 return setError(QStringLiteral(
"Node %1 references invalid mesh %2")
1073 .arg(document->nodes.size()).arg(node.mesh));
1074 if (node.camera >= document->cameras.size())
1075 return setError(QStringLiteral(
"Node %1 references invalid camera %2")
1076 .arg(document->nodes.size()).arg(node.camera));
1077 if (node.light >= document->lights.size())
1078 return setError(QStringLiteral(
"Node %1 references invalid light %2")
1079 .arg(document->nodes.size()).arg(node.light));
1080 document->nodes.append(node);
1088 const int nodeCount = document->nodes.size();
1089 QVarLengthArray<
int, 64> parentCount(nodeCount);
1090 std::fill(parentCount.begin(), parentCount.end(), 0);
1091 for (
int i = 0; i < nodeCount; ++i) {
1092 Node &node = document->nodes[i];
1093 for (qsizetype c = 0; c < node.children.size(); ++c) {
1094 const int child = node.children.at(c);
1095 if (child < 0 || child >= nodeCount)
1096 return setError(QStringLiteral(
"Node %1 references invalid child node %2").arg(i).arg(child));
1097 if (++parentCount[child] > 1) {
1102 qCWarning(lcQuick3DGltf) <<
"Node" << child
1103 <<
"has more than one parent; ignoring the edge from node" << i;
1104 --parentCount[child];
1105 node.children.removeAt(c--);
1113 constexpr int maxNodeDepth = 4096;
1114 QVarLengthArray<
int, 64> parent(nodeCount);
1115 std::fill(parent.begin(), parent.end(), -1);
1116 for (
int i = 0; i < nodeCount; ++i) {
1117 for (
int child : document->nodes.at(i).children)
1120 QVarLengthArray<
int, 64> depth(nodeCount);
1121 std::fill(depth.begin(), depth.end(), -1);
1122 QVarLengthArray<
int, 64> chain;
1123 for (
int i = 0; i < nodeCount; ++i) {
1126 while (n >= 0 && depth[n] < 0 && chain.size() <= nodeCount) {
1130 if (chain.size() > nodeCount)
1131 return setError(QStringLiteral(
"Node hierarchy contains a cycle"));
1132 int d = n >= 0 ? depth[n] : 0;
1133 for (
auto it = chain.rbegin(); it != chain.rend(); ++it)
1135 if (d > maxNodeDepth)
1136 return setError(QStringLiteral(
"Node hierarchy is deeper than %1").arg(maxNodeDepth));
1141 for (
const auto &value : root.value(QLatin1String(
"skins")).toArray()) {
1142 const QJsonObject object = value.toObject();
1144 skin.name = object.value(QLatin1String(
"name")).toString();
1145 skin.inverseBindMatrices = object.value(QLatin1String(
"inverseBindMatrices")).toInt(-1);
1146 skin.skeleton = object.value(QLatin1String(
"skeleton")).toInt(-1);
1147 for (
const auto &joint : object.value(QLatin1String(
"joints")).toArray())
1148 skin.joints.append(joint.toInt(-1));
1150 if (skin.inverseBindMatrices >= document->accessors.size())
1151 return setError(QStringLiteral(
"Skin %1 references invalid accessor %2")
1152 .arg(document->skins.size()).arg(skin.inverseBindMatrices));
1153 for (
int joint : std::as_const(skin.joints)) {
1154 if (joint < 0 || joint >= nodeCount)
1155 return setError(QStringLiteral(
"Skin %1 references invalid joint node %2")
1156 .arg(document->skins.size()).arg(joint));
1158 document->skins.append(skin);
1160 for (
int i = 0; i < nodeCount; ++i) {
1161 if (document->nodes.at(i).skin >= document->skins.size())
1162 return setError(QStringLiteral(
"Node %1 references invalid skin %2")
1163 .arg(i).arg(document->nodes.at(i).skin));
1167 for (
const auto &value : root.value(QLatin1String(
"animations")).toArray()) {
1168 const QJsonObject object = value.toObject();
1169 Animation animation;
1170 animation.name = object.value(QLatin1String(
"name")).toString();
1172 for (
const auto &samplerValue : object.value(QLatin1String(
"samplers")).toArray()) {
1173 const QJsonObject samplerObject = samplerValue.toObject();
1174 AnimationSampler sampler;
1175 sampler.input = samplerObject.value(QLatin1String(
"input")).toInt(-1);
1176 sampler.output = samplerObject.value(QLatin1String(
"output")).toInt(-1);
1177 const QString interpolation = samplerObject.value(QLatin1String(
"interpolation")).toString();
1178 if (interpolation == QLatin1String(
"STEP"))
1179 sampler.interpolation = AnimationSampler::Interpolation::Step;
1180 else if (interpolation == QLatin1String(
"CUBICSPLINE"))
1181 sampler.interpolation = AnimationSampler::Interpolation::CubicSpline;
1182 if (sampler.input < 0 || sampler.input >= document->accessors.size()
1183 || sampler.output < 0 || sampler.output >= document->accessors.size()) {
1185 QStringLiteral(
"Animation %1 sampler references an invalid accessor")
1186 .arg(document->animations.size()));
1188 animation.samplers.append(sampler);
1191 for (
const auto &channelValue : object.value(QLatin1String(
"channels")).toArray()) {
1192 const QJsonObject channelObject = channelValue.toObject();
1193 AnimationChannel channel;
1194 channel.sampler = channelObject.value(QLatin1String(
"sampler")).toInt(-1);
1195 const QJsonObject target = channelObject.value(QLatin1String(
"target")).toObject();
1196 channel.targetNode = target.value(QLatin1String(
"node")).toInt(-1);
1197 const QString path = target.value(QLatin1String(
"path")).toString();
1198 if (path == QLatin1String(
"rotation"))
1199 channel.path = AnimationChannel::Path::Rotation;
1200 else if (path == QLatin1String(
"scale"))
1201 channel.path = AnimationChannel::Path::Scale;
1202 else if (path == QLatin1String(
"weights"))
1203 channel.path = AnimationChannel::Path::Weights;
1204 else if (path != QLatin1String(
"translation")) {
1205 qCWarning(lcQuick3DGltf) <<
"Ignoring animation channel with unsupported path" << path;
1208 if (channel.sampler < 0 || channel.sampler >= animation.samplers.size())
1209 return setError(QStringLiteral(
"Animation %1 channel references invalid sampler %2")
1210 .arg(document->animations.size())
1211 .arg(channel.sampler));
1212 if (channel.targetNode >= nodeCount)
1213 return setError(QStringLiteral(
"Animation %1 channel references invalid node %2")
1214 .arg(document->animations.size())
1215 .arg(channel.targetNode));
1216 animation.channels.append(channel);
1219 document->animations.append(animation);
1223 for (
const auto &value : root.value(QLatin1String(
"scenes")).toArray()) {
1224 const QJsonObject object = value.toObject();
1226 scene.name = object.value(QLatin1String(
"name")).toString();
1227 QSet<
int> seenRoots;
1228 for (
const auto &nodeIndex : object.value(QLatin1String(
"nodes")).toArray()) {
1229 const int index = nodeIndex.toInt(-1);
1230 if (index < 0 || index >= nodeCount)
1231 return setError(QStringLiteral(
"Scene %1 references invalid node %2")
1232 .arg(document->scenes.size()).arg(index));
1237 if (parentCount[index] != 0 || seenRoots.contains(index)) {
1238 qCWarning(lcQuick3DGltf) <<
"Scene" << document->scenes.size() <<
"references node" << index
1239 <<
"which is not a unique root node; ignoring it";
1242 seenRoots.insert(index);
1243 scene.nodes.append(index);
1245 document->scenes.append(scene);
1247 document->scene = root.value(QLatin1String(
"scene")).toInt(-1);
1248 if (document->scene >= document->scenes.size())
1249 return setError(QStringLiteral(
"Document references invalid default scene %1").arg(document->scene));