diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp new file mode 100644 index 0000000000..c8501688ac --- /dev/null +++ b/libraries/fbx/src/GLTFReader.cpp @@ -0,0 +1,1380 @@ +// +// GLTFReader.cpp +// libraries/fbx/src +// +// Created by Luis Cuenca on 8/30/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include "GLTFReader.h" +#include "FBXReader.h" + + +GLTFReader::GLTFReader() { + +} + +bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, + QString& value, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isString()); + if (_defined) { + value = object[fieldname].toString(); + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname, + bool& value, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isBool()); + if (_defined) { + value = object[fieldname].toBool(); + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, + int& value, QMap& defined) { + bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); + if (_defined) { + value = object[fieldname].toInt(); + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, + double& value, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); + if (_defined) { + value = object[fieldname].toDouble(); + } + defined.insert(fieldname, _defined); + return _defined; +} +bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, + QJsonObject& value, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); + if (_defined) { + value = object[fieldname].toObject(); + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); + if (_defined) { + QJsonArray arr = object[fieldname].toArray(); + foreach(const QJsonValue & v, arr) { + if (!v.isNull()) { + values.push_back(v.toInt()); + } + } + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); + if (_defined) { + QJsonArray arr = object[fieldname].toArray(); + foreach(const QJsonValue & v, arr) { + if (v.isDouble()) { + values.push_back(v.toDouble()); + } + } + } + defined.insert(fieldname, _defined); + return _defined; +} + +bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, + QJsonArray& objects, QMap& defined) { + bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); + if (_defined) { + objects = object[fieldname].toArray(); + } + defined.insert(fieldname, _defined); + return _defined; +} + +int GLTFReader::getMeshPrimitiveRenderingMode(const QString& type) +{ + if (type == "POINTS") { + return GLTFMeshPrimitivesRenderingMode::POINTS; + } + if (type == "LINES") { + return GLTFMeshPrimitivesRenderingMode::LINES; + } + if (type == "LINE_LOOP") { + return GLTFMeshPrimitivesRenderingMode::LINE_LOOP; + } + if (type == "LINE_STRIP") { + return GLTFMeshPrimitivesRenderingMode::LINE_STRIP; + } + if (type == "TRIANGLES") { + return GLTFMeshPrimitivesRenderingMode::TRIANGLES; + } + if (type == "TRIANGLE_STRIP") { + return GLTFMeshPrimitivesRenderingMode::TRIANGLE_STRIP; + } + if (type == "TRIANGLE_FAN") { + return GLTFMeshPrimitivesRenderingMode::TRIANGLE_FAN; + } + return GLTFMeshPrimitivesRenderingMode::TRIANGLES; +} + +int GLTFReader::getAccessorType(const QString& type) +{ + if (type == "SCALAR") { + return GLTFAccessorType::SCALAR; + } + if (type == "VEC2") { + return GLTFAccessorType::VEC2; + } + if (type == "VEC3") { + return GLTFAccessorType::VEC3; + } + if (type == "VEC4") { + return GLTFAccessorType::VEC4; + } + if (type == "MAT2") { + return GLTFAccessorType::MAT2; + } + if (type == "MAT3") { + return GLTFAccessorType::MAT3; + } + if (type == "MAT4") { + return GLTFAccessorType::MAT4; + } + return GLTFAccessorType::SCALAR; +} + +int GLTFReader::getMaterialAlphaMode(const QString& type) +{ + if (type == "OPAQUE") { + return GLTFMaterialAlphaMode::OPAQUE; + } + if (type == "MASK") { + return GLTFMaterialAlphaMode::MASK; + } + if (type == "BLEND") { + return GLTFMaterialAlphaMode::BLEND; + } + return GLTFMaterialAlphaMode::OPAQUE; +} + +int GLTFReader::getCameraType(const QString& type) +{ + if (type == "orthographic") { + return GLTFCameraTypes::ORTHOGRAPHIC; + } + if (type == "perspective") { + return GLTFCameraTypes::PERSPECTIVE; + } + return GLTFCameraTypes::PERSPECTIVE; +} + +int GLTFReader::getImageMimeType(const QString& mime) +{ + if (mime == "image/jpeg") { + return GLTFImageMimetype::JPEG; + } + if (mime == "image/png") { + return GLTFImageMimetype::PNG; + } + return GLTFImageMimetype::JPEG; +} + +int GLTFReader::getAnimationSamplerInterpolation(const QString& interpolation) +{ + if (interpolation == "LINEAR") { + return GLTFAnimationSamplerInterpolation::LINEAR; + } + return GLTFAnimationSamplerInterpolation::LINEAR; +} + +bool GLTFReader::setAsset(const QJsonObject& object) { + QJsonObject jsAsset; + bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); + if (isAssetDefined) { + if (!getStringVal(jsAsset, "version", _file.asset.version, + _file.asset.defined) || _file.asset.version != "2.0") { + return false; + } + getStringVal(jsAsset, "generator", _file.asset.generator, _file.asset.defined); + getStringVal(jsAsset, "copyright", _file.asset.copyright, _file.asset.defined); + } + return isAssetDefined; +} + +bool GLTFReader::addAccessor(const QJsonObject& object) { + GLTFAccessor accessor; + + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); + getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); + getIntVal(object, "componentType", accessor.componentType, accessor.defined); + getIntVal(object, "count", accessor.count, accessor.defined); + getBoolVal(object, "normalized", accessor.normalized, accessor.defined); + QString type; + if (getStringVal(object, "type", type, accessor.defined)) { + accessor.type = getAccessorType(type); + } + getDoubleArrayVal(object, "max", accessor.max, accessor.defined); + getDoubleArrayVal(object, "min", accessor.min, accessor.defined); + + _file.accessors.push_back(accessor); + + return true; +} + +bool GLTFReader::addAnimation(const QJsonObject& object) { + GLTFAnimation animation; + + QJsonArray channels; + if (getObjectArrayVal(object, "channels", channels, animation.defined)) { + foreach(const QJsonValue & v, channels) { + if (v.isObject()) { + GLTFChannel channel; + getIntVal(v.toObject(), "sampler", channel.sampler, channel.defined); + QJsonObject jsChannel; + if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { + getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); + getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); + } + } + } + } + + QJsonArray samplers; + if (getObjectArrayVal(object, "samplers", samplers, animation.defined)) { + foreach(const QJsonValue & v, samplers) { + if (v.isObject()) { + GLTFAnimationSampler sampler; + getIntVal(v.toObject(), "input", sampler.input, sampler.defined); + getIntVal(v.toObject(), "output", sampler.input, sampler.defined); + QString interpolation; + if (getStringVal(v.toObject(), "interpolation", interpolation, sampler.defined)) { + sampler.interpolation = getAnimationSamplerInterpolation(interpolation); + } + } + } + } + + _file.animations.push_back(animation); + + return true; +} + +bool GLTFReader::addBufferView(const QJsonObject& object) { + GLTFBufferView bufferview; + + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); + getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); + getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); + getIntVal(object, "target", bufferview.target, bufferview.defined); + + _file.bufferviews.push_back(bufferview); + + return true; +} + +bool GLTFReader::addBuffer(const QJsonObject& object) { + GLTFBuffer buffer; + + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); + if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { + if (!readBinary(buffer.uri, buffer.blob)) { + return false; + } + } + _file.buffers.push_back(buffer); + + return true; +} + +bool GLTFReader::addCamera(const QJsonObject& object) { + GLTFCamera camera; + + QJsonObject jsPerspective; + QJsonObject jsOrthographic; + QString type; + getStringVal(object, "name", camera.name, camera.defined); + if (getObjectVal(object, "perspective", jsPerspective, camera.defined)) { + getDoubleVal(jsPerspective, "aspectRatio", camera.perspective.aspectRatio, camera.perspective.defined); + getDoubleVal(jsPerspective, "yfov", camera.perspective.yfov, camera.perspective.defined); + getDoubleVal(jsPerspective, "zfar", camera.perspective.zfar, camera.perspective.defined); + getDoubleVal(jsPerspective, "znear", camera.perspective.znear, camera.perspective.defined); + camera.type = GLTFCameraTypes::PERSPECTIVE; + } else if (getObjectVal(object, "orthographic", jsOrthographic, camera.defined)) { + getDoubleVal(jsOrthographic, "zfar", camera.orthographic.zfar, camera.orthographic.defined); + getDoubleVal(jsOrthographic, "znear", camera.orthographic.znear, camera.orthographic.defined); + getDoubleVal(jsOrthographic, "xmag", camera.orthographic.xmag, camera.orthographic.defined); + getDoubleVal(jsOrthographic, "ymag", camera.orthographic.ymag, camera.orthographic.defined); + camera.type = GLTFCameraTypes::ORTHOGRAPHIC; + } else if (getStringVal(object, "type", type, camera.defined)) { + camera.type = getCameraType(type); + } + + _file.cameras.push_back(camera); + + return true; +} + +bool GLTFReader::addImage(const QJsonObject& object) { + GLTFImage image; + + QString mime; + getStringVal(object, "uri", image.uri, image.defined); + if (getStringVal(object, "mimeType", mime, image.defined)) { + image.mimeType = getImageMimeType(mime); + } + getIntVal(object, "bufferView", image.bufferView, image.defined); + + _file.images.push_back(image); + + return true; +} + +bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, + int& outidx, QMap& defined) { + QJsonObject subobject; + if (getObjectVal(object, field, subobject, defined)) { + QMap tmpdefined = QMap(); + return getIntVal(subobject, "index", outidx, tmpdefined); + } + return false; +} + +bool GLTFReader::addMaterial(const QJsonObject& object) { + GLTFMaterial material; + + getStringVal(object, "name", material.name, material.defined); + getDoubleArrayVal(object, "emissiveFactor", material.emissiveFactor, material.defined); + getIndexFromObject(object, "emissiveTexture", material.emissiveTexture, material.defined); + getIndexFromObject(object, "normalTexture", material.normalTexture, material.defined); + getIndexFromObject(object, "occlusionTexture", material.occlusionTexture, material.defined); + getBoolVal(object, "doubleSided", material.doubleSided, material.defined); + QString alphamode; + if (getStringVal(object, "alphaMode", alphamode, material.defined)) { + material.alphaMode = getMaterialAlphaMode(alphamode); + } + getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); + QJsonObject jsMetallicRoughness; + if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", + material.pbrMetallicRoughness.baseColorFactor, + material.pbrMetallicRoughness.defined); + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", + material.pbrMetallicRoughness.baseColorTexture, + material.pbrMetallicRoughness.defined); + getDoubleVal(jsMetallicRoughness, "metallicFactor", + material.pbrMetallicRoughness.metallicFactor, + material.pbrMetallicRoughness.defined); + getDoubleVal(jsMetallicRoughness, "roughnessFactor", + material.pbrMetallicRoughness.roughnessFactor, + material.pbrMetallicRoughness.defined); + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, + material.pbrMetallicRoughness.defined); + } + _file.materials.push_back(material); + return true; +} + +bool GLTFReader::addMesh(const QJsonObject& object) { + GLTFMesh mesh; + + getStringVal(object, "name", mesh.name, mesh.defined); + getDoubleArrayVal(object, "weights", mesh.weights, mesh.defined); + QJsonArray jsPrimitives; + object.keys(); + if (getObjectArrayVal(object, "primitives", jsPrimitives, mesh.defined)) { + foreach(const QJsonValue & prim, jsPrimitives) { + if (prim.isObject()) { + GLTFMeshPrimitive primitive; + QJsonObject jsPrimitive = prim.toObject(); + getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); + getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); + getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); + + QJsonObject jsAttributes; + if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { + QStringList attrKeys = jsAttributes.keys(); + foreach(const QString & attrKey, attrKeys) { + int attrVal; + getIntVal(jsAttributes, attrKey, attrVal, primitive.attributes.defined); + primitive.attributes.values.insert(attrKey, attrVal); + } + } + + QJsonArray jsTargets; + if (getObjectArrayVal(jsPrimitive, "targets", jsTargets, primitive.defined)) + { + foreach(const QJsonValue & tar, jsTargets) { + if (tar.isObject()) { + QJsonObject jsTarget = tar.toObject(); + QStringList tarKeys = jsTarget.keys(); + GLTFMeshPrimitiveAttr target; + foreach(const QString & tarKey, tarKeys) { + int tarVal; + getIntVal(jsAttributes, tarKey, tarVal, target.defined); + target.values.insert(tarKey, tarVal); + } + primitive.targets.push_back(target); + } + } + } + mesh.primitives.push_back(primitive); + } + } + + } + + _file.meshes.push_back(mesh); + + return true; +} + +bool GLTFReader::addNode(const QJsonObject& object) { + GLTFNode node; + + getStringVal(object, "name", node.name, node.defined); + getIntVal(object, "camera", node.camera, node.defined); + getIntVal(object, "mesh", node.mesh, node.defined); + getIntArrayVal(object, "children", node.children, node.defined); + getDoubleArrayVal(object, "translation", node.translation, node.defined); + getDoubleArrayVal(object, "rotation", node.rotation, node.defined); + getDoubleArrayVal(object, "scale", node.scale, node.defined); + getDoubleArrayVal(object, "matrix", node.matrix, node.defined); + getIntVal(object, "skin", node.skin, node.defined); + getStringVal(object, "jointName", node.jointName, node.defined); + getIntArrayVal(object, "skeletons", node.skeletons, node.defined); + + _file.nodes.push_back(node); + + return true; +} + +bool GLTFReader::addSampler(const QJsonObject& object) { + GLTFSampler sampler; + + getIntVal(object, "magFilter", sampler.magFilter, sampler.defined); + getIntVal(object, "minFilter", sampler.minFilter, sampler.defined); + getIntVal(object, "wrapS", sampler.wrapS, sampler.defined); + getIntVal(object, "wrapT", sampler.wrapT, sampler.defined); + + _file.samplers.push_back(sampler); + + return true; + +} + +bool GLTFReader::addScene(const QJsonObject& object) { + GLTFScene scene; + + getStringVal(object, "name", scene.name, scene.defined); + getIntArrayVal(object, "nodes", scene.nodes, scene.defined); + + _file.scenes.push_back(scene); + return true; +} + +bool GLTFReader::addSkin(const QJsonObject& object) { + GLTFSkin skin; + + getIntVal(object, "inverseBindMatrices", skin.inverseBindMatrices, skin.defined); + getIntVal(object, "skeleton", skin.skeleton, skin.defined); + getIntArrayVal(object, "joints", skin.joints, skin.defined); + + _file.skins.push_back(skin); + + return true; +} + +bool GLTFReader::addTexture(const QJsonObject& object) { + GLTFTexture texture; + getIntVal(object, "sampler", texture.sampler, texture.defined); + getIntVal(object, "source", texture.source, texture.defined); + + _file.textures.push_back(texture); + + return true; +} + +bool GLTFReader::parseGLTF(const QByteArray& model) { + PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); + + QJsonDocument d = QJsonDocument::fromJson(model); + QJsonObject jsFile = d.object(); + + bool isvalid = setAsset(jsFile); + if (isvalid) { + QJsonArray accessors; + if (getObjectArrayVal(jsFile, "accessors", accessors, _file.defined)) { + foreach(const QJsonValue & accVal, accessors) { + if (accVal.isObject()) { + addAccessor(accVal.toObject()); + } + } + } + + QJsonArray animations; + if (getObjectArrayVal(jsFile, "animations", animations, _file.defined)) { + foreach(const QJsonValue & animVal, accessors) { + if (animVal.isObject()) { + addAnimation(animVal.toObject()); + } + } + } + + QJsonArray bufferViews; + if (getObjectArrayVal(jsFile, "bufferViews", bufferViews, _file.defined)) { + foreach(const QJsonValue & bufviewVal, bufferViews) { + if (bufviewVal.isObject()) { + addBufferView(bufviewVal.toObject()); + } + } + } + + QJsonArray buffers; + if (getObjectArrayVal(jsFile, "buffers", buffers, _file.defined)) { + foreach(const QJsonValue & bufVal, buffers) { + if (bufVal.isObject()) { + addBuffer(bufVal.toObject()); + } + } + } + + QJsonArray cameras; + if (getObjectArrayVal(jsFile, "cameras", cameras, _file.defined)) { + foreach(const QJsonValue & camVal, cameras) { + if (camVal.isObject()) { + addCamera(camVal.toObject()); + } + } + } + + QJsonArray images; + if (getObjectArrayVal(jsFile, "images", images, _file.defined)) { + foreach(const QJsonValue & imgVal, images) { + if (imgVal.isObject()) { + addImage(imgVal.toObject()); + } + } + } + + QJsonArray materials; + if (getObjectArrayVal(jsFile, "materials", materials, _file.defined)) { + foreach(const QJsonValue & matVal, materials) { + if (matVal.isObject()) { + addMaterial(matVal.toObject()); + } + } + } + + QJsonArray meshes; + if (getObjectArrayVal(jsFile, "meshes", meshes, _file.defined)) { + foreach(const QJsonValue & meshVal, meshes) { + if (meshVal.isObject()) { + addMesh(meshVal.toObject()); + } + } + } + + QJsonArray nodes; + if (getObjectArrayVal(jsFile, "nodes", nodes, _file.defined)) { + foreach(const QJsonValue & nodeVal, nodes) { + if (nodeVal.isObject()) { + addNode(nodeVal.toObject()); + } + } + } + + QJsonArray samplers; + if (getObjectArrayVal(jsFile, "samplers", samplers, _file.defined)) { + foreach(const QJsonValue & samVal, samplers) { + if (samVal.isObject()) { + addSampler(samVal.toObject()); + } + } + } + + QJsonArray scenes; + if (getObjectArrayVal(jsFile, "scenes", scenes, _file.defined)) { + foreach(const QJsonValue & sceneVal, scenes) { + if (sceneVal.isObject()) { + addScene(sceneVal.toObject()); + } + } + } + + QJsonArray skins; + if (getObjectArrayVal(jsFile, "skins", skins, _file.defined)) { + foreach(const QJsonValue & skinVal, skins) { + if (skinVal.isObject()) { + addSkin(skinVal.toObject()); + } + } + } + + QJsonArray textures; + if (getObjectArrayVal(jsFile, "textures", textures, _file.defined)) { + foreach(const QJsonValue & texVal, textures) { + if (texVal.isObject()) { + addTexture(texVal.toObject()); + } + } + } + } else { + qCDebug(modelformat) << "Error parsing GLTF file."; + return false; + } + return true; +} + +glm::mat4 GLTFReader::getModelTransform(const GLTFNode& node) { + glm::mat4 tmat = glm::mat4(1.0); + + if (node.defined["matrix"] && node.matrix.size() == 16) { + tmat = glm::mat4(node.matrix[0], node.matrix[1], node.matrix[2], node.matrix[3], + node.matrix[4], node.matrix[5], node.matrix[6], node.matrix[7], + node.matrix[8], node.matrix[9], node.matrix[10], node.matrix[11], + node.matrix[12], node.matrix[13], node.matrix[14], node.matrix[15]); + } else { + + if (node.defined["rotation"] && node.rotation.size() == 4) { + //quat(x,y,z,w) to quat(w,x,y,z) + glm::quat rotquat = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]); + tmat = glm::mat4_cast(rotquat) * tmat; + } + + if (node.defined["scale"] && node.scale.size() == 3) { + glm::vec3 scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]); + glm::mat4 s = glm::mat4(1.0); + s = glm::scale(s, scale); + tmat = s * tmat; + } + + if (node.defined["translation"] && node.translation.size() == 3) { + glm::vec3 trans = glm::vec3(node.translation[0], node.translation[1], node.translation[2]); + glm::mat4 t = glm::mat4(1.0); + t = glm::translate(t, trans); + tmat = t * tmat; + } + } + return tmat; +} + +bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { + + //Build dependencies + QVector> nodeDependencies(_file.nodes.size()); + int nodecount = 0; + foreach(auto &node, _file.nodes) { + //nodes_transforms.push_back(getModelTransform(node)); + foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); + nodecount++; + } + + nodecount = 0; + foreach(auto &node, _file.nodes) { + // collect node transform + _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); + if (nodeDependencies[nodecount].size() == 1) { + int parentidx = nodeDependencies[nodecount][0]; + while (true) { // iterate parents + // collect parents transforms + _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); + if (nodeDependencies[parentidx].size() == 1) { + parentidx = nodeDependencies[parentidx][0]; + } else break; + } + } + + nodecount++; + } + + //Build default joints + geometry.joints.resize(1); + geometry.joints[0].isFree = false; + geometry.joints[0].parentIndex = -1; + geometry.joints[0].distanceToParent = 0; + geometry.joints[0].translation = glm::vec3(0, 0, 0); + geometry.joints[0].rotationMin = glm::vec3(0, 0, 0); + geometry.joints[0].rotationMax = glm::vec3(0, 0, 0); + geometry.joints[0].name = "OBJ"; + geometry.joints[0].isSkeletonJoint = true; + + geometry.jointIndices["x"] = 1; + + //Build materials + QVector materialIDs; + QString unknown = "Default"; + int ukcount = 0; + foreach(auto material, _file.materials) { + QString mid = (material.defined["name"]) ? material.name : unknown + ukcount++; + materialIDs.push_back(mid); + } + + for (int i = 0; i < materialIDs.size(); i++) { + QString& matid = materialIDs[i]; + geometry.materials[matid] = FBXMaterial(); + FBXMaterial& fbxMaterial = geometry.materials[matid]; + fbxMaterial._material = std::make_shared(); + setFBXMaterial(fbxMaterial, _file.materials[i]); + } + + + + nodecount = 0; + // Build meshes + foreach(auto &node, _file.nodes) { + + if (node.defined["mesh"]) { + qCDebug(modelformat) << "node_transforms" << node.transforms; + foreach(auto &primitive, _file.meshes[node.mesh].primitives) { + geometry.meshes.append(FBXMesh()); + FBXMesh& mesh = geometry.meshes[geometry.meshes.size() - 1]; + FBXCluster cluster; + cluster.jointIndex = 0; + cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + mesh.clusters.append(cluster); + + FBXMeshPart part = FBXMeshPart(); + + int indicesAccessorIdx = primitive.indices; + + GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; + GLTFBufferView& indicesBufferview = _file.bufferviews[indicesAccessor.bufferView]; + GLTFBuffer& indicesBuffer = _file.buffers[indicesBufferview.buffer]; + + int indicesAccBoffset = indicesAccessor.defined["byteOffset"] ? indicesAccessor.byteOffset : 0; + + QVector raw_indices; + QVector raw_vertices; + QVector raw_normals; + + addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + indicesAccBoffset, + indicesBufferview.byteLength, + part.triangleIndices, + indicesAccessor.type, + indicesAccessor.componentType); + + QList keys = primitive.attributes.values.keys(); + + foreach(auto &key, keys) { + int accessorIdx = primitive.attributes.values[key]; + + GLTFAccessor& accessor = _file.accessors[accessorIdx]; + GLTFBufferView& bufferview = _file.bufferviews[accessor.bufferView]; + GLTFBuffer& buffer = _file.buffers[bufferview.buffer]; + + int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0; + if (key == "POSITION") { + QVector vertices; + addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + bufferview.byteLength, vertices, + accessor.type, + accessor.componentType); + for (int n = 0; n < vertices.size(); n = n + 3) { + mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); + } + } else if (key == "NORMAL") { + QVector normals; + addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + bufferview.byteLength, + normals, + accessor.type, + accessor.componentType); + for (int n = 0; n < normals.size(); n = n + 3) { + mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); + } + } else if (key == "TEXCOORD_0") { + QVector texcoords; + addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + bufferview.byteLength, + texcoords, + accessor.type, + accessor.componentType); + for (int n = 0; n < texcoords.size(); n = n + 2) { + mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + } + } else if (key == "TEXCOORD_1") { + QVector texcoords; + addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + bufferview.byteLength, + texcoords, + accessor.type, + accessor.componentType); + for (int n = 0; n < texcoords.size(); n = n + 2) { + mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); + } + } + + } + + if (primitive.defined["material"]) { + part.materialID = materialIDs[primitive.material]; + } + mesh.parts.push_back(part); + + // populate the texture coordenates if they don't exist + if (mesh.texCoords.size() == 0) { + for (int i = 0; i < part.triangleIndices.size(); i++) mesh.texCoords.push_back(glm::vec2(0.0, 1.0)); + } + mesh.meshExtents.reset(); + foreach(const glm::vec3& vertex, mesh.vertices) { + mesh.meshExtents.addPoint(vertex); + geometry.meshExtents.addPoint(vertex); + } + + // since mesh.modelTransform seems to not have any effect I apply the transformation the model + for (int h = 0; h < mesh.vertices.size(); h++) { + glm::vec4 ver = glm::vec4(mesh.vertices[h], 1); + if (node.transforms.size() > 0) { + ver = node.transforms[0] * ver; // for model dependency should multiply also by parents transforms? + mesh.vertices[h] = glm::vec3(ver[0], ver[1], ver[2]); + } + } + + mesh.meshIndex = geometry.meshes.size(); + FBXReader::buildModelMesh(mesh, url.toString()); + } + + } + nodecount++; + } + + + return true; +} + +FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, + const QUrl& url, bool loadLightmaps, float lightmapLevel) { + _url = url; + + parseGLTF(model); + //_file.dump(); + FBXGeometry* geometryPtr = new FBXGeometry(); + FBXGeometry& geometry = *geometryPtr; + + buildGeometry(geometry, url); + + //fbxDebugDump(geometry); + return geometryPtr; + +} + +bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { + QUrl binaryUrl = _url.resolved(QUrl(url).fileName()); + qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; + bool success; + std::tie(success, outdata) = requestData(binaryUrl); + + return success; +} + +bool GLTFReader::doesResourceExist(const QString& url) { + if (_url.isEmpty()) { + return false; + } + QUrl candidateUrl = _url.resolved(QUrl(url).fileName()); + return DependencyManager::get()->resourceExists(candidateUrl); +} + +std::tuple GLTFReader::requestData(QUrl& url) { + auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + + if (!request) { + return std::make_tuple(false, QByteArray()); + } + + QEventLoop loop; + QObject::connect(request, &ResourceRequest::finished, &loop, &QEventLoop::quit); + request->send(); + loop.exec(); + + if (request->getResult() == ResourceRequest::Success) { + return std::make_tuple(true, request->getData()); + } else { + return std::make_tuple(false, QByteArray()); + } +} + + +QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { + if (!qApp) { + return nullptr; + } + bool aboutToQuit{ false }; + auto connection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [&] { + aboutToQuit = true; + }); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest netRequest(url); + netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest); + if (!qApp || aboutToQuit) { + netReply->deleteLater(); + return nullptr; + } + QEventLoop loop; // Create an event loop that will quit when we get the finished signal + QObject::connect(netReply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); // Nothing is going to happen on this whole run thread until we get this + + QObject::disconnect(connection); + return netReply; // trying to sync later on. +} + +FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { + FBXTexture fbxtex = FBXTexture(); + fbxtex.texcoordSet = 0; + + if (texture.defined["source"]) { + QString fname = QUrl(_file.images[texture.source].uri).fileName(); + QUrl textureUrl = _url.resolved(fname); + qCDebug(modelformat) << "fname: " << fname; + qCDebug(modelformat) << "textureUrl: " << textureUrl; + qCDebug(modelformat) << "Url: " << _url; + fbxtex.name = fname; + fbxtex.filename = textureUrl.toEncoded(); + } + return fbxtex; +} + +void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& material) { + + + if (material.defined["name"]) { + fbxmat.name = fbxmat.materialID = material.name; + } + + if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) { + glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], + material.emissiveFactor[1], + material.emissiveFactor[2]); + fbxmat._material->setEmissive(emissive); + } + + if (material.defined["emissiveTexture"]) { + fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); + fbxmat.useEmissiveMap = true; + } + + if (material.defined["normalTexture"]) { + fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); + fbxmat.useNormalMap = true; + } + + if (material.defined["occlusionTexture"]) { + fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); + fbxmat.useOcclusionMap = true; + } + + if (material.defined["pbrMetallicRoughness"]) { + fbxmat.isPBSMaterial = true; + + if (material.pbrMetallicRoughness.defined["metallicFactor"]) { + fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; + } + if (material.pbrMetallicRoughness.defined["baseColorTexture"]) { + fbxmat.opacityTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.albedoTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.useAlbedoMap = true; + fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.useMetallicMap = true; + } + if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { + fbxmat.roughnessTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.useRoughnessMap = true; + + } + if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { + fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); + } + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + material.pbrMetallicRoughness.baseColorFactor.size() == 4) { + glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], + material.pbrMetallicRoughness.baseColorFactor[2]); + fbxmat.diffuseColor = dcolor; + fbxmat._material->setAlbedo(dcolor); + fbxmat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); + } + } + +} + +template +bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int byteLength, + QVector& outarray, int accessorType) { + + QDataStream blobstream(bin); + blobstream.setByteOrder(QDataStream::LittleEndian); + blobstream.setVersion(QDataStream::Qt_5_9); + blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision); + + int vsize = byteLength / sizeof(T); + + qCDebug(modelformat) << "size1: " << vsize; + int dataskipped = blobstream.skipRawData(byteOffset); + qCDebug(modelformat) << "dataskipped: " << dataskipped; + + + while (outarray.size() < vsize) { + + T value1, value2, value3, value4, + value5, value6, value7, value8, + value9, value10, value11, value12, + value13, value14, value15, value16; + + if (accessorType == GLTFAccessorType::SCALAR) { + + blobstream >> value1; + + outarray.push_back(value1); + } else if (accessorType == GLTFAccessorType::VEC2) { + + blobstream >> value1; + blobstream >> value2; + + outarray.push_back(value1); + outarray.push_back(value2); + } else if (accessorType == GLTFAccessorType::VEC3) { + + blobstream >> value1; + blobstream >> value2; + blobstream >> value3; + + outarray.push_back(value1); + outarray.push_back(value2); + outarray.push_back(value3); + } else if (accessorType == GLTFAccessorType::VEC4 || accessorType == GLTFAccessorType::MAT2) { + + blobstream >> value1; + blobstream >> value2; + blobstream >> value3; + blobstream >> value4; + + outarray.push_back(value1); + outarray.push_back(value2); + outarray.push_back(value3); + outarray.push_back(value4); + } else if (accessorType == GLTFAccessorType::MAT3) { + + blobstream >> value1; + blobstream >> value2; + blobstream >> value3; + blobstream >> value4; + blobstream >> value5; + blobstream >> value6; + blobstream >> value7; + blobstream >> value8; + blobstream >> value9; + + outarray.push_back(value1); + outarray.push_back(value2); + outarray.push_back(value3); + outarray.push_back(value4); + outarray.push_back(value5); + outarray.push_back(value6); + outarray.push_back(value7); + outarray.push_back(value8); + outarray.push_back(value9); + } else if (accessorType == GLTFAccessorType::MAT4) { + + blobstream >> value1; + blobstream >> value2; + blobstream >> value3; + blobstream >> value4; + blobstream >> value5; + blobstream >> value6; + blobstream >> value7; + blobstream >> value8; + blobstream >> value9; + blobstream >> value10; + blobstream >> value11; + blobstream >> value12; + blobstream >> value13; + blobstream >> value14; + blobstream >> value15; + blobstream >> value16; + + outarray.push_back(value1); + outarray.push_back(value2); + outarray.push_back(value3); + outarray.push_back(value4); + outarray.push_back(value5); + outarray.push_back(value6); + outarray.push_back(value7); + outarray.push_back(value8); + outarray.push_back(value9); + outarray.push_back(value10); + outarray.push_back(value11); + outarray.push_back(value12); + outarray.push_back(value13); + outarray.push_back(value14); + outarray.push_back(value15); + outarray.push_back(value16); + + } + } + blobstream.unsetDevice(); + return true; +} +template +bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength, + QVector& outarray, int accessorType, int componentType) { + + switch (componentType) { + case GLTFAccessorComponentType::BYTE: {} + case GLTFAccessorComponentType::UNSIGNED_BYTE: { + readArray(bin, byteOffset, byteLength, outarray, accessorType); + break; + } + case GLTFAccessorComponentType::SHORT: { + readArray(bin, byteOffset, byteLength, outarray, accessorType); + break; + } + case GLTFAccessorComponentType::UNSIGNED_INT: { + readArray(bin, byteOffset, byteLength, outarray, accessorType); + break; + } + case GLTFAccessorComponentType::UNSIGNED_SHORT: { + readArray(bin, byteOffset, byteLength, outarray, accessorType); + break; + } + case GLTFAccessorComponentType::FLOAT: { + readArray(bin, byteOffset, byteLength, outarray, accessorType); + break; + } + } + return true; +} + +void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, + const QVector& in_normals, QVector& outIndices, + QVector& out_vertices, QVector& out_normals) { + for (int i = 0; i < inIndices.size(); i = i + 3) { + + int idx1 = inIndices[i]; + int idx2 = inIndices[i+1]; + int idx3 = inIndices[i+2]; + + out_vertices.push_back(in_vertices[idx1]); + out_vertices.push_back(in_vertices[idx2]); + out_vertices.push_back(in_vertices[idx3]); + + out_normals.push_back(in_normals[idx1]); + out_normals.push_back(in_normals[idx2]); + out_normals.push_back(in_normals[idx3]); + + outIndices.push_back(i); + outIndices.push_back(i+1); + outIndices.push_back(i+2); + } +} + +void GLTFReader::fbxDebugDump(const FBXGeometry& fbxgeo) { + qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; + qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; + qCDebug(modelformat) << " offset =" << fbxgeo.offset; + + qCDebug(modelformat) << " leftEyeJointIndex =" << fbxgeo.leftEyeJointIndex; + qCDebug(modelformat) << " rightEyeJointIndex =" << fbxgeo.rightEyeJointIndex; + qCDebug(modelformat) << " neckJointIndex =" << fbxgeo.neckJointIndex; + qCDebug(modelformat) << " rootJointIndex =" << fbxgeo.rootJointIndex; + qCDebug(modelformat) << " leanJointIndex =" << fbxgeo.leanJointIndex; + qCDebug(modelformat) << " headJointIndex =" << fbxgeo.headJointIndex; + qCDebug(modelformat) << " leftHandJointIndex" << fbxgeo.leftHandJointIndex; + qCDebug(modelformat) << " rightHandJointIndex" << fbxgeo.rightHandJointIndex; + qCDebug(modelformat) << " leftToeJointIndex" << fbxgeo.leftToeJointIndex; + qCDebug(modelformat) << " rightToeJointIndex" << fbxgeo.rightToeJointIndex; + qCDebug(modelformat) << " leftEyeSize = " << fbxgeo.leftEyeSize; + qCDebug(modelformat) << " rightEyeSize = " << fbxgeo.rightEyeSize; + + qCDebug(modelformat) << " palmDirection = " << fbxgeo.palmDirection; + + qCDebug(modelformat) << " neckPivot = " << fbxgeo.neckPivot; + + qCDebug(modelformat) << " bindExtents.size() = " << fbxgeo.bindExtents.size(); + qCDebug(modelformat) << " meshExtents.size() = " << fbxgeo.meshExtents.size(); + + qCDebug(modelformat) << " jointIndices.size() =" << fbxgeo.jointIndices.size(); + qCDebug(modelformat) << " joints.count() =" << fbxgeo.joints.count(); + qCDebug(modelformat) << "---------------- Meshes ----------------"; + qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); + qCDebug(modelformat) << " blendshapeChannelNames = " << fbxgeo.blendshapeChannelNames; + foreach(FBXMesh mesh, fbxgeo.meshes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " meshpointer =" << mesh._mesh.get(); + qCDebug(modelformat) << " meshindex =" << mesh.meshIndex; + qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.size(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " normals.count() =" << mesh.normals.size(); + qCDebug(modelformat) << " tangents.count() =" << mesh.tangents.size(); + qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); + qCDebug(modelformat) << " texCoords.count() =" << mesh.texCoords.count(); + qCDebug(modelformat) << " texCoords1.count() =" << mesh.texCoords1.count(); + qCDebug(modelformat) << " clusterIndices.count() =" << mesh.clusterIndices.count(); + qCDebug(modelformat) << " clusterWeights.count() =" << mesh.clusterWeights.count(); + qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; + qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); + qCDebug(modelformat) << "---------------- Meshes (blendshapes)--------"; + foreach(FBXBlendshape bshape, mesh.blendshapes) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " bshape.indices.count() =" << bshape.indices.count(); + qCDebug(modelformat) << " bshape.vertices.count() =" << bshape.vertices.count(); + qCDebug(modelformat) << " bshape.normals.count() =" << bshape.normals.count(); + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "---------------- Meshes (meshparts)--------"; + foreach(FBXMeshPart meshPart, mesh.parts) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); + qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); + qCDebug(modelformat) << " materialID =" << meshPart.materialID; + qCDebug(modelformat) << "\n"; + + } + qCDebug(modelformat) << "---------------- Meshes (clusters)--------"; + qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); + foreach(FBXCluster cluster, mesh.clusters) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; + qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "\n"; + } + qCDebug(modelformat) << "---------------- AnimationFrames ----------------"; + foreach(FBXAnimationFrame anim, fbxgeo.animationFrames) { + qCDebug(modelformat) << " anim.translations = " << anim.translations; + qCDebug(modelformat) << " anim.rotations = " << anim.rotations; + } + QList mitomona_keys = fbxgeo.meshIndicesToModelNames.keys(); + foreach(int key, mitomona_keys) { + qCDebug(modelformat) << " meshIndicesToModelNames key =" << key << " val =" << fbxgeo.meshIndicesToModelNames[key]; + } + + qCDebug(modelformat) << "---------------- Materials ----------------"; + + foreach(FBXMaterial mat, fbxgeo.materials) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " mat.materialID =" << mat.materialID; + qCDebug(modelformat) << " diffuseColor =" << mat.diffuseColor; + qCDebug(modelformat) << " diffuseFactor =" << mat.diffuseFactor; + qCDebug(modelformat) << " specularColor =" << mat.specularColor; + qCDebug(modelformat) << " specularFactor =" << mat.specularFactor; + qCDebug(modelformat) << " emissiveColor =" << mat.emissiveColor; + qCDebug(modelformat) << " emissiveFactor =" << mat.emissiveFactor; + qCDebug(modelformat) << " shininess =" << mat.shininess; + qCDebug(modelformat) << " opacity =" << mat.opacity; + qCDebug(modelformat) << " metallic =" << mat.metallic; + qCDebug(modelformat) << " roughness =" << mat.roughness; + qCDebug(modelformat) << " emissiveIntensity =" << mat.emissiveIntensity; + qCDebug(modelformat) << " ambientFactor =" << mat.ambientFactor; + + qCDebug(modelformat) << " materialID =" << mat.materialID; + qCDebug(modelformat) << " name =" << mat.name; + qCDebug(modelformat) << " shadingModel =" << mat.shadingModel; + qCDebug(modelformat) << " _material =" << mat._material.get(); + + qCDebug(modelformat) << " normalTexture =" << mat.normalTexture.filename; + qCDebug(modelformat) << " albedoTexture =" << mat.albedoTexture.filename; + qCDebug(modelformat) << " opacityTexture =" << mat.opacityTexture.filename; + qCDebug(modelformat) << " glossTexture =" << mat.glossTexture.filename; + qCDebug(modelformat) << " roughnessTexture =" << mat.roughnessTexture.filename; + qCDebug(modelformat) << " specularTexture =" << mat.specularTexture.filename; + qCDebug(modelformat) << " metallicTexture =" << mat.metallicTexture.filename; + qCDebug(modelformat) << " emissiveTexture =" << mat.emissiveTexture.filename; + qCDebug(modelformat) << " occlusionTexture =" << mat.occlusionTexture.filename; + qCDebug(modelformat) << " scatteringTexture =" << mat.scatteringTexture.filename; + qCDebug(modelformat) << " lightmapTexture =" << mat.lightmapTexture.filename; + + qCDebug(modelformat) << " lightmapParams =" << mat.lightmapParams; + + qCDebug(modelformat) << " isPBSMaterial =" << mat.isPBSMaterial; + qCDebug(modelformat) << " useNormalMap =" << mat.useNormalMap; + qCDebug(modelformat) << " useAlbedoMap =" << mat.useAlbedoMap; + qCDebug(modelformat) << " useOpacityMap =" << mat.useOpacityMap; + qCDebug(modelformat) << " useRoughnessMap =" << mat.useRoughnessMap; + qCDebug(modelformat) << " useSpecularMap =" << mat.useSpecularMap; + qCDebug(modelformat) << " useMetallicMap =" << mat.useMetallicMap; + qCDebug(modelformat) << " useEmissiveMap =" << mat.useEmissiveMap; + qCDebug(modelformat) << " useOcclusionMap =" << mat.useOcclusionMap; + qCDebug(modelformat) << "\n"; + } + + qCDebug(modelformat) << "---------------- Joints ----------------"; + + foreach(FBXJoint joint, fbxgeo.joints) { + qCDebug(modelformat) << "\n"; + qCDebug(modelformat) << " shapeInfo.avgPoint =" << joint.shapeInfo.avgPoint; + qCDebug(modelformat) << " shapeInfo.debugLines =" << joint.shapeInfo.debugLines; + qCDebug(modelformat) << " shapeInfo.dots =" << joint.shapeInfo.dots; + qCDebug(modelformat) << " shapeInfo.points =" << joint.shapeInfo.points; + + qCDebug(modelformat) << " isFree =" << joint.isFree; + qCDebug(modelformat) << " freeLineage" << joint.freeLineage; + qCDebug(modelformat) << " parentIndex" << joint.parentIndex; + qCDebug(modelformat) << " distanceToParent" << joint.distanceToParent; + qCDebug(modelformat) << " translation" << joint.translation; + qCDebug(modelformat) << " preTransform" << joint.preTransform; + qCDebug(modelformat) << " preRotation" << joint.preRotation; + qCDebug(modelformat) << " rotation" << joint.rotation; + qCDebug(modelformat) << " postRotation" << joint.postRotation; + qCDebug(modelformat) << " postTransform" << joint.postTransform; + qCDebug(modelformat) << " transform" << joint.transform; + qCDebug(modelformat) << " rotationMin" << joint.rotationMin; + qCDebug(modelformat) << " rotationMax" << joint.rotationMax; + qCDebug(modelformat) << " inverseDefaultRotation" << joint.inverseDefaultRotation; + qCDebug(modelformat) << " inverseBindRotation" << joint.inverseBindRotation; + qCDebug(modelformat) << " bindTransform" << joint.bindTransform; + qCDebug(modelformat) << " name" << joint.name; + qCDebug(modelformat) << " isSkeletonJoint" << joint.isSkeletonJoint; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.hasGeometricOffset; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricTranslation; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricRotation; + qCDebug(modelformat) << " bindTransformFoundInCluster" << joint.geometricScaling; + qCDebug(modelformat) << "\n"; + } + + qCDebug(modelformat) << "\n"; +} \ No newline at end of file diff --git a/libraries/fbx/src/GLTFReader.h b/libraries/fbx/src/GLTFReader.h new file mode 100644 index 0000000000..3554594768 --- /dev/null +++ b/libraries/fbx/src/GLTFReader.h @@ -0,0 +1,786 @@ +// +// GLTFReader.h +// libraries/fbx/src +// +// Created by Luis Cuenca on 8/30/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_GLTFReader_h +#define hifi_GLTFReader_h + +#include +#include +#include "ModelFormatLogging.h" +#include "FBXReader.h" + + +struct GLTFAsset { + QString generator; + QString version; //required + QString copyright; + QMap defined; + void dump() { + if (defined["generator"]) { + qCDebug(modelformat) << "generator: " << generator; + } + if (defined["version"]) { + qCDebug(modelformat) << "version: " << version; + } + if (defined["copyright"]) { + qCDebug(modelformat) << "copyright: " << copyright; + } + } +}; + +struct GLTFNode { + QString name; + int camera; + int mesh; + QVector children; + QVector translation; + QVector rotation; + QVector scale; + QVector matrix; + QVector transforms; + int skin; + QVector skeletons; + QString jointName; + QMap defined; + void dump() { + if (defined["name"]) { + qCDebug(modelformat) << "name: " << name; + } + if (defined["camera"]) { + qCDebug(modelformat) << "camera: " << camera; + } + if (defined["mesh"]) { + qCDebug(modelformat) << "mesh: " << mesh; + } + if (defined["skin"]) { + qCDebug(modelformat) << "skin: " << skin; + } + if (defined["jointName"]) { + qCDebug(modelformat) << "jointName: " << jointName; + } + if (defined["children"]) { + qCDebug(modelformat) << "children: " << children; + } + if (defined["translation"]) { + qCDebug(modelformat) << "translation: " << translation; + } + if (defined["rotation"]) { + qCDebug(modelformat) << "rotation: " << rotation; + } + if (defined["scale"]) { + qCDebug(modelformat) << "scale: " << scale; + } + if (defined["matrix"]) { + qCDebug(modelformat) << "matrix: " << matrix; + } + if (defined["skeletons"]) { + qCDebug(modelformat) << "skeletons: " << skeletons; + } + } +}; + +// Meshes + +struct GLTFMeshPrimitivesTarget { + int normal; + int position; + int tangent; + QMap defined; + void dump() { + if (defined["normal"]) { + qCDebug(modelformat) << "normal: " << normal; + } + if (defined["position"]) { + qCDebug(modelformat) << "position: " << position; + } + if (defined["tangent"]) { + qCDebug(modelformat) << "tangent: " << tangent; + } + } +}; + +namespace GLTFMeshPrimitivesRenderingMode { + enum Values { + POINTS = 0, + LINES, + LINE_LOOP, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN + }; +} + +struct GLTFMeshPrimitiveAttr { + QMap values; + QMap defined; + void dump() { + QList keys = values.keys(); + qCDebug(modelformat) << "values: "; + foreach(auto k, keys) { + qCDebug(modelformat) << k << ": " << values[k]; + } + } +}; + +struct GLTFMeshPrimitive { + GLTFMeshPrimitiveAttr attributes; + int indices; + int material; + int mode{ GLTFMeshPrimitivesRenderingMode::TRIANGLES }; + QVector targets; + QMap defined; + void dump() { + if (defined["attributes"]) { + qCDebug(modelformat) << "attributes: "; + attributes.dump(); + } + if (defined["indices"]) { + qCDebug(modelformat) << "indices: " << indices; + } + if (defined["material"]) { + qCDebug(modelformat) << "material: " << material; + } + if (defined["mode"]) { + qCDebug(modelformat) << "mode: " << mode; + } + if (defined["targets"]) { + qCDebug(modelformat) << "targets: "; + foreach(auto t, targets) t.dump(); + } + } +}; + +struct GLTFMesh { + QString name; + QVector primitives; + QVector weights; + QMap defined; + void dump() { + if (defined["name"]) { + qCDebug(modelformat) << "name: " << name; + } + if (defined["primitives"]) { + qCDebug(modelformat) << "primitives: "; + foreach(auto prim, primitives) prim.dump(); + } + if (defined["weights"]) { + qCDebug(modelformat) << "weights: " << weights; + } + } +}; + +// BufferViews + +namespace GLTFBufferViewTarget { + enum Values { + ARRAY_BUFFER = 34962, + ELEMENT_ARRAY_BUFFER = 34963 + }; +} + +struct GLTFBufferView { + int buffer; //required + int byteLength; //required + int byteOffset; + int target; + QMap defined; + void dump() { + if (defined["buffer"]) { + qCDebug(modelformat) << "buffer: " << buffer; + } + if (defined["byteLength"]) { + qCDebug(modelformat) << "byteLength: " << byteLength; + } + if (defined["byteOffset"]) { + qCDebug(modelformat) << "byteOffset: " << byteOffset; + } + if (defined["target"]) { + qCDebug(modelformat) << "target: " << target; + } + } +}; + +// Buffers + +struct GLTFBuffer { + int byteLength; //required + QString uri; + QByteArray blob; + QMap defined; + void dump() { + if (defined["byteLength"]) { + qCDebug(modelformat) << "byteLength: " << byteLength; + } + if (defined["uri"]) { + qCDebug(modelformat) << "uri: " << uri; + } + if (defined["blob"]) { + qCDebug(modelformat) << "blob: " << "DEFINED"; + } + } +}; + +// Samplers +namespace GLTFSamplerFilterType { + enum Values { + NEAREST = 9728, + LINEAR = 9729, + NEAREST_MIPMAP_NEAREST = 9984, + LINEAR_MIPMAP_NEAREST = 9985, + NEAREST_MIPMAP_LINEAR = 9986, + LINEAR_MIPMAP_LINEAR = 9987 + }; +} + +namespace GLTFSamplerWrapType { + enum Values { + CLAMP_TO_EDGE = 33071, + MIRRORED_REPEAT = 33648, + REPEAT = 10497 + }; +} + +struct GLTFSampler { + int magFilter; + int minFilter; + int wrapS; + int wrapT; + QMap defined; + void dump() { + if (defined["magFilter"]) { + qCDebug(modelformat) << "magFilter: " << magFilter; + } + if (defined["minFilter"]) { + qCDebug(modelformat) << "minFilter: " << minFilter; + } + if (defined["wrapS"]) { + qCDebug(modelformat) << "wrapS: " << wrapS; + } + if (defined["wrapT"]) { + qCDebug(modelformat) << "wrapT: " << wrapT; + } + } +}; + +// Cameras + +struct GLTFCameraPerspective { + double aspectRatio; + double yfov; //required + double zfar; + double znear; //required + QMap defined; + void dump() { + if (defined["zfar"]) { + qCDebug(modelformat) << "zfar: " << zfar; + } + if (defined["znear"]) { + qCDebug(modelformat) << "znear: " << znear; + } + if (defined["aspectRatio"]) { + qCDebug(modelformat) << "aspectRatio: " << aspectRatio; + } + if (defined["yfov"]) { + qCDebug(modelformat) << "yfov: " << yfov; + } + } +}; + +struct GLTFCameraOrthographic { + double zfar; //required + double znear; //required + double xmag; //required + double ymag; //required + QMap defined; + void dump() { + if (defined["zfar"]) { + qCDebug(modelformat) << "zfar: " << zfar; + } + if (defined["znear"]) { + qCDebug(modelformat) << "znear: " << znear; + } + if (defined["xmag"]) { + qCDebug(modelformat) << "xmag: " << xmag; + } + if (defined["ymag"]) { + qCDebug(modelformat) << "ymag: " << ymag; + } + } +}; + +namespace GLTFCameraTypes { + enum Values { + ORTHOGRAPHIC = 0, + PERSPECTIVE + }; +} + +struct GLTFCamera { + QString name; + GLTFCameraPerspective perspective; //required (or) + GLTFCameraOrthographic orthographic; //required (or) + int type; + QMap defined; + void dump() { + if (defined["name"]) { + qCDebug(modelformat) << "name: " << name; + } + if (defined["type"]) { + qCDebug(modelformat) << "type: " << type; + } + if (defined["perspective"]) { + perspective.dump(); + } + if (defined["orthographic"]) { + orthographic.dump(); + } + } +}; + +// Images + +namespace GLTFImageMimetype { + enum Values { + JPEG = 0, + PNG + }; +}; + +struct GLTFImage { + QString uri; //required (or) + int mimeType; + int bufferView; //required (or) + QMap defined; + void dump() { + if (defined["uri"]) { + qCDebug(modelformat) << "uri: " << uri; + } + if (defined["mimeType"]) { + qCDebug(modelformat) << "mimeType: " << mimeType; + } + if (defined["bufferView"]) { + qCDebug(modelformat) << "bufferView: " << bufferView; + } + } +}; + +// Materials + +struct GLTFpbrMetallicRoughness { + QVector baseColorFactor; + int baseColorTexture; + int metallicRoughnessTexture; + double metallicFactor; + double roughnessFactor; + QMap defined; + void dump() { + if (defined["baseColorFactor"]) { + qCDebug(modelformat) << "baseColorFactor: " << baseColorFactor; + } + if (defined["baseColorTexture"]) { + qCDebug(modelformat) << "baseColorTexture: " << baseColorTexture; + } + if (defined["metallicRoughnessTexture"]) { + qCDebug(modelformat) << "metallicRoughnessTexture: " << metallicRoughnessTexture; + } + if (defined["metallicFactor"]) { + qCDebug(modelformat) << "metallicFactor: " << metallicFactor; + } + if (defined["roughnessFactor"]) { + qCDebug(modelformat) << "roughnessFactor: " << roughnessFactor; + } + if (defined["baseColorFactor"]) { + qCDebug(modelformat) << "baseColorFactor: " << baseColorFactor; + } + } +}; + +namespace GLTFMaterialAlphaMode { + enum Values { + OPAQUE = 0, + MASK, + BLEND + }; +}; + +struct GLTFMaterial { + QString name; + QVector emissiveFactor; + int emissiveTexture; + int normalTexture; + int occlusionTexture; + int alphaMode; + double alphaCutoff; + bool doubleSided; + GLTFpbrMetallicRoughness pbrMetallicRoughness; + QMap defined; + void dump() { + if (defined["name"]) { + qCDebug(modelformat) << "name: " << name; + } + if (defined["emissiveTexture"]) { + qCDebug(modelformat) << "emissiveTexture: " << emissiveTexture; + } + if (defined["normalTexture"]) { + qCDebug(modelformat) << "normalTexture: " << normalTexture; + } + if (defined["occlusionTexture"]) { + qCDebug(modelformat) << "occlusionTexture: " << occlusionTexture; + } + if (defined["emissiveFactor"]) { + qCDebug(modelformat) << "emissiveFactor: " << emissiveFactor; + } + if (defined["pbrMetallicRoughness"]) { + pbrMetallicRoughness.dump(); + } + } +}; + +// Accesors + +namespace GLTFAccessorType { + enum Values { + SCALAR = 0, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 + }; +} +namespace GLTFAccessorComponentType { + enum Values { + BYTE = 5120, + UNSIGNED_BYTE = 5121, + SHORT = 5122, + UNSIGNED_SHORT = 5123, + UNSIGNED_INT = 5125, + FLOAT = 5126 + }; +} +struct GLTFAccessor { + int bufferView; + int byteOffset; + int componentType; //required + int count; //required + int type; //required + bool normalized{ false }; + QVector max; + QVector min; + QMap defined; + void dump() { + if (defined["bufferView"]) { + qCDebug(modelformat) << "bufferView: " << bufferView; + } + if (defined["byteOffset"]) { + qCDebug(modelformat) << "byteOffset: " << byteOffset; + } + if (defined["componentType"]) { + qCDebug(modelformat) << "componentType: " << componentType; + } + if (defined["count"]) { + qCDebug(modelformat) << "count: " << count; + } + if (defined["type"]) { + qCDebug(modelformat) << "type: " << type; + } + if (defined["normalized"]) { + qCDebug(modelformat) << "normalized: " << (normalized ? "TRUE" : "FALSE"); + } + if (defined["max"]) { + qCDebug(modelformat) << "max: "; + foreach(float m, max) { + qCDebug(modelformat) << m; + } + } + if (defined["min"]) { + qCDebug(modelformat) << "min: "; + foreach(float m, min) { + qCDebug(modelformat) << m; + } + } + } +}; + +// Animation + +namespace GLTFChannelTargetPath { + enum Values { + TRANSLATION = 0, + ROTATION, + SCALE + }; +} + +struct GLTFChannelTarget { + int node; + int path; + QMap defined; + void dump() { + if (defined["node"]) { + qCDebug(modelformat) << "node: " << node; + } + if (defined["path"]) { + qCDebug(modelformat) << "path: " << path; + } + } +}; + +struct GLTFChannel { + int sampler; + GLTFChannelTarget target; + QMap defined; + void dump() { + if (defined["sampler"]) { + qCDebug(modelformat) << "sampler: " << sampler; + } + if (defined["target"]) { + target.dump(); + } + } +}; + +namespace GLTFAnimationSamplerInterpolation { + enum Values{ + LINEAR = 0 + }; +} + +struct GLTFAnimationSampler { + int input; + int output; + int interpolation; + QMap defined; + void dump() { + if (defined["input"]) { + qCDebug(modelformat) << "input: " << input; + } + if (defined["output"]) { + qCDebug(modelformat) << "output: " << output; + } + if (defined["interpolation"]) { + qCDebug(modelformat) << "interpolation: " << interpolation; + } + } +}; + +struct GLTFAnimation { + QVector channels; + QVector samplers; + QMap defined; + void dump() { + if (defined["channels"]) { + foreach(auto channel, channels) channel.dump(); + } + if (defined["samplers"]) { + foreach(auto sampler, samplers) sampler.dump(); + } + } +}; + +struct GLTFScene { + QString name; + QVector nodes; + QMap defined; + void dump() { + if (defined["name"]) { + qCDebug(modelformat) << "name: " << name; + } + if (defined["nodes"]) { + qCDebug(modelformat) << "nodes: "; + foreach(int node, nodes) qCDebug(modelformat) << node; + } + } +}; + +struct GLTFSkin { + int inverseBindMatrices; + QVector joints; + int skeleton; + QMap defined; + void dump() { + if (defined["inverseBindMatrices"]) { + qCDebug(modelformat) << "inverseBindMatrices: " << inverseBindMatrices; + } + if (defined["skeleton"]) { + qCDebug(modelformat) << "skeleton: " << skeleton; + } + if (defined["joints"]) { + qCDebug(modelformat) << "joints: "; + foreach(int joint, joints) qCDebug(modelformat) << joint; + } + } +}; + +struct GLTFTexture { + int sampler; + int source; + QMap defined; + void dump() { + if (defined["sampler"]) { + qCDebug(modelformat) << "sampler: " << sampler; + } + if (defined["source"]) { + qCDebug(modelformat) << "source: " << sampler; + } + } +}; + +struct GLTFFile { + GLTFAsset asset; + int scene = 0; + QVector accessors; + QVector animations; + QVector bufferviews; + QVector buffers; + QVector cameras; + QVector images; + QVector materials; + QVector meshes; + QVector nodes; + QVector samplers; + QVector scenes; + QVector skins; + QVector textures; + QMap defined; + void dump() { + if (defined["asset"]) { + asset.dump(); + } + if (defined["scene"]) { + qCDebug(modelformat) << "scene: " << scene; + } + if (defined["accessors"]) { + foreach(auto acc, accessors) acc.dump(); + } + if (defined["animations"]) { + foreach(auto ani, animations) ani.dump(); + } + if (defined["bufferviews"]) { + foreach(auto bv, bufferviews) bv.dump(); + } + if (defined["buffers"]) { + foreach(auto b, buffers) b.dump(); + } + if (defined["cameras"]) { + foreach(auto c, cameras) c.dump(); + } + if (defined["images"]) { + foreach(auto i, images) i.dump(); + } + if (defined["materials"]) { + foreach(auto mat, materials) mat.dump(); + } + if (defined["meshes"]) { + foreach(auto mes, meshes) mes.dump(); + } + if (defined["nodes"]) { + foreach(auto nod, nodes) nod.dump(); + } + if (defined["samplers"]) { + foreach(auto sa, samplers) sa.dump(); + } + if (defined["scenes"]) { + foreach(auto sc, scenes) sc.dump(); + } + if (defined["skins"]) { + foreach(auto sk, nodes) sk.dump(); + } + if (defined["textures"]) { + foreach(auto tex, textures) tex.dump(); + } + } +}; + +class GLTFReader : public QObject { + Q_OBJECT +public: + GLTFReader(); + FBXGeometry* readGLTF(QByteArray& model, const QVariantHash& mapping, + const QUrl& url, bool loadLightmaps = true, float lightmapLevel = 1.0f); +private: + GLTFFile _file; + QUrl _url; + + glm::mat4 getModelTransform(const GLTFNode& node); + + bool buildGeometry(FBXGeometry& geometry, const QUrl& url); + bool parseGLTF(const QByteArray& model); + + bool getStringVal(const QJsonObject& object, const QString& fieldname, + QString& value, QMap& defined); + bool getBoolVal(const QJsonObject& object, const QString& fieldname, + bool& value, QMap& defined); + bool getIntVal(const QJsonObject& object, const QString& fieldname, + int& value, QMap& defined); + bool getDoubleVal(const QJsonObject& object, const QString& fieldname, + double& value, QMap& defined); + bool getObjectVal(const QJsonObject& object, const QString& fieldname, + QJsonObject& value, QMap& defined); + bool getIntArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined); + bool getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, + QVector& values, QMap& defined); + bool getObjectArrayVal(const QJsonObject& object, const QString& fieldname, + QJsonArray& objects, QMap& defined); + + int getMaterialAlphaMode(const QString& type); + int getAccessorType(const QString& type); + int getAnimationSamplerInterpolation(const QString& interpolation); + int getCameraType(const QString& type); + int getImageMimeType(const QString& mime); + int getMeshPrimitiveRenderingMode(const QString& type); + + bool getIndexFromObject(const QJsonObject& object, const QString& field, + int& outidx, QMap& defined); + + bool setAsset(const QJsonObject& object); + bool addAccessor(const QJsonObject& object); + bool addAnimation(const QJsonObject& object); + bool addBufferView(const QJsonObject& object); + bool addBuffer(const QJsonObject& object); + bool addCamera(const QJsonObject& object); + bool addImage(const QJsonObject& object); + bool addMaterial(const QJsonObject& object); + bool addMesh(const QJsonObject& object); + bool addNode(const QJsonObject& object); + bool addSampler(const QJsonObject& object); + bool addScene(const QJsonObject& object); + bool addSkin(const QJsonObject& object); + bool addTexture(const QJsonObject& object); + + bool readBinary(const QString& url, QByteArray& outdata); + + template + bool readArray(const QByteArray& bin, int byteOffset, int byteLength, + QVector& outarray, int accessorType); + + template + bool addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength, + QVector& outarray, int accessorType, int componentType); + + void retriangulate(const QVector& in_indices, const QVector& in_vertices, + const QVector& in_normals, QVector& out_indices, + QVector& out_vertices, QVector& out_normals); + + std::tuple requestData(QUrl& url); + QNetworkReply* request(QUrl& url, bool isTest); + bool doesResourceExist(const QString& url); + + + void setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& material); + FBXTexture getFBXTexture(const GLTFTexture& texture); + void fbxDebugDump(const FBXGeometry& fbxgeo); +}; + +#endif // hifi_GLTFReader_h \ No newline at end of file diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index b62ad7b366..6a14e6d6b7 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -14,6 +14,7 @@ #include #include "FBXReader.h" #include "OBJReader.h" +#include "GLTFReader.h" #include #include @@ -175,9 +176,12 @@ void GeometryReader::run() { QString urlname = _url.path().toLower(); if (!urlname.isEmpty() && !_url.path().isEmpty() && - (_url.path().toLower().endsWith(".fbx") || - _url.path().toLower().endsWith(".obj") || - _url.path().toLower().endsWith(".obj.gz"))) { + + (_url.path().toLower().endsWith(".fbx") || + _url.path().toLower().endsWith(".obj") || + _url.path().toLower().endsWith(".obj.gz") || + _url.path().toLower().endsWith(".gltf"))) { + FBXGeometry::Pointer fbxGeometry; if (_url.path().toLower().endsWith(".fbx")) { @@ -189,12 +193,18 @@ void GeometryReader::run() { fbxGeometry.reset(OBJReader().readOBJ(_data, _mapping, _combineParts, _url)); } else if (_url.path().toLower().endsWith(".obj.gz")) { QByteArray uncompressedData; - if (gunzip(_data, uncompressedData)){ + if (gunzip(_data, uncompressedData)) { fbxGeometry.reset(OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url)); } else { - throw QString("failed to decompress .obj.gz" ); + throw QString("failed to decompress .obj.gz"); } + } else if (_url.path().toLower().endsWith(".gltf")) { + std::shared_ptr glreader = std::make_shared(); + fbxGeometry.reset(glreader->readGLTF(_data, _mapping, _url)); + if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { + throw QString("empty geometry, possibly due to an unsupported GLTF version"); + } } else { throw QString("unsupported format"); }