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 QT_WARNING_PUSH QT_WARNING_DISABLE_GCC(
"-Warray-bounds")
362 const quint32 chunkLength = qFromLittleEndian<quint32>(data.constData() + offset);
363 const quint32 chunkType = qFromLittleEndian<quint32>(data.constData() + offset + 4);
366 if (offset + qint64(chunkLength) > qint64(length))
367 return setError(QStringLiteral(
"Truncated GLB chunk at offset %1").arg(offset - 8));
369 if (chunkType == GLB_CHUNK_JSON && json.isEmpty())
370 json = data.mid(offset, chunkLength);
371 else if (chunkType == GLB_CHUNK_BIN && binChunk.isEmpty())
372 binChunk = data.mid(offset, chunkLength);
374 qCWarning(lcQuick3DGltf) <<
"Skipping unknown GLB chunk type" << Qt::hex << chunkType;
377 offset += chunkLength;
379 offset += 4 - (offset % 4);
382 return setError(QStringLiteral(
"GLB container has no JSON chunk"));
385 QJsonParseError jsonError;
386 const QJsonDocument jsonDocument = QJsonDocument::fromJson(json, &jsonError);
387 if (jsonDocument.isNull())
388 return setError(QStringLiteral(
"Invalid glTF JSON: %1").arg(jsonError.errorString()));
389 if (!jsonDocument.isObject())
390 return setError(QStringLiteral(
"Invalid glTF JSON: root is not an object"));
392 const QJsonObject root = jsonDocument.object();
396 const QJsonValue assetValue = root.value(QLatin1String(
"asset"));
397 if (!assetValue.isObject())
398 return setError(QStringLiteral(
"Not a glTF document: no asset object"));
399 const QJsonObject asset = assetValue.toObject();
400 document->asset.version = asset.value(QLatin1String(
"version")).toString();
401 document->asset.minVersion = asset.value(QLatin1String(
"minVersion")).toString();
402 document->asset.generator = asset.value(QLatin1String(
"generator")).toString();
403 document->asset.copyright = asset.value(QLatin1String(
"copyright")).toString();
405 const int major = document->asset.version.section(QLatin1Char(
'.'), 0, 0).toInt();
407 return setError(QStringLiteral(
"Unsupported glTF version '%1'").arg(document->asset.version));
412 if (!document->asset.minVersion.isEmpty()) {
413 const int minMajor = document->asset.minVersion.section(QLatin1Char(
'.'), 0, 0).toInt();
414 const int minMinor = document->asset.minVersion.section(QLatin1Char(
'.'), 1, 1).toInt();
415 if (minMajor != 2 || minMinor > 0)
416 return setError(QStringLiteral(
"Asset requires glTF version '%1' or higher")
417 .arg(document->asset.minVersion));
422 const QJsonArray extensionsUsed = root.value(QLatin1String(
"extensionsUsed")).toArray();
423 for (
const auto &value : extensionsUsed)
424 document->extensionsUsed.append(value.toString());
425 const QJsonArray extensionsRequired = root.value(QLatin1String(
"extensionsRequired")).toArray();
426 for (
const auto &value : extensionsRequired)
427 document->extensionsRequired.append(value.toString());
428 document->rootExtensions = root.value(QLatin1String(
"extensions")).toObject();
432 const QJsonValue variantsExt = document->rootExtensions.value(QLatin1String(
"KHR_materials_variants"));
433 if (variantsExt.isObject()) {
434 const QJsonArray variants = variantsExt.toObject().value(QLatin1String(
"variants")).toArray();
435 for (
const auto &value : variants)
436 document->materialVariants.append(value.toObject().value(QLatin1String(
"name")).toString());
440 const QStringList supported = supportedExtensions();
441 for (
const QString &required : std::as_const(document->extensionsRequired)) {
442 if (!supported.contains(required))
443 return setError(QStringLiteral(
"Asset requires unsupported glTF extension '%1'").arg(required));
445 for (
const QString &used : std::as_const(document->extensionsUsed)) {
446 if (!supported.contains(used))
447 qCWarning(lcQuick3DGltf) <<
"Ignoring unsupported glTF extension" << used;
451 const QJsonArray buffers = root.value(QLatin1String(
"buffers")).toArray();
452 for (
const auto &value : buffers) {
453 const QJsonObject object = value.toObject();
455 buffer.uri = object.value(QLatin1String(
"uri")).toString();
456 buffer.byteLength = qint64(object.value(QLatin1String(
"byteLength")).toDouble());
457 if (buffer.byteLength < 0)
458 return setError(QStringLiteral(
"Buffer %1 has negative byte length").arg(document->buffers.size()));
459 buffer.meshoptFallback = object.value(QLatin1String(
"extensions")).toObject()
460 .value(QLatin1String(
"EXT_meshopt_compression")).toObject()
461 .value(QLatin1String(
"fallback")).toBool(
false);
462 if (buffer.meshoptFallback) {
463 document->buffers.append(buffer);
466 if (buffer.uri.isEmpty()) {
468 if (document->buffers.isEmpty() && !binChunk.isEmpty())
469 buffer.data = binChunk;
470 else if (binChunk.isEmpty())
472 QStringLiteral(
"Buffer %1 has no URI and there is no GLB BIN chunk")
473 .arg(document->buffers.size()));
475 return setError(QStringLiteral(
"Only the first buffer may refer to the GLB BIN chunk"));
477 QString resolveError;
478 buffer.data = QSSGGltfResourceResolver::loadUri(buffer.uri, baseDir, &resolveError);
482 if (!resolveError.isEmpty())
483 return setError(QStringLiteral(
"Failed to load buffer %1: %2")
484 .arg(document->buffers.size()).arg(resolveError));
486 if (buffer.data.size() < buffer.byteLength)
487 return setError(QStringLiteral(
"Buffer %1 is %2 bytes, expected at least %3")
488 .arg(document->buffers.size()).arg(buffer.data.size()).arg(buffer.byteLength));
489 document->buffers.append(buffer);
493 MeshoptDecodeState meshoptState;
494 const QJsonArray bufferViews = root.value(QLatin1String(
"bufferViews")).toArray();
495 for (
const auto &value : bufferViews) {
496 const QJsonObject object = value.toObject();
498 view.buffer = object.value(QLatin1String(
"buffer")).toInt(-1);
499 view.byteOffset = qint64(object.value(QLatin1String(
"byteOffset")).toDouble(0));
500 view.byteLength = qint64(object.value(QLatin1String(
"byteLength")).toDouble());
501 view.byteStride = object.value(QLatin1String(
"byteStride")).toInt(0);
502 view.target = object.value(QLatin1String(
"target")).toInt(0);
503 view.name = object.value(QLatin1String(
"name")).toString();
505 const QJsonValue meshoptValue = object.value(QLatin1String(
"extensions")).toObject()
506 .value(QLatin1String(
"EXT_meshopt_compression"));
507 if (meshoptValue.isObject()) {
509 if (!decodeMeshoptBufferView(meshoptValue.toObject(), *document, view, meshoptState, &decodeError)) {
510 return setError(QStringLiteral(
"Buffer view %1: EXT_meshopt_compression: %2")
511 .arg(document->bufferViews.size()).arg(decodeError));
513 document->bufferViews.append(view);
517 if (view.buffer < 0 || view.buffer >= document->buffers.size())
519 QStringLiteral(
"Buffer view %1 references invalid buffer %2")
520 .arg(document->bufferViews.size()).arg(view.buffer));
521 if (document->buffers.at(view.buffer).meshoptFallback)
522 return setError(QStringLiteral(
"Buffer view %1 references unloaded fallback buffer %2")
523 .arg(document->bufferViews.size()).arg(view.buffer));
527 if (view.byteOffset < 0 || view.byteLength < 0 || view.byteStride < 0 || view.byteStride > 252)
528 return setError(QStringLiteral(
"Buffer view %1 has an invalid byte offset, length, or stride")
529 .arg(document->bufferViews.size()));
533 const qint64 bufferLength = document->buffers.at(view.buffer).byteLength;
534 if (view.byteLength > bufferLength || view.byteOffset > bufferLength - view.byteLength)
536 QStringLiteral(
"Buffer view %1 extends past the end of buffer %2")
537 .arg(document->bufferViews.size()).arg(view.buffer));
538 document->bufferViews.append(view);
542 const QJsonArray accessors = root.value(QLatin1String(
"accessors")).toArray();
543 for (
const auto &value : accessors) {
544 const QJsonObject object = value.toObject();
546 accessor.bufferView = object.value(QLatin1String(
"bufferView")).toInt(-1);
547 accessor.byteOffset = qint64(object.value(QLatin1String(
"byteOffset")).toDouble(0));
548 const int componentType = object.value(QLatin1String(
"componentType")).toInt();
549 if (!isValidComponentType(componentType))
550 return setError(QStringLiteral(
"Accessor %1 has invalid component type %2")
551 .arg(document->accessors.size()).arg(componentType));
552 accessor.componentType = Accessor::ComponentType(componentType);
554 accessor.type = accessorTypeFromString(object.value(QLatin1String(
"type")).toString(), &typeOk);
556 return setError(QStringLiteral(
"Accessor %1 has invalid type '%2'")
557 .arg(document->accessors.size())
558 .arg(object.value(QLatin1String(
"type")).toString()));
567 constexpr qint64 maxAccessorElements = qint64(256) * 1024 * 1024;
568 const double rawCount = object.value(QLatin1String(
"count")).toDouble();
569 if (!(rawCount >= 0.0 && rawCount <=
double(maxAccessorElements)))
570 return setError(QStringLiteral(
"Accessor %1 has a negative or unreasonably large count")
571 .arg(document->accessors.size()));
572 accessor.count = qint64(rawCount);
573 if (accessor.byteOffset < 0)
574 return setError(QStringLiteral(
"Accessor %1 has a negative byte offset")
575 .arg(document->accessors.size()));
576 accessor.normalized = object.value(QLatin1String(
"normalized")).toBool(
false);
577 accessor.name = object.value(QLatin1String(
"name")).toString();
578 const QJsonArray minValues = object.value(QLatin1String(
"min")).toArray();
579 for (
const auto &m : minValues)
580 accessor.min.append(m.toDouble());
581 const QJsonArray maxValues = object.value(QLatin1String(
"max")).toArray();
582 for (
const auto &m : maxValues)
583 accessor.max.append(m.toDouble());
585 const QJsonValue sparseValue = object.value(QLatin1String(
"sparse"));
586 if (sparseValue.isObject()) {
587 const QJsonObject sparseObject = sparseValue.toObject();
588 Accessor::Sparse sparse;
591 const double rawSparseCount = sparseObject.value(QLatin1String(
"count")).toDouble();
592 if (!(rawSparseCount >= 0.0 && rawSparseCount <=
double(maxAccessorElements)))
593 return setError(QStringLiteral(
"Accessor %1 has a negative or unreasonably large sparse count")
594 .arg(document->accessors.size()));
595 sparse.count = qint64(rawSparseCount);
596 const QJsonObject indices = sparseObject.value(QLatin1String(
"indices")).toObject();
597 sparse.indicesBufferView = indices.value(QLatin1String(
"bufferView")).toInt(-1);
598 sparse.indicesByteOffset = qint64(indices.value(QLatin1String(
"byteOffset")).toDouble(0));
599 const int indicesComponentType = indices.value(QLatin1String(
"componentType")).toInt();
600 if (!isValidComponentType(indicesComponentType))
602 QStringLiteral(
"Accessor %1 sparse indices have invalid component type")
603 .arg(document->accessors.size()));
604 sparse.indicesComponentType = Accessor::ComponentType(indicesComponentType);
605 const QJsonObject values = sparseObject.value(QLatin1String(
"values")).toObject();
606 sparse.valuesBufferView = values.value(QLatin1String(
"bufferView")).toInt(-1);
607 sparse.valuesByteOffset = qint64(values.value(QLatin1String(
"byteOffset")).toDouble(0));
609 if (sparse.indicesBufferView < 0 || sparse.indicesBufferView >= document->bufferViews.size()
610 || sparse.valuesBufferView < 0 || sparse.valuesBufferView >= document->bufferViews.size()) {
611 return setError(QStringLiteral(
"Accessor %1 sparse data references an invalid buffer view")
612 .arg(document->accessors.size()));
614 if (sparse.count > accessor.count
615 || sparse.indicesByteOffset < 0 || sparse.valuesByteOffset < 0) {
616 return setError(QStringLiteral(
"Accessor %1 has an invalid sparse count or byte offset")
617 .arg(document->accessors.size()));
620 const BufferView &indicesView = document->bufferViews.at(sparse.indicesBufferView);
621 const BufferView &valuesView = document->bufferViews.at(sparse.valuesBufferView);
622 const qint64 indicesSpan =
623 sparse.count * Accessor::componentByteSize(sparse.indicesComponentType);
624 const qint64 valuesSpan = sparse.count * accessor.elementByteSize();
625 if (indicesSpan > indicesView.byteLength
626 || sparse.indicesByteOffset > indicesView.byteLength - indicesSpan
627 || valuesSpan > valuesView.byteLength
628 || sparse.valuesByteOffset > valuesView.byteLength - valuesSpan) {
630 QStringLiteral(
"Accessor %1 sparse data extends past the end of its buffer view")
631 .arg(document->accessors.size()));
633 accessor.sparse = sparse;
636 if (accessor.bufferView >= document->bufferViews.size())
638 QStringLiteral(
"Accessor %1 references invalid buffer view %2")
639 .arg(document->accessors.size()).arg(accessor.bufferView));
640 if (accessor.bufferView >= 0) {
641 const BufferView &view = document->bufferViews.at(accessor.bufferView);
642 const qint64 elementSize = accessor.elementByteSize();
643 const qint64 stride = view.byteStride > 0 ? view.byteStride : elementSize;
645 const qint64 span = (accessor.count - 1) * stride + elementSize;
646 if (accessor.count > 0
647 && (span > view.byteLength || accessor.byteOffset > view.byteLength - span))
648 return setError(QStringLiteral(
"Accessor %1 extends past the end of buffer view %2")
649 .arg(document->accessors.size())
650 .arg(accessor.bufferView));
656 constexpr qint64 maxSyntheticAccessorBytes = qint64(256) * 1024 * 1024;
657 if (accessor.count * accessor.elementByteSize() > maxSyntheticAccessorBytes)
658 return setError(QStringLiteral(
"Accessor %1 has no buffer view and an unreasonably large count")
659 .arg(document->accessors.size()));
661 document->accessors.append(accessor);
665 const QJsonArray images = root.value(QLatin1String(
"images")).toArray();
666 for (
const auto &value : images) {
667 const QJsonObject object = value.toObject();
669 image.uri = object.value(QLatin1String(
"uri")).toString();
670 image.bufferView = object.value(QLatin1String(
"bufferView")).toInt(-1);
671 image.mimeType = object.value(QLatin1String(
"mimeType")).toString();
672 image.name = object.value(QLatin1String(
"name")).toString();
673 if (image.bufferView >= document->bufferViews.size())
674 return setError(QStringLiteral(
"Image %1 references invalid buffer view %2")
675 .arg(document->images.size()).arg(image.bufferView));
676 document->images.append(image);
680 const QJsonArray samplers = root.value(QLatin1String(
"samplers")).toArray();
681 for (
const auto &value : samplers) {
682 const QJsonObject object = value.toObject();
684 sampler.magFilter = object.value(QLatin1String(
"magFilter")).toInt(0);
685 sampler.minFilter = object.value(QLatin1String(
"minFilter")).toInt(0);
686 sampler.wrapS = object.value(QLatin1String(
"wrapS")).toInt(Sampler::Repeat);
687 sampler.wrapT = object.value(QLatin1String(
"wrapT")).toInt(Sampler::Repeat);
688 sampler.name = object.value(QLatin1String(
"name")).toString();
689 document->samplers.append(sampler);
693 const QJsonArray textures = root.value(QLatin1String(
"textures")).toArray();
694 for (
const auto &value : textures) {
695 const QJsonObject object = value.toObject();
697 texture.sampler = object.value(QLatin1String(
"sampler")).toInt(-1);
698 texture.source = object.value(QLatin1String(
"source")).toInt(-1);
699 texture.name = object.value(QLatin1String(
"name")).toString();
700 texture.extensions = object.value(QLatin1String(
"extensions")).toObject();
702 const QJsonValue webpExt = texture.extensions.value(QLatin1String(
"EXT_texture_webp"));
703 if (webpExt.isObject())
704 texture.webpSource = webpExt.toObject().value(QLatin1String(
"source")).toInt(-1);
706 if (texture.sampler >= document->samplers.size())
707 return setError(QStringLiteral(
"Texture %1 references invalid sampler %2")
708 .arg(document->textures.size()).arg(texture.sampler));
709 if (texture.source >= document->images.size() || texture.webpSource >= document->images.size())
710 return setError(QStringLiteral(
"Texture %1 references invalid image %2")
711 .arg(document->textures.size())
712 .arg(qMax(texture.source, texture.webpSource)));
713 document->textures.append(texture);
722 int badTextureIndex = -1;
723 const auto parseTexture = [&](
const QJsonObject &object,
724 const char *scaleOrStrengthKey =
nullptr) {
725 const TextureInfo info = parseTextureInfo(object, scaleOrStrengthKey);
726 if (info.index >= document->textures.size())
727 badTextureIndex = info.index;
731 const QJsonArray materials = root.value(QLatin1String(
"materials")).toArray();
732 for (
const auto &value : materials) {
733 const QJsonObject object = value.toObject();
735 material.name = object.value(QLatin1String(
"name")).toString();
737 const QJsonValue pbrValue = object.value(QLatin1String(
"pbrMetallicRoughness"));
738 if (pbrValue.isObject()) {
739 const QJsonObject pbr = pbrValue.toObject();
740 material.baseColorFactor =
741 toVector4D(pbr.value(QLatin1String(
"baseColorFactor")).toArray(), material.baseColorFactor);
742 material.baseColorTexture = parseTexture(pbr.value(QLatin1String(
"baseColorTexture")).toObject());
743 material.metallicFactor =
float(pbr.value(QLatin1String(
"metallicFactor")).toDouble(1.0));
744 material.roughnessFactor =
float(pbr.value(QLatin1String(
"roughnessFactor")).toDouble(1.0));
745 material.metallicRoughnessTexture =
746 parseTexture(pbr.value(QLatin1String(
"metallicRoughnessTexture")).toObject());
749 material.normalTexture = parseTexture(object.value(QLatin1String(
"normalTexture")).toObject(),
"scale");
750 material.occlusionTexture =
751 parseTexture(object.value(QLatin1String(
"occlusionTexture")).toObject(),
"strength");
752 material.emissiveTexture = parseTexture(object.value(QLatin1String(
"emissiveTexture")).toObject());
753 material.emissiveFactor =
754 toVector3D(object.value(QLatin1String(
"emissiveFactor")).toArray(), material.emissiveFactor);
756 const QString alphaMode = object.value(QLatin1String(
"alphaMode")).toString();
757 if (alphaMode == QLatin1String(
"MASK"))
758 material.alphaMode = Material::AlphaMode::Mask;
759 else if (alphaMode == QLatin1String(
"BLEND"))
760 material.alphaMode = Material::AlphaMode::Blend;
761 material.alphaCutoff =
float(object.value(QLatin1String(
"alphaCutoff")).toDouble(0.5));
762 material.doubleSided = object.value(QLatin1String(
"doubleSided")).toBool(
false);
764 material.extensions = object.value(QLatin1String(
"extensions")).toObject();
765 const QJsonObject &ext = material.extensions;
767 material.unlit = ext.contains(QLatin1String(
"KHR_materials_unlit"));
769 const QJsonValue sgValue = ext.value(QLatin1String(
"KHR_materials_pbrSpecularGlossiness"));
770 if (sgValue.isObject()) {
771 const QJsonObject sg = sgValue.toObject();
772 Material::SpecularGlossiness specularGlossiness;
773 specularGlossiness.diffuseFactor = toVector4D(sg.value(QLatin1String(
"diffuseFactor")).toArray(),
774 specularGlossiness.diffuseFactor);
775 specularGlossiness.diffuseTexture = parseTexture(sg.value(QLatin1String(
"diffuseTexture")).toObject());
776 specularGlossiness.specularFactor = toVector3D(sg.value(QLatin1String(
"specularFactor")).toArray(),
777 specularGlossiness.specularFactor);
778 specularGlossiness.glossinessFactor =
float(sg.value(QLatin1String(
"glossinessFactor")).toDouble(1.0));
779 specularGlossiness.specularGlossinessTexture = parseTexture(
780 sg.value(QLatin1String(
"specularGlossinessTexture")).toObject());
781 material.specularGlossiness = specularGlossiness;
784 const QJsonValue ccValue = ext.value(QLatin1String(
"KHR_materials_clearcoat"));
785 if (ccValue.isObject()) {
786 const QJsonObject cc = ccValue.toObject();
787 Material::Clearcoat clearcoat;
788 clearcoat.clearcoatFactor =
float(cc.value(QLatin1String(
"clearcoatFactor")).toDouble(0.0));
789 clearcoat.clearcoatTexture = parseTexture(cc.value(QLatin1String(
"clearcoatTexture")).toObject());
790 clearcoat.clearcoatRoughnessFactor =
791 float(cc.value(QLatin1String(
"clearcoatRoughnessFactor")).toDouble(0.0));
792 clearcoat.clearcoatRoughnessTexture =
793 parseTexture(cc.value(QLatin1String(
"clearcoatRoughnessTexture")).toObject());
794 clearcoat.clearcoatNormalTexture =
795 parseTexture(cc.value(QLatin1String(
"clearcoatNormalTexture")).toObject(),
"scale");
796 material.clearcoat = clearcoat;
799 const QJsonValue trValue = ext.value(QLatin1String(
"KHR_materials_transmission"));
800 if (trValue.isObject()) {
801 const QJsonObject tr = trValue.toObject();
802 Material::Transmission transmission;
803 transmission.transmissionFactor =
float(tr.value(QLatin1String(
"transmissionFactor")).toDouble(0.0));
804 transmission.transmissionTexture =
805 parseTexture(tr.value(QLatin1String(
"transmissionTexture")).toObject());
806 material.transmission = transmission;
809 const QJsonValue volValue = ext.value(QLatin1String(
"KHR_materials_volume"));
810 if (volValue.isObject()) {
811 const QJsonObject vol = volValue.toObject();
812 Material::Volume volume;
813 volume.thicknessFactor =
float(vol.value(QLatin1String(
"thicknessFactor")).toDouble(0.0));
814 volume.thicknessTexture = parseTexture(vol.value(QLatin1String(
"thicknessTexture")).toObject());
815 volume.attenuationDistance =
float(vol.value(QLatin1String(
"attenuationDistance")).toDouble(0.0));
816 volume.attenuationColor =
817 toVector3D(vol.value(QLatin1String(
"attenuationColor")).toArray(), volume.attenuationColor);
818 material.volume = volume;
821 const QJsonValue iorValue = ext.value(QLatin1String(
"KHR_materials_ior"));
822 if (iorValue.isObject())
823 material.ior =
float(iorValue.toObject().value(QLatin1String(
"ior")).toDouble(1.5));
825 const QJsonValue esValue = ext.value(QLatin1String(
"KHR_materials_emissive_strength"));
826 if (esValue.isObject())
827 material.emissiveStrength =
828 float(esValue.toObject().value(QLatin1String(
"emissiveStrength")).toDouble(1.0));
830 const QJsonValue spValue = ext.value(QLatin1String(
"KHR_materials_specular"));
831 if (spValue.isObject()) {
832 const QJsonObject sp = spValue.toObject();
833 Material::Specular specular;
834 specular.specularFactor =
float(sp.value(QLatin1String(
"specularFactor")).toDouble(1.0));
835 specular.specularTexture = parseTexture(sp.value(QLatin1String(
"specularTexture")).toObject());
836 specular.specularColorFactor = toVector3D(sp.value(QLatin1String(
"specularColorFactor")).toArray(),
837 specular.specularColorFactor);
838 specular.specularColorTexture =
839 parseTexture(sp.value(QLatin1String(
"specularColorTexture")).toObject());
840 material.specular = specular;
843 const QJsonValue shValue = ext.value(QLatin1String(
"KHR_materials_sheen"));
844 if (shValue.isObject()) {
845 const QJsonObject sh = shValue.toObject();
846 Material::Sheen sheen;
847 sheen.sheenColorFactor = toVector3D(sh.value(QLatin1String(
"sheenColorFactor")).toArray(), sheen.sheenColorFactor);
848 sheen.sheenColorTexture = parseTexture(sh.value(QLatin1String(
"sheenColorTexture")).toObject());
849 sheen.sheenRoughnessFactor =
float(sh.value(QLatin1String(
"sheenRoughnessFactor")).toDouble(0.0));
850 sheen.sheenRoughnessTexture = parseTexture(sh.value(QLatin1String(
"sheenRoughnessTexture")).toObject());
851 material.sheen = sheen;
854 const QJsonValue anValue = ext.value(QLatin1String(
"KHR_materials_anisotropy"));
855 if (anValue.isObject()) {
856 const QJsonObject an = anValue.toObject();
857 Material::Anisotropy anisotropy;
858 anisotropy.anisotropyStrength =
float(an.value(QLatin1String(
"anisotropyStrength")).toDouble(0.0));
859 anisotropy.anisotropyRotation =
float(an.value(QLatin1String(
"anisotropyRotation")).toDouble(0.0));
860 anisotropy.anisotropyTexture = parseTexture(an.value(QLatin1String(
"anisotropyTexture")).toObject());
861 material.anisotropy = anisotropy;
864 const QJsonValue irValue = ext.value(QLatin1String(
"KHR_materials_iridescence"));
865 if (irValue.isObject()) {
866 const QJsonObject ir = irValue.toObject();
867 Material::Iridescence iridescence;
868 iridescence.iridescenceFactor =
float(ir.value(QLatin1String(
"iridescenceFactor")).toDouble(0.0));
869 iridescence.iridescenceTexture = parseTexture(ir.value(QLatin1String(
"iridescenceTexture")).toObject());
870 iridescence.iridescenceIor =
float(ir.value(QLatin1String(
"iridescenceIor")).toDouble(1.3));
871 iridescence.iridescenceThicknessMinimum =
float(ir.value(QLatin1String(
"iridescenceThicknessMinimum")).toDouble(100.0));
872 iridescence.iridescenceThicknessMaximum =
float(ir.value(QLatin1String(
"iridescenceThicknessMaximum")).toDouble(400.0));
873 iridescence.iridescenceThicknessTexture = parseTexture(
874 ir.value(QLatin1String(
"iridescenceThicknessTexture")).toObject());
875 material.iridescence = iridescence;
878 const QJsonValue dispValue = ext.value(QLatin1String(
"KHR_materials_dispersion"));
879 if (dispValue.isObject())
880 material.dispersion =
float(dispValue.toObject().value(QLatin1String(
"dispersion")).toDouble(0.0));
882 document->materials.append(material);
885 if (badTextureIndex >= 0)
886 return setError(QStringLiteral(
"Material references invalid texture %1").arg(badTextureIndex));
889 const QJsonArray meshes = root.value(QLatin1String(
"meshes")).toArray();
890 for (
const auto &value : meshes) {
891 const QJsonObject object = value.toObject();
893 mesh.name = object.value(QLatin1String(
"name")).toString();
894 const QJsonArray weights = object.value(QLatin1String(
"weights")).toArray();
895 for (
const auto &w : weights)
896 mesh.weights.append(
float(w.toDouble()));
898 const QJsonArray primitives = object.value(QLatin1String(
"primitives")).toArray();
899 for (
const auto &primitiveValue : primitives) {
900 const QJsonObject primitiveObject = primitiveValue.toObject();
901 MeshPrimitive primitive;
902 const QJsonObject attributes = primitiveObject.value(QLatin1String(
"attributes")).toObject();
903 for (
auto it = attributes.constBegin(); it != attributes.constEnd(); ++it)
904 primitive.attributes.insert(it.key().toUtf8(), it.value().toInt(-1));
905 primitive.indices = primitiveObject.value(QLatin1String(
"indices")).toInt(-1);
906 primitive.material = primitiveObject.value(QLatin1String(
"material")).toInt(-1);
907 primitive.mode = primitiveObject.value(QLatin1String(
"mode")).toInt(MeshPrimitive::Triangles);
908 const QJsonArray targets = primitiveObject.value(QLatin1String(
"targets")).toArray();
909 for (
const auto &targetValue : targets) {
910 const QJsonObject targetObject = targetValue.toObject();
911 QHash<QByteArray,
int> target;
912 for (
auto it = targetObject.constBegin(); it != targetObject.constEnd(); ++it)
913 target.insert(it.key().toUtf8(), it.value().toInt(-1));
914 primitive.targets.append(target);
916 primitive.extensions = primitiveObject.value(QLatin1String(
"extensions")).toObject();
918 const QJsonValue variantsExt = primitive.extensions.value(QLatin1String(
"KHR_materials_variants"));
919 if (variantsExt.isObject()) {
920 const QJsonArray mappings = variantsExt.toObject().value(QLatin1String(
"mappings")).toArray();
921 for (
const auto &mappingValue : mappings) {
922 const QJsonObject mappingObject = mappingValue.toObject();
923 MeshPrimitive::VariantMapping mapping;
924 mapping.material = mappingObject.value(QLatin1String(
"material")).toInt(-1);
925 if (mapping.material < 0 || mapping.material >= document->materials.size()) {
927 QStringLiteral(
"Mesh %1 primitive variant mapping references invalid material %2")
928 .arg(document->meshes.size())
929 .arg(mapping.material));
931 const QJsonArray mappingVariants = mappingObject.value(QLatin1String(
"variants")).toArray();
932 for (
const auto &variantValue : mappingVariants) {
933 const int variant = variantValue.toInt(-1);
934 if (variant < 0 || variant >= document->materialVariants.size()) {
936 QStringLiteral(
"Mesh %1 primitive variant mapping references invalid variant %2")
937 .arg(document->meshes.size())
940 mapping.variants.append(variant);
942 primitive.variantMappings.append(mapping);
950 const auto checkAccessor = [&](
int accessor,
const char *what) {
951 if (accessor >= document->accessors.size())
952 return setError(QStringLiteral(
"Mesh %1 primitive references invalid %2 accessor %3")
953 .arg(document->meshes.size()).arg(QLatin1String(what)).arg(accessor));
956 for (
auto it = primitive.attributes.constBegin(); it != primitive.attributes.constEnd(); ++it) {
957 if (!checkAccessor(it.value(), it.key().constData()))
960 if (primitive.indices >= 0 && !checkAccessor(primitive.indices,
"index"))
962 if (primitive.material >= document->materials.size())
963 return setError(QStringLiteral(
"Mesh %1 primitive references invalid material %2")
964 .arg(document->meshes.size())
965 .arg(primitive.material));
967 mesh.primitives.append(primitive);
969 if (mesh.primitives.isEmpty())
970 qCWarning(lcQuick3DGltf) <<
"Mesh" << document->meshes.size() <<
"has no primitives";
971 document->meshes.append(mesh);
975 const QJsonArray cameras = root.value(QLatin1String(
"cameras")).toArray();
976 for (
const auto &value : cameras) {
977 const QJsonObject object = value.toObject();
979 camera.name = object.value(QLatin1String(
"name")).toString();
980 const QString type = object.value(QLatin1String(
"type")).toString();
981 if (type == QLatin1String(
"orthographic")) {
982 camera.type = Camera::Type::Orthographic;
983 const QJsonObject ortho = object.value(QLatin1String(
"orthographic")).toObject();
984 camera.xmag =
float(ortho.value(QLatin1String(
"xmag")).toDouble());
985 camera.ymag =
float(ortho.value(QLatin1String(
"ymag")).toDouble());
986 camera.znear =
float(ortho.value(QLatin1String(
"znear")).toDouble());
987 camera.zfar =
float(ortho.value(QLatin1String(
"zfar")).toDouble());
989 camera.type = Camera::Type::Perspective;
990 const QJsonObject perspective = object.value(QLatin1String(
"perspective")).toObject();
991 camera.aspectRatio =
float(perspective.value(QLatin1String(
"aspectRatio")).toDouble(0.0));
992 camera.yfov =
float(perspective.value(QLatin1String(
"yfov")).toDouble());
993 camera.znear =
float(perspective.value(QLatin1String(
"znear")).toDouble());
994 camera.zfar =
float(perspective.value(QLatin1String(
"zfar")).toDouble(0.0));
996 document->cameras.append(camera);
1001 const QJsonValue lightsExt = document->rootExtensions.value(QLatin1String(
"KHR_lights_punctual"));
1002 if (lightsExt.isObject()) {
1003 const QJsonArray lights = lightsExt.toObject().value(QLatin1String(
"lights")).toArray();
1004 for (
const auto &value : lights) {
1005 const QJsonObject object = value.toObject();
1007 light.name = object.value(QLatin1String(
"name")).toString();
1008 const QString type = object.value(QLatin1String(
"type")).toString();
1009 if (type == QLatin1String(
"point"))
1010 light.type = Light::Type::Point;
1011 else if (type == QLatin1String(
"spot"))
1012 light.type = Light::Type::Spot;
1014 light.type = Light::Type::Directional;
1015 light.color = toVector3D(object.value(QLatin1String(
"color")).toArray(), light.color);
1016 light.intensity =
float(object.value(QLatin1String(
"intensity")).toDouble(1.0));
1017 light.range =
float(object.value(QLatin1String(
"range")).toDouble(0.0));
1018 if (light.type == Light::Type::Spot) {
1019 const QJsonObject spot = object.value(QLatin1String(
"spot")).toObject();
1020 light.innerConeAngle =
float(spot.value(QLatin1String(
"innerConeAngle")).toDouble(0.0));
1021 light.outerConeAngle =
float(spot.value(QLatin1String(
"outerConeAngle")).toDouble(M_PI_4));
1023 document->lights.append(light);
1029 const QJsonArray nodes = root.value(QLatin1String(
"nodes")).toArray();
1030 for (
const auto &value : nodes) {
1031 const QJsonObject object = value.toObject();
1033 node.name = object.value(QLatin1String(
"name")).toString();
1034 const QJsonArray children = object.value(QLatin1String(
"children")).toArray();
1035 for (
const auto &child : children)
1036 node.children.append(child.toInt(-1));
1037 node.mesh = object.value(QLatin1String(
"mesh")).toInt(-1);
1038 node.skin = object.value(QLatin1String(
"skin")).toInt(-1);
1039 node.camera = object.value(QLatin1String(
"camera")).toInt(-1);
1041 const QJsonValue matrixValue = object.value(QLatin1String(
"matrix"));
1042 if (matrixValue.isArray()) {
1043 const QJsonArray m = matrixValue.toArray();
1044 if (m.size() == 16) {
1046 for (
int i = 0; i < 16; ++i)
1047 values[i] =
float(m.at(i).toDouble());
1049 node.matrix = QMatrix4x4(values).transposed();
1050 node.hasMatrix =
true;
1053 node.translation = toVector3D(object.value(QLatin1String(
"translation")).toArray(), node.translation);
1054 const QJsonArray rotation = object.value(QLatin1String(
"rotation")).toArray();
1055 if (rotation.size() == 4) {
1057 node.rotation = QQuaternion(
float(rotation.at(3).toDouble()),
float(rotation.at(0).toDouble()),
1058 float(rotation.at(1).toDouble()),
float(rotation.at(2).toDouble()));
1060 node.scale = toVector3D(object.value(QLatin1String(
"scale")).toArray(), node.scale);
1061 const QJsonArray weights = object.value(QLatin1String(
"weights")).toArray();
1062 for (
const auto &w : weights)
1063 node.weights.append(
float(w.toDouble()));
1064 node.extensions = object.value(QLatin1String(
"extensions")).toObject();
1066 const QJsonValue lightExt = node.extensions.value(QLatin1String(
"KHR_lights_punctual"));
1067 if (lightExt.isObject())
1068 node.light = lightExt.toObject().value(QLatin1String(
"light")).toInt(-1);
1070 const QJsonValue visibilityExt = node.extensions.value(QLatin1String(
"KHR_node_visibility"));
1071 if (visibilityExt.isObject())
1072 node.visible = visibilityExt.toObject().value(QLatin1String(
"visible")).toBool(
true);
1074 const QJsonValue instancingExt = node.extensions.value(QLatin1String(
"EXT_mesh_gpu_instancing"));
1075 if (instancingExt.isObject()) {
1076 const QJsonObject instanceAttributes =
1077 instancingExt.toObject().value(QLatin1String(
"attributes")).toObject();
1078 for (
auto it = instanceAttributes.constBegin(); it != instanceAttributes.constEnd(); ++it) {
1079 const int accessor = it.value().toInt(-1);
1080 if (accessor < 0 || accessor >= document->accessors.size()) {
1081 return setError(QStringLiteral(
"Node %1 instance attribute %2 references invalid accessor %3")
1082 .arg(document->nodes.size()).arg(it.key()).arg(accessor));
1084 if (it.key() == QLatin1String(
"TRANSLATION"))
1085 node.instanceTranslation = accessor;
1086 else if (it.key() == QLatin1String(
"ROTATION"))
1087 node.instanceRotation = accessor;
1088 else if (it.key() == QLatin1String(
"SCALE"))
1089 node.instanceScale = accessor;
1091 qCWarning(lcQuick3DGltf) <<
"Ignoring unsupported instance attribute" << it.key();
1093 node.hasInstancing = node.instanceTranslation >= 0 || node.instanceRotation >= 0
1094 || node.instanceScale >= 0;
1097 if (node.mesh >= document->meshes.size())
1098 return setError(QStringLiteral(
"Node %1 references invalid mesh %2")
1099 .arg(document->nodes.size()).arg(node.mesh));
1100 if (node.camera >= document->cameras.size())
1101 return setError(QStringLiteral(
"Node %1 references invalid camera %2")
1102 .arg(document->nodes.size()).arg(node.camera));
1103 if (node.light >= document->lights.size())
1104 return setError(QStringLiteral(
"Node %1 references invalid light %2")
1105 .arg(document->nodes.size()).arg(node.light));
1106 document->nodes.append(node);
1114 const int nodeCount = document->nodes.size();
1115 QVarLengthArray<
int, 64> parentCount(nodeCount);
1116 std::fill(parentCount.begin(), parentCount.end(), 0);
1117 for (
int i = 0; i < nodeCount; ++i) {
1118 Node &node = document->nodes[i];
1119 for (qsizetype c = 0; c < node.children.size(); ++c) {
1120 const int child = node.children.at(c);
1121 if (child < 0 || child >= nodeCount)
1122 return setError(QStringLiteral(
"Node %1 references invalid child node %2").arg(i).arg(child));
1123 if (++parentCount[child] > 1) {
1128 qCWarning(lcQuick3DGltf) <<
"Node" << child
1129 <<
"has more than one parent; ignoring the edge from node" << i;
1130 --parentCount[child];
1131 node.children.removeAt(c--);
1139 constexpr int maxNodeDepth = 4096;
1140 QVarLengthArray<
int, 64> parent(nodeCount);
1141 std::fill(parent.begin(), parent.end(), -1);
1142 for (
int i = 0; i < nodeCount; ++i) {
1143 for (
int child : document->nodes.at(i).children)
1146 QVarLengthArray<
int, 64> depth(nodeCount);
1147 std::fill(depth.begin(), depth.end(), -1);
1148 QVarLengthArray<
int, 64> chain;
1149 for (
int i = 0; i < nodeCount; ++i) {
1152 while (n >= 0 && depth[n] < 0 && chain.size() <= nodeCount) {
1156 if (chain.size() > nodeCount)
1157 return setError(QStringLiteral(
"Node hierarchy contains a cycle"));
1158 int d = n >= 0 ? depth[n] : 0;
1159 for (
auto it = chain.rbegin(); it != chain.rend(); ++it)
1161 if (d > maxNodeDepth)
1162 return setError(QStringLiteral(
"Node hierarchy is deeper than %1").arg(maxNodeDepth));
1167 const QJsonArray skins = root.value(QLatin1String(
"skins")).toArray();
1168 for (
const auto &value : skins) {
1169 const QJsonObject object = value.toObject();
1171 skin.name = object.value(QLatin1String(
"name")).toString();
1172 skin.inverseBindMatrices = object.value(QLatin1String(
"inverseBindMatrices")).toInt(-1);
1173 skin.skeleton = object.value(QLatin1String(
"skeleton")).toInt(-1);
1174 const QJsonArray joints = object.value(QLatin1String(
"joints")).toArray();
1175 for (
const auto &joint : joints)
1176 skin.joints.append(joint.toInt(-1));
1178 if (skin.inverseBindMatrices >= document->accessors.size())
1179 return setError(QStringLiteral(
"Skin %1 references invalid accessor %2")
1180 .arg(document->skins.size()).arg(skin.inverseBindMatrices));
1181 for (
int joint : std::as_const(skin.joints)) {
1182 if (joint < 0 || joint >= nodeCount)
1183 return setError(QStringLiteral(
"Skin %1 references invalid joint node %2")
1184 .arg(document->skins.size()).arg(joint));
1186 document->skins.append(skin);
1188 for (
int i = 0; i < nodeCount; ++i) {
1189 if (document->nodes.at(i).skin >= document->skins.size())
1190 return setError(QStringLiteral(
"Node %1 references invalid skin %2")
1191 .arg(i).arg(document->nodes.at(i).skin));
1195 const QJsonArray animations = root.value(QLatin1String(
"animations")).toArray();
1196 for (
const auto &value : animations) {
1197 const QJsonObject object = value.toObject();
1198 Animation animation;
1199 animation.name = object.value(QLatin1String(
"name")).toString();
1201 const QJsonArray animationSamplers = object.value(QLatin1String(
"samplers")).toArray();
1202 for (
const auto &samplerValue : animationSamplers) {
1203 const QJsonObject samplerObject = samplerValue.toObject();
1204 AnimationSampler sampler;
1205 sampler.input = samplerObject.value(QLatin1String(
"input")).toInt(-1);
1206 sampler.output = samplerObject.value(QLatin1String(
"output")).toInt(-1);
1207 const QString interpolation = samplerObject.value(QLatin1String(
"interpolation")).toString();
1208 if (interpolation == QLatin1String(
"STEP"))
1209 sampler.interpolation = AnimationSampler::Interpolation::Step;
1210 else if (interpolation == QLatin1String(
"CUBICSPLINE"))
1211 sampler.interpolation = AnimationSampler::Interpolation::CubicSpline;
1212 if (sampler.input < 0 || sampler.input >= document->accessors.size()
1213 || sampler.output < 0 || sampler.output >= document->accessors.size()) {
1215 QStringLiteral(
"Animation %1 sampler references an invalid accessor")
1216 .arg(document->animations.size()));
1218 animation.samplers.append(sampler);
1221 const QJsonArray channels = object.value(QLatin1String(
"channels")).toArray();
1222 for (
const auto &channelValue : channels) {
1223 const QJsonObject channelObject = channelValue.toObject();
1224 AnimationChannel channel;
1225 channel.sampler = channelObject.value(QLatin1String(
"sampler")).toInt(-1);
1226 const QJsonObject target = channelObject.value(QLatin1String(
"target")).toObject();
1227 channel.targetNode = target.value(QLatin1String(
"node")).toInt(-1);
1228 const QString path = target.value(QLatin1String(
"path")).toString();
1229 if (path == QLatin1String(
"rotation"))
1230 channel.path = AnimationChannel::Path::Rotation;
1231 else if (path == QLatin1String(
"scale"))
1232 channel.path = AnimationChannel::Path::Scale;
1233 else if (path == QLatin1String(
"weights"))
1234 channel.path = AnimationChannel::Path::Weights;
1235 else if (path != QLatin1String(
"translation")) {
1236 qCWarning(lcQuick3DGltf) <<
"Ignoring animation channel with unsupported path" << path;
1239 if (channel.sampler < 0 || channel.sampler >= animation.samplers.size())
1240 return setError(QStringLiteral(
"Animation %1 channel references invalid sampler %2")
1241 .arg(document->animations.size())
1242 .arg(channel.sampler));
1243 if (channel.targetNode >= nodeCount)
1244 return setError(QStringLiteral(
"Animation %1 channel references invalid node %2")
1245 .arg(document->animations.size())
1246 .arg(channel.targetNode));
1247 animation.channels.append(channel);
1250 document->animations.append(animation);
1254 const QJsonArray scenes = root.value(QLatin1String(
"scenes")).toArray();
1255 for (
const auto &value : scenes) {
1256 const QJsonObject object = value.toObject();
1258 scene.name = object.value(QLatin1String(
"name")).toString();
1259 QSet<
int> seenRoots;
1260 const QJsonArray sceneNodes = object.value(QLatin1String(
"nodes")).toArray();
1261 for (
const auto &nodeIndex : sceneNodes) {
1262 const int index = nodeIndex.toInt(-1);
1263 if (index < 0 || index >= nodeCount)
1264 return setError(QStringLiteral(
"Scene %1 references invalid node %2")
1265 .arg(document->scenes.size()).arg(index));
1270 if (parentCount[index] != 0 || seenRoots.contains(index)) {
1271 qCWarning(lcQuick3DGltf) <<
"Scene" << document->scenes.size() <<
"references node" << index
1272 <<
"which is not a unique root node; ignoring it";
1275 seenRoots.insert(index);
1276 scene.nodes.append(index);
1278 document->scenes.append(scene);
1280 document->scene = root.value(QLatin1String(
"scene")).toInt(-1);
1281 if (document->scene >= document->scenes.size())
1282 return setError(QStringLiteral(
"Document references invalid default scene %1").arg(document->scene));