/**************************************************************************** ** ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "gltfimporter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * glTF 2.0 conformance report * * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 * Samples: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 * * Most of the reference samples are rendered correctly, with the following exceptions: * * 'extensions' and 'extras' are ignored everywhere except in nodes. * * asset * generator, copyright, minVersion: not parsed * accessors * min, max, normalized, sparse: not parsed * animations * the whole object is not parsed * buffers * all parsed * bufferViews * all parsed * cameras * all parsed * images * mimeType, bufferView, name: not parsed * materials * emissiveTexture, emissiveFactor: not parsed * alphaMode, alphaCutoff, doubleSided: not parsed * texCoord, strength: not parsed * meshes * weights: not parsed * nodes * skin, weights: not parsed * samplers * all parsed * scenes * all parsed * textures * all parsed */ #ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature # define qUtf16PrintableImpl(string) \ static_cast(static_cast(string.utf16())) #endif #define KEY_ASSET QLatin1String("asset") #define KEY_VERSION QLatin1String("version") #define KEY_CAMERA QLatin1String("camera") #define KEY_CAMERAS QLatin1String("cameras") #define KEY_SCENES QLatin1String("scenes") #define KEY_NODES QLatin1String("nodes") #define KEY_MESHES QLatin1String("meshes") #define KEY_CHILDREN QLatin1String("children") #define KEY_MESH QLatin1String("mesh") #define KEY_MATRIX QLatin1String("matrix") #define KEY_ROTATION QLatin1String("rotation") #define KEY_SCALE QLatin1String("scale") #define KEY_TRANSLATION QLatin1String("translation") #define KEY_TYPE QLatin1String("type") #define KEY_PERSPECTIVE QLatin1String("perspective") #define KEY_ORTHOGRAPHIC QLatin1String("orthographic") #define KEY_NAME QLatin1String("name") #define KEY_COUNT QLatin1String("count") #define KEY_YFOV QLatin1String("yfov") #define KEY_ZNEAR QLatin1String("znear") #define KEY_ZFAR QLatin1String("zfar") #define KEY_XMAG QLatin1String("xmag") #define KEY_YMAG QLatin1String("ymag") #define KEY_MATERIALS QLatin1String("materials") #define KEY_EXTENSIONS QLatin1String("extensions") #define KEY_COMMON_MAT QLatin1String("KHR_materials_common") #define KEY_TECHNIQUE QLatin1String("technique") #define KEY_VALUES QLatin1String("values") #define KEY_BUFFERS QLatin1String("buffers") #define KEY_SHADERS QLatin1String("shaders") #define KEY_PROGRAMS QLatin1String("programs") #define KEY_PROGRAM QLatin1String("program") #define KEY_TECHNIQUES QLatin1String("techniques") #define KEY_ACCESSORS QLatin1String("accessors") #define KEY_IMAGES QLatin1String("images") #define KEY_TEXTURES QLatin1String("textures") #define KEY_SCENE QLatin1String("scene") #define KEY_BUFFER QLatin1String("buffer") #define KEY_TARGET QLatin1String("target") #define KEY_BYTE_OFFSET QLatin1String("byteOffset") #define KEY_BYTE_LENGTH QLatin1String("byteLength") #define KEY_BYTE_STRIDE QLatin1String("byteStride") #define KEY_PRIMITIVES QLatin1String("primitives") #define KEY_MODE QLatin1String("mode") #define KEY_MATERIAL QLatin1String("material") #define KEY_ATTRIBUTES QLatin1String("attributes") #define KEY_INDICES QLatin1String("indices") #define KEY_URI QLatin1String("uri") #define KEY_FORMAT QLatin1String("format") #define KEY_PASSES QLatin1String("passes") #define KEY_SOURCE QLatin1String("source") #define KEY_SAMPLER QLatin1String("sampler") #define KEY_SAMPLERS QLatin1String("samplers") #define KEY_SEMANTIC QLatin1String("semantic") #define KEY_STATES QLatin1String("states") #define KEY_UNIFORMS QLatin1String("uniforms") #define KEY_PARAMETERS QLatin1String("parameters") #define KEY_WRAP_S QLatin1String("wrapS") #define KEY_MIN_FILTER QLatin1String("minFilter") #define KEY_MAG_FILTER QLatin1String("magFilter") #define KEY_LIGHT QLatin1String("light") #define KEY_LIGHTS QLatin1String("lights") #define KEY_POINT_LIGHT QLatin1String("point") #define KEY_DIRECTIONAL_LIGHT QLatin1String("directional") #define KEY_SPOT_LIGHT QLatin1String("spot") #define KEY_AMBIENT_LIGHT QLatin1String("ambient") #define KEY_COLOR QLatin1String("color") #define KEY_FALLOFF_ANGLE QLatin1String("falloffAngle") #define KEY_DIRECTION QLatin1String("direction") #define KEY_CONST_ATTENUATION QLatin1String("constantAttenuation") #define KEY_LINEAR_ATTENUATION QLatin1String("linearAttenuation") #define KEY_QUAD_ATTENUATION QLatin1String("quadraticAttenuation") #define KEY_INTENSITY QLatin1String("intensity") #define KEY_PBR_METAL_ROUGH QLatin1String("pbrMetallicRoughness") #define KEY_BASE_COLOR QLatin1String("baseColorFactor") #define KEY_BASE_COLOR_TEX QLatin1String("baseColorTexture") #define KEY_METAL_FACTOR QLatin1String("metallicFactor") #define KEY_METAL_ROUGH_TEX QLatin1String("metallicRoughnessTexture") #define KEY_ROUGH_FACTOR QLatin1String("roughnessFactor") #define KEY_NORMAL_TEX QLatin1String("normalTexture") #define KEY_OCCLUSION_TEX QLatin1String("occlusionTexture") #define KEY_INDEX QLatin1String("index") #define KEY_INSTANCE_TECHNIQUE QLatin1String("instanceTechnique") #define KEY_INSTANCE_PROGRAM QLatin1String("instanceProgram") #define KEY_BUFFER_VIEWS QLatin1String("bufferViews") #define KEY_BUFFER_VIEW QLatin1String("bufferView") #define KEY_VERTEX_SHADER QLatin1String("vertexShader") #define KEY_FRAGMENT_SHADER QLatin1String("fragmentShader") #define KEY_TESS_CTRL_SHADER QLatin1String("tessCtrlShader") #define KEY_TESS_EVAL_SHADER QLatin1String("tessEvalShader") #define KEY_GEOMETRY_SHADER QLatin1String("geometryShader") #define KEY_COMPUTE_SHADER QLatin1String("computeShader") #define KEY_INTERNAL_FORMAT QLatin1String("internalFormat") #define KEY_COMPONENT_TYPE QLatin1String("componentType") #define KEY_ASPECT_RATIO QLatin1String("aspect_ratio") #define KEY_VALUE QLatin1String("value") #define KEY_ENABLE QLatin1String("enable") #define KEY_FUNCTIONS QLatin1String("functions") #define KEY_BLEND_EQUATION QLatin1String("blendEquationSeparate") #define KEY_BLEND_FUNCTION QLatin1String("blendFuncSeparate") #define KEY_TECHNIQUE_CORE QLatin1String("techniqueCore") #define KEY_TECHNIQUE_GL2 QLatin1String("techniqueGL2") #define KEY_GABIFILTER QLatin1String("gapifilter") #define KEY_API QLatin1String("api") #define KEY_MAJORVERSION QLatin1String("majorVersion") #define KEY_MINORVERSION QLatin1String("minorVersion") #define KEY_PROFILE QLatin1String("profile") #define KEY_VENDOR QLatin1String("vendor") #define KEY_FILTERKEYS QLatin1String("filterkeys") #define KEY_RENDERPASSES QLatin1String("renderpasses") #define KEY_EFFECT QLatin1String("effect") #define KEY_EFFECTS QLatin1String("effects") #define KEY_PROPERTIES QLatin1String("properties") #define KEY_POSITION QLatin1String("position") #define KEY_UPVECTOR QLatin1String("upVector") #define KEY_VIEW_CENTER QLatin1String("viewCenter") QT_BEGIN_NAMESPACE using namespace Qt3DCore; using namespace Qt3DExtras; namespace { inline QVector3D jsonArrToVec3(const QJsonArray &array) { return QVector3D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble()); } inline QVector4D jsonArrToVec4(const QJsonArray &array) { return QVector4D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble(), array[3].toDouble()); } inline QVariant jsonArrToColorVariant(const QJsonArray &array) { return QVariant(QColor::fromRgbF(array[0].toDouble(), array[1].toDouble(), array[2].toDouble(), array[3].toDouble())); } inline QColor vec4ToQColor(const QVariant &vec4Var) { const QVector4D v = vec4Var.value(); return QColor::fromRgbF(v.x(), v.y(), v.z()); } inline QVariant vec4ToColorVariant(const QVariant &vec4Var) { return QVariant(vec4ToQColor(vec4Var)); } Qt3DRender::QFilterKey *buildFilterKey(const QString &key, const QJsonValue &val) { Qt3DRender::QFilterKey *fk = new Qt3DRender::QFilterKey; fk->setName(key); if (val.isString()) fk->setValue(val.toString()); else fk->setValue(val.toInt()); return fk; } } // namespace namespace Qt3DRender { Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport", QtWarningMsg); class GLTFRawTextureImage : public QAbstractTextureImage { Q_OBJECT public: explicit GLTFRawTextureImage(QNode *parent = nullptr); QTextureImageDataGeneratorPtr dataGenerator() const final; void setImage(const QImage &image); private: QImage m_image; class GLTFRawTextureImageFunctor : public QTextureImageDataGenerator { public: explicit GLTFRawTextureImageFunctor(const QImage &image); QTextureImageDataPtr operator()() final; bool operator ==(const QTextureImageDataGenerator &other) const final; QT3D_FUNCTOR(GLTFRawTextureImageFunctor) private: QImage m_image; }; }; GLTFImporter::GLTFImporter() : QSceneImporter() , m_parseDone(false) , m_majorVersion(1) , m_minorVersion(0) { } GLTFImporter::~GLTFImporter() { } /*! \class Qt3DRender::GLTFImporter \inmodule Qt3DRender \internal \brief Handles importing of gltf files. */ /*! Set the base \a path for importing scenes. */ void GLTFImporter::setBasePath(const QString& path) { m_basePath = path; } /*! Set a \a json document as the file used for importing a scene. Returns true if the operation is successful. */ bool GLTFImporter::setJSON(const QJsonDocument &json) { if (!json.isObject()) { return false; } m_json = json; m_parseDone = false; return true; } /*! * Sets the path based on parameter \a source. The path is * used by the parser to load the scene file. * If the file is valid, parsing is automatically triggered. */ void GLTFImporter::setSource(const QUrl &source) { const QString path = QUrlHelper::urlToLocalFileOrQrc(source); QFileInfo finfo(path); if (Q_UNLIKELY(!finfo.exists())) { qCWarning(GLTFImporterLog, "missing file: %ls", qUtf16PrintableImpl(path)); return; } QFile f(path); f.open(QIODevice::ReadOnly); if (Q_UNLIKELY(!setJSON(qLoadGLTF(f.readAll())))) { qCWarning(GLTFImporterLog, "not a JSON document"); return; } setBasePath(finfo.dir().absolutePath()); } /*! * Sets the \a basePath used by the parser to load the scene file. * If the file derived from \a data is valid, parsing is automatically * triggered. */ void GLTFImporter::setData(const QByteArray& data, const QString &basePath) { if (Q_UNLIKELY(!setJSON(qLoadGLTF(data)))) { qCWarning(GLTFImporterLog, "not a JSON document"); return; } setBasePath(basePath); } /*! * Returns true if the \a extensions are supported by the * GLTF parser. */ bool GLTFImporter::areFileTypesSupported(const QStringList &extensions) const { return GLTFImporter::isGLTFSupported(extensions); } /*! Imports the node specified in \a id from the GLTF file. */ Qt3DCore::QEntity* GLTFImporter::node(const QString &id) { QJsonValue jsonVal; if (m_majorVersion > 1) { const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray(); if (Q_UNLIKELY(id.toInt() >= nodes.count())) { qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return nullptr; } jsonVal = nodes[id.toInt()]; } else { const QJsonObject nodes = m_json.object().value(KEY_NODES).toObject(); jsonVal = nodes.value(id); if (Q_UNLIKELY(jsonVal.isUndefined())) { qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return nullptr; } } const QJsonObject jsonObj = jsonVal.toObject(); QEntity* result = nullptr; // Qt3D has a limitation that a QEntity can only have 1 mesh and 1 material component // So if the node has only 1 mesh, we only create 1 QEntity // Otherwise if there are n meshes, there is 1 QEntity, with n children for each mesh/material combo { QList entities; const QJsonValue meshesValue = jsonObj.value(KEY_MESHES); if (meshesValue.isUndefined()) { const QJsonValue mesh = jsonObj.value(KEY_MESH); if (!mesh.isUndefined()) { const QString meshName = QString::number(mesh.toInt()); const auto geometryRenderers = qAsConst(m_meshDict).equal_range(meshName); for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) { QGeometryRenderer *geometryRenderer = it.value(); QEntity *entity = new QEntity; entity->addComponent(geometryRenderer); QMaterial *mat = material(m_meshMaterialDict[geometryRenderer]); if (mat) entity->addComponent(mat); entities.append(entity); } } } else { const auto meshes = meshesValue.toArray(); for (const QJsonValue &mesh : meshes) { const QString meshName = mesh.toString(); const auto geometryRenderers = qAsConst(m_meshDict).equal_range(meshName); if (Q_UNLIKELY(geometryRenderers.first == geometryRenderers.second)) { qCWarning(GLTFImporterLog, "node %ls references unknown mesh %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(meshName)); continue; } for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) { QGeometryRenderer *geometryRenderer = it.value(); QEntity *entity = new QEntity; entity->addComponent(geometryRenderer); QMaterial *mat = material(m_meshMaterialDict[geometryRenderer]); if (mat) entity->addComponent(mat); entities.append(entity); } } } switch (entities.size()) { case 0: break; case 1: result = qAsConst(entities).first(); break; default: result = new QEntity; for (QEntity *entity : qAsConst(entities)) entity->setParent(result); } } const auto cameraVal = jsonObj.value(KEY_CAMERA); const auto matrix = jsonObj.value(KEY_MATRIX); const auto rotation = jsonObj.value(KEY_ROTATION); const auto translation = jsonObj.value(KEY_TRANSLATION); const auto scale = jsonObj.value(KEY_SCALE); Qt3DCore::QTransform *trans = nullptr; QCameraLens *cameraLens = nullptr; QCamera *cameraEntity = nullptr; // If the node contains no meshes, results will still be null here. // If the node has camera and transform, promote it to QCamera, as that makes it more // convenient to adjust the imported camera in the application. if (result == nullptr) { if (!cameraVal.isUndefined() && (!matrix.isUndefined() || !rotation.isUndefined() || !translation.isUndefined() || !scale.isUndefined())) { cameraEntity = new QCamera; trans = cameraEntity->transform(); cameraLens = cameraEntity->lens(); result = cameraEntity; } else { result = new QEntity; } } // recursively retrieve children const auto children = jsonObj.value(KEY_CHILDREN).toArray(); for (const QJsonValue &c : children) { QEntity* child = node((m_majorVersion > 1) ? QString::number(c.toInt()) : c.toString()); if (!child) continue; child->setParent(result); } renameFromJson(jsonObj, result); // Node Transforms if (!matrix.isUndefined()) { QMatrix4x4 m(Qt::Uninitialized); QJsonArray matrixValues = matrix.toArray(); for (int i = 0; i < 16; ++i) { double v = matrixValues.at(i).toDouble(); m(i % 4, i >> 2) = v; } if (!trans) trans = new Qt3DCore::QTransform; trans->setMatrix(m); } // Rotation quaternion if (!rotation.isUndefined()) { if (!trans) trans = new Qt3DCore::QTransform; QQuaternion quaternion(jsonArrToVec4(rotation.toArray())); trans->setRotation(quaternion); } // Translation if (!translation.isUndefined()) { if (!trans) trans = new Qt3DCore::QTransform; trans->setTranslation(jsonArrToVec3(translation.toArray())); } // Scale if (!scale.isUndefined()) { if (!trans) trans = new Qt3DCore::QTransform; trans->setScale3D(jsonArrToVec3(scale.toArray())); } // Add the Transform component if (trans != nullptr) result->addComponent(trans); if (!cameraVal.isUndefined()) { const bool newLens = cameraLens == nullptr; if (newLens) cameraLens = new QCameraLens; const QString cameraID = (m_majorVersion > 1) ? QString::number(cameraVal.toInt()) : cameraVal.toString(); if (!fillCamera(*cameraLens, cameraEntity, cameraID)) { qCWarning(GLTFImporterLog, "failed to build camera: %ls on node %ls", qUtf16PrintableImpl(cameraID), qUtf16PrintableImpl(id)); } else if (newLens) { result->addComponent(cameraLens); } } const auto extensionsVal = jsonObj.value(KEY_EXTENSIONS); if (!extensionsVal.isUndefined()) { const auto commonMat = extensionsVal.toObject().value(KEY_COMMON_MAT); if (!commonMat.isUndefined()) { const QJsonValue lightVal = commonMat.toObject().value(KEY_LIGHT); const QString lightId = (m_majorVersion > 1) ? QString::number(lightVal.toInt()) : lightVal.toString(); QAbstractLight *lightComp = m_lights.value(lightId); if (Q_UNLIKELY(!lightComp)) { qCWarning(GLTFImporterLog, "failed to find light: %ls for node %ls", qUtf16PrintableImpl(lightId), qUtf16PrintableImpl(id)); } else { result->addComponent(lightComp); } } } return result; } /*! Imports the scene specified in parameter \a id. */ Qt3DCore::QEntity* GLTFImporter::scene(const QString &id) { parse(); QEntity* sceneEntity = nullptr; if (m_majorVersion > 1) { const QJsonArray scenes = m_json.object().value(KEY_SCENES).toArray(); const auto sceneVal = scenes.first(); if (Q_UNLIKELY(sceneVal.isUndefined())) { if (Q_UNLIKELY(!id.isNull())) qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return defaultScene(); } const QJsonObject sceneObj = sceneVal.toObject(); sceneEntity = new QEntity; const auto nodes = sceneObj.value(KEY_NODES).toArray(); for (const QJsonValue &n : nodes) { QEntity* child = node(QString::number(n.toInt())); if (!child) continue; child->setParent(sceneEntity); } } else { const QJsonObject scenes = m_json.object().value(KEY_SCENES).toObject(); const auto sceneVal = scenes.value(id); if (Q_UNLIKELY(sceneVal.isUndefined())) { if (Q_UNLIKELY(!id.isNull())) qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return defaultScene(); } const QJsonObject sceneObj = sceneVal.toObject(); sceneEntity = new QEntity; const auto nodes = sceneObj.value(KEY_NODES).toArray(); for (const QJsonValue &nnv : nodes) { QString nodeName = nnv.toString(); QEntity* child = node(nodeName); if (!child) continue; child->setParent(sceneEntity); } } cleanup(); return sceneEntity; } GLTFImporter::BufferData::BufferData() : length(0) , data(nullptr) { } GLTFImporter::BufferData::BufferData(const QJsonObject &json) : length(json.value(KEY_BYTE_LENGTH).toInt()), path(json.value(KEY_URI).toString()), data(nullptr) { } GLTFImporter::ParameterData::ParameterData() : type(0) { } GLTFImporter::ParameterData::ParameterData(const QJsonObject &json) : semantic(json.value(KEY_SEMANTIC).toString()), type(json.value(KEY_TYPE).toInt()) { } GLTFImporter::AccessorData::AccessorData() : type(QAttribute::Float) , dataSize(0) , count(0) , offset(0) , stride(0) { } GLTFImporter::AccessorData::AccessorData(const QJsonObject &json, int major, int minor) : type(accessorTypeFromJSON(json.value(KEY_COMPONENT_TYPE).toInt())), dataSize(accessorDataSizeFromJson(json.value(KEY_TYPE).toString())), count(json.value(KEY_COUNT).toInt()), offset(0), stride(0) { Q_UNUSED(minor); if (major > 1) { bufferViewName = QString::number(json.value(KEY_BUFFER_VIEW).toInt()); } else { bufferViewName = json.value(KEY_BUFFER_VIEW).toString(); } const auto byteOffset = json.value(KEY_BYTE_OFFSET); if (!byteOffset.isUndefined()) offset = byteOffset.toInt(); const auto byteStride = json.value(KEY_BYTE_STRIDE); if (!byteStride.isUndefined()) stride = byteStride.toInt(); } bool GLTFImporter::isGLTFSupported(const QStringList &extensions) { for (auto suffix: qAsConst(extensions)) { suffix = suffix.toLower(); if (suffix == QLatin1String("json") || suffix == QLatin1String("gltf") || suffix == QLatin1String("qgltf")) return true; } return false; } bool GLTFImporter::isEmbeddedResource(const QString &url) { return url.startsWith("data:"); } void GLTFImporter::renameFromJson(const QJsonObject &json, QObject * const object) { const auto name = json.value(KEY_NAME); if (!name.isUndefined()) object->setObjectName(name.toString()); } bool GLTFImporter::hasStandardUniformNameFromSemantic(const QString &semantic) { //Standard Uniforms if (semantic.isEmpty()) return false; switch (semantic.at(0).toLatin1()) { case 'L': // return semantic == QLatin1String("LOCAL"); return false; case 'M': return semantic == QLatin1String("MODEL") || semantic == QLatin1String("MODELVIEW") || semantic == QLatin1String("MODELVIEWPROJECTION") || semantic == QLatin1String("MODELINVERSE") || semantic == QLatin1String("MODELVIEWPROJECTIONINVERSE") || semantic == QLatin1String("MODELINVERSETRANSPOSE") || semantic == QLatin1String("MODELVIEWINVERSETRANSPOSE"); case 'V': return semantic == QLatin1String("VIEW") || semantic == QLatin1String("VIEWINVERSE") || semantic == QLatin1String("VIEWPORT"); case 'P': return semantic == QLatin1String("PROJECTION") || semantic == QLatin1String("PROJECTIONINVERSE"); } return false; } QString GLTFImporter::standardAttributeNameFromSemantic(const QString &semantic) { //Standard Attributes if (semantic.startsWith(QLatin1String("POSITION"))) return QAttribute::defaultPositionAttributeName(); if (semantic.startsWith(QLatin1String("NORMAL"))) return QAttribute::defaultNormalAttributeName(); if (semantic.startsWith(QLatin1String("TEXCOORD"))) return QAttribute::defaultTextureCoordinateAttributeName(); if (semantic.startsWith(QLatin1String("COLOR"))) return QAttribute::defaultColorAttributeName(); if (semantic.startsWith(QLatin1String("TANGENT"))) return QAttribute::defaultTangentAttributeName(); // if (semantic.startsWith(QLatin1String("JOINT"))); // if (semantic.startsWith(QLatin1String("JOINTMATRIX"))); // if (semantic.startsWith(QLatin1String("WEIGHT"))); return QString(); } QParameter *GLTFImporter::parameterFromTechnique(QTechnique *technique, const QString ¶meterName) { const QList parameters = m_techniqueParameters.value(technique); for (QParameter *parameter : parameters) { if (parameter->name() == parameterName) return parameter; } return nullptr; } Qt3DCore::QEntity* GLTFImporter::defaultScene() { if (Q_UNLIKELY(m_defaultScene.isEmpty())) { qCWarning(GLTFImporterLog, "no default scene"); return nullptr; } return scene(m_defaultScene); } QMaterial *GLTFImporter::materialWithCustomShader(const QString &id, const QJsonObject &jsonObj) { QString effectName = jsonObj.value(KEY_EFFECT).toString(); if (effectName.isEmpty()) { // GLTF custom shader material (with qgltf tool specific customizations) // Default ES2 Technique QString techniqueName = jsonObj.value(KEY_TECHNIQUE).toString(); const auto it = qAsConst(m_techniques).find(techniqueName); if (Q_UNLIKELY(it == m_techniques.cend())) { qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return nullptr; } QTechnique *technique = *it; technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGLES); technique->graphicsApiFilter()->setMajorVersion(2); technique->graphicsApiFilter()->setMinorVersion(0); technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); //Optional Core technique QTechnique *coreTechnique = nullptr; QTechnique *gl2Technique = nullptr; QString coreTechniqueName = jsonObj.value(KEY_TECHNIQUE_CORE).toString(); if (!coreTechniqueName.isNull()) { const auto it = qAsConst(m_techniques).find(coreTechniqueName); if (Q_UNLIKELY(it == m_techniques.cend())) { qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", qUtf16PrintableImpl(coreTechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); } else { coreTechnique = it.value(); coreTechnique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); coreTechnique->graphicsApiFilter()->setMajorVersion(3); coreTechnique->graphicsApiFilter()->setMinorVersion(1); coreTechnique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile); } } //Optional GL2 technique QString gl2TechniqueName = jsonObj.value(KEY_TECHNIQUE_GL2).toString(); if (!gl2TechniqueName.isNull()) { const auto it = qAsConst(m_techniques).find(gl2TechniqueName); if (Q_UNLIKELY(it == m_techniques.cend())) { qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls", qUtf16PrintableImpl(gl2TechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); } else { gl2Technique = it.value(); gl2Technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL); gl2Technique->graphicsApiFilter()->setMajorVersion(2); gl2Technique->graphicsApiFilter()->setMinorVersion(0); gl2Technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile); } } // glTF doesn't deal in effects, but we need a trivial one to wrap // up our techniques // However we need to create a unique effect for each material instead // of caching because QMaterial does not keep up with effects // its not the parent of. QEffect *effect = new QEffect; effect->setObjectName(techniqueName); effect->addTechnique(technique); if (coreTechnique != nullptr) effect->addTechnique(coreTechnique); if (gl2Technique != nullptr) effect->addTechnique(gl2Technique); QMaterial *mat = new QMaterial; mat->setEffect(effect); renameFromJson(jsonObj, mat); const QJsonObject values = jsonObj.value(KEY_VALUES).toObject(); for (auto it = values.begin(), end = values.end(); it != end; ++it) { const QString vName = it.key(); QParameter *param = parameterFromTechnique(technique, vName); if (param == nullptr && coreTechnique != nullptr) param = parameterFromTechnique(coreTechnique, vName); if (param == nullptr && gl2Technique != nullptr) param = parameterFromTechnique(gl2Technique, vName); if (Q_UNLIKELY(!param)) { qCWarning(GLTFImporterLog, "unknown parameter: %ls in technique %ls processing material %ls", qUtf16PrintableImpl(vName), qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id)); continue; } ParameterData paramData = m_parameterDataDict.value(param); QVariant var = parameterValueFromJSON(paramData.type, it.value()); mat->addParameter(new QParameter(param->name(), var)); } // of material technique-instance values iteration return mat; } else { // Qt3D exported QGLTF custom material QMaterial *mat = new QMaterial; renameFromJson(jsonObj, mat); QEffect *effect = m_effects.value(effectName); if (effect) { mat->setEffect(effect); } else { qCWarning(GLTFImporterLog, "Effect %ls missing for material %ls", qUtf16PrintableImpl(effectName), qUtf16PrintableImpl(mat->objectName())); } const QJsonObject params = jsonObj.value(KEY_PARAMETERS).toObject(); for (auto it = params.begin(), end = params.end(); it != end; ++it) mat->addParameter(buildParameter(it.key(), it.value().toObject())); return mat; } } QMaterial *GLTFImporter::commonMaterial(const QJsonObject &jsonObj) { const auto jsonExt = jsonObj.value(KEY_EXTENSIONS).toObject().value(KEY_COMMON_MAT).toObject(); if (m_majorVersion == 1 && jsonExt.isEmpty()) return nullptr; QVariantHash params; bool hasDiffuseMap = false; bool hasSpecularMap = false; bool hasNormalMap = false; bool hasAlpha = false; if (m_majorVersion > 1) { QMaterial *mat = pbrMaterial(jsonObj); if (mat) return mat; } const QJsonObject values = jsonExt.value(KEY_VALUES).toObject(); for (auto it = values.begin(), end = values.end(); it != end; ++it) { const QString vName = it.key(); const QJsonValue val = it.value(); QVariant var; QString propertyName = vName; if (vName == QLatin1String("ambient") && val.isArray()) { var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val)); } else if (vName == QLatin1String("diffuse")) { if (val.isString()) { var = parameterValueFromJSON(GL_SAMPLER_2D, val); hasDiffuseMap = true; } else if (val.isArray()) { var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val)); } } else if (vName == QLatin1String("specular")) { if (val.isString()) { var = parameterValueFromJSON(GL_SAMPLER_2D, val); hasSpecularMap = true; } else if (val.isArray()) { var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val)); } } else if (vName == QLatin1String("cool")) { // Custom Qt3D extension for gooch var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val)); } else if (vName == QLatin1String("warm")) { // Custom Qt3D extension for gooch var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val)); } else if (vName == QLatin1String("shininess") && val.isDouble()) { var = parameterValueFromJSON(GL_FLOAT, val); } else if (vName == QLatin1String("normalmap") && val.isString()) { var = parameterValueFromJSON(GL_SAMPLER_2D, val); propertyName = QStringLiteral("normal"); hasNormalMap = true; } else if (vName == QLatin1String("transparency")) { var = parameterValueFromJSON(GL_FLOAT, val); propertyName = QStringLiteral("alpha"); hasAlpha = true; } else if (vName == QLatin1String("transparent")) { hasAlpha = parameterValueFromJSON(GL_BOOL, val).toBool(); } else if (vName == QLatin1String("textureScale")) { var = parameterValueFromJSON(GL_FLOAT, val); propertyName = QStringLiteral("textureScale"); } else if (vName == QLatin1String("alpha")) { // Custom Qt3D extension for gooch var = parameterValueFromJSON(GL_FLOAT, val); } else if (vName == QLatin1String("beta")) { // Custom Qt3D extension for gooch var = parameterValueFromJSON(GL_FLOAT, val); } if (var.isValid()) params[propertyName] = var; } const QJsonObject funcValues = jsonExt.value(KEY_FUNCTIONS).toObject(); if (!funcValues.isEmpty()) { const QJsonArray fArray = funcValues[KEY_BLEND_FUNCTION].toArray(); const QJsonArray eArray = funcValues[KEY_BLEND_EQUATION].toArray(); if (fArray.size() == 4) { params[QStringLiteral("sourceRgbArg")] = fArray[0].toInt(); params[QStringLiteral("sourceAlphaArg")] = fArray[1].toInt(); params[QStringLiteral("destinationRgbArg")] = fArray[2].toInt(); params[QStringLiteral("destinationAlphaArg")] = fArray[3].toInt(); } // We get separate values but our QPhongAlphaMaterial only supports single argument, // so we just use the first one. if (eArray.size() == 2) params[QStringLiteral("blendFunctionArg")] = eArray[0].toInt(); } QMaterial *mat = nullptr; const QString technique = jsonExt.value(KEY_TECHNIQUE).toString(); if (technique == QStringLiteral("PHONG")) { if (hasNormalMap) { if (hasSpecularMap) { mat = new QNormalDiffuseSpecularMapMaterial; } else { if (Q_UNLIKELY(!hasDiffuseMap)) { qCWarning(GLTFImporterLog, "Common material with normal and specular maps needs a diffuse map as well"); } else { if (hasAlpha) mat = new QNormalDiffuseMapAlphaMaterial; else mat = new QNormalDiffuseMapMaterial; } } } else { if (hasSpecularMap) { if (Q_UNLIKELY(!hasDiffuseMap)) qCWarning(GLTFImporterLog, "Common material with specular map needs a diffuse map as well"); else mat = new QDiffuseSpecularMapMaterial; } else if (hasDiffuseMap) { mat = new QDiffuseMapMaterial; } else { if (hasAlpha) mat = new QPhongAlphaMaterial; else mat = new QPhongMaterial; } } } else if (technique == QStringLiteral("GOOCH")) { // Qt3D specific extension mat = new QGoochMaterial; } else if (technique == QStringLiteral("PERVERTEX")) { // Qt3D specific extension mat = new QPerVertexColorMaterial; } if (Q_UNLIKELY(!mat)) { qCWarning(GLTFImporterLog, "Could not find a suitable built-in material for KHR_materials_common"); } else { for (QVariantHash::const_iterator it = params.constBegin(), itEnd = params.constEnd(); it != itEnd; ++it) mat->setProperty(it.key().toUtf8(), it.value()); } renameFromJson(jsonObj, mat); return mat; } QMaterial *GLTFImporter::pbrMaterial(const QJsonObject &jsonObj) { // check for pbrMetallicRoughness material QMetalRoughMaterial *mrMaterial = nullptr; QJsonValue jsonValue = jsonObj.value(KEY_PBR_METAL_ROUGH); if (!jsonValue.isUndefined()) { const QJsonObject pbrObj = jsonValue.toObject(); mrMaterial = new QMetalRoughMaterial; jsonValue = pbrObj.value(KEY_BASE_COLOR); if (!jsonValue.isUndefined()) mrMaterial->setBaseColor(jsonArrToColorVariant(jsonValue.toArray())); jsonValue = pbrObj.value(KEY_BASE_COLOR_TEX); if (!jsonValue.isUndefined()) { const QJsonObject texObj = jsonValue.toObject(); const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt()); const auto it = m_textures.find(textureId); if (Q_UNLIKELY(it == m_textures.end())) { qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId)); } else { mrMaterial->setBaseColor(QVariant::fromValue(it.value())); } } jsonValue = pbrObj.value(KEY_METAL_FACTOR); if (!jsonValue.isUndefined()) mrMaterial->setMetalness(jsonValue.toVariant()); jsonValue = pbrObj.value(KEY_METAL_ROUGH_TEX); if (!jsonValue.isUndefined()) { const QJsonObject texObj = jsonValue.toObject(); const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt()); const auto it = m_textures.find(textureId); if (Q_UNLIKELY(it == m_textures.end())) { qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId)); } else { // find the texture again const QJsonArray texArray = m_json.object().value(KEY_TEXTURES).toArray(); const QJsonObject tObj = texArray.at(texObj.value(KEY_INDEX).toInt()).toObject(); const QString sourceId = QString::number(tObj.value(KEY_SOURCE).toInt()); QImage image; if (m_imagePaths.contains(sourceId)) { image.load(m_imagePaths.value(sourceId)); } else if (m_imageData.contains(sourceId)) { image = m_imageData.value(sourceId); } else { return mrMaterial; } // at this point, in image there is data for metalness (on B) and // roughness (on G) bytes. 2 new textures are created // to make Qt3D happy, since it samples only on R. QTexture2D* metalTex = new QTexture2D; QTexture2D* roughTex = new QTexture2D; GLTFRawTextureImage* metalImgTex = new GLTFRawTextureImage(); GLTFRawTextureImage* roughImgTex = new GLTFRawTextureImage(); QImage metalness(image.size(), image.format()); QImage roughness(image.size(), image.format()); const uchar *imgData = image.constBits(); const int pixelBytes = image.depth() / 8; Q_ASSERT_X(pixelBytes < 3, "GLTFImporter::pbrMaterial", "Unsupported texture format"); for (int y = 0; y < image.height(); y++) { for (int x = 0; x < image.width(); x++) { metalness.setPixel(x, y, qRgb(imgData[0], imgData[0], imgData[0])); roughness.setPixel(x, y, qRgb(imgData[1], imgData[1], imgData[1])); imgData += pixelBytes; } } metalImgTex->setImage(metalness); metalTex->addTextureImage(metalImgTex); roughImgTex->setImage(roughness); roughTex->addTextureImage(roughImgTex); setTextureSamplerInfo("", tObj, metalTex); setTextureSamplerInfo("", tObj, roughTex); mrMaterial->setMetalness(QVariant::fromValue(metalTex)); mrMaterial->setRoughness(QVariant::fromValue(roughTex)); } } jsonValue = pbrObj.value(KEY_ROUGH_FACTOR); if (!jsonValue.isUndefined()) mrMaterial->setRoughness(jsonValue.toVariant()); } jsonValue = jsonObj.value(KEY_NORMAL_TEX); if (!jsonValue.isUndefined()) { const QJsonObject texObj = jsonValue.toObject(); const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt()); const auto it = m_textures.find(textureId); if (Q_UNLIKELY(it == m_textures.end())) { qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId)); } else { if (mrMaterial) mrMaterial->setNormal(QVariant::fromValue(it.value())); } } jsonValue = jsonObj.value(KEY_OCCLUSION_TEX); if (!jsonValue.isUndefined()) { const QJsonObject texObj = jsonValue.toObject(); const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt()); const auto it = m_textures.find(textureId); if (Q_UNLIKELY(it == m_textures.end())) { qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId)); } else { if (mrMaterial) mrMaterial->setAmbientOcclusion(QVariant::fromValue(it.value())); } } return mrMaterial; } QMaterial* GLTFImporter::material(const QString &id) { const auto it = qAsConst(m_materialCache).find(id); if (it != m_materialCache.cend()) return it.value(); QJsonValue jsonVal; if (m_majorVersion > 1) { const QJsonArray mats = m_json.object().value(KEY_MATERIALS).toArray(); jsonVal = mats.at(id.toInt()); } else { const QJsonObject mats = m_json.object().value(KEY_MATERIALS).toObject(); jsonVal = mats.value(id); } if (Q_UNLIKELY(jsonVal.isUndefined())) { qCWarning(GLTFImporterLog, "unknown material %ls in GLTF file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return nullptr; } const QJsonObject jsonObj = jsonVal.toObject(); QMaterial *mat = nullptr; // Prefer common materials over custom shaders. mat = commonMaterial(jsonObj); if (!mat) mat = materialWithCustomShader(id, jsonObj); m_materialCache[id] = mat; return mat; } bool GLTFImporter::fillCamera(QCameraLens &lens, QCamera *cameraEntity, const QString &id) const { QJsonObject jsonObj; if (m_majorVersion > 1) { const QJsonArray camArray = m_json.object().value(KEY_CAMERAS).toArray(); if (id.toInt() >= camArray.count()) { qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return false; } jsonObj = camArray[id.toInt()].toObject(); } else { const auto jsonVal = m_json.object().value(KEY_CAMERAS).toObject().value(id); if (Q_UNLIKELY(jsonVal.isUndefined())) { qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath)); return false; } jsonObj = jsonVal.toObject(); } QString camTy = jsonObj.value(KEY_TYPE).toString(); if (camTy == QLatin1String("perspective")) { const auto pVal = jsonObj.value(KEY_PERSPECTIVE); if (Q_UNLIKELY(pVal.isUndefined())) { qCWarning(GLTFImporterLog, "camera: %ls missing 'perspective' object", qUtf16PrintableImpl(id)); return false; } const QJsonObject pObj = pVal.toObject(); double aspectRatio = pObj.value(KEY_ASPECT_RATIO).toDouble(); double yfov = pObj.value(KEY_YFOV).toDouble(); double frustumNear = pObj.value(KEY_ZNEAR).toDouble(); double frustumFar = pObj.value(KEY_ZFAR).toDouble(); lens.setPerspectiveProjection(qRadiansToDegrees(yfov), aspectRatio, frustumNear, frustumFar); } else if (camTy == QLatin1String("orthographic")) { const auto pVal = jsonObj.value(KEY_ORTHOGRAPHIC); if (Q_UNLIKELY(pVal.isUndefined())) { qCWarning(GLTFImporterLog, "camera: %ls missing 'orthographic' object", qUtf16PrintableImpl(id)); return false; } const QJsonObject pObj = pVal.toObject(); double xmag = pObj.value(KEY_XMAG).toDouble() / 2.0f; double ymag = pObj.value(KEY_YMAG).toDouble() / 2.0f; double frustumNear = pObj.value(KEY_ZNEAR).toDouble(); double frustumFar = pObj.value(KEY_ZFAR).toDouble(); lens.setOrthographicProjection(-xmag, xmag, -ymag, ymag, frustumNear, frustumFar); } else { qCWarning(GLTFImporterLog, "camera: %ls has unsupported type: %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(camTy)); return false; } if (cameraEntity) { if (jsonObj.contains(KEY_POSITION)) cameraEntity->setPosition(jsonArrToVec3(jsonObj.value(KEY_POSITION).toArray())); if (jsonObj.contains(KEY_UPVECTOR)) cameraEntity->setUpVector(jsonArrToVec3(jsonObj.value(KEY_UPVECTOR).toArray())); if (jsonObj.contains(KEY_VIEW_CENTER)) cameraEntity->setViewCenter(jsonArrToVec3(jsonObj.value(KEY_VIEW_CENTER).toArray())); } renameFromJson(jsonObj, &lens); return true; } void GLTFImporter::parse() { if (m_parseDone) return; const QJsonValue asset = m_json.object().value(KEY_ASSET); if (!asset.isUndefined()) processJSONAsset(asset.toObject()); if (m_majorVersion > 1) { parseV2(); } else { parseV1(); } m_parseDone = true; } void GLTFImporter::parseV1() { const QJsonObject buffers = m_json.object().value(KEY_BUFFERS).toObject(); for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it) processJSONBuffer(it.key(), it.value().toObject()); const QJsonObject views = m_json.object().value(KEY_BUFFER_VIEWS).toObject(); loadBufferData(); for (auto it = views.begin(), end = views.end(); it != end; ++it) processJSONBufferView(it.key(), it.value().toObject()); unloadBufferData(); const QJsonObject shaders = m_json.object().value(KEY_SHADERS).toObject(); for (auto it = shaders.begin(), end = shaders.end(); it != end; ++it) processJSONShader(it.key(), it.value().toObject()); const QJsonObject programs = m_json.object().value(KEY_PROGRAMS).toObject(); for (auto it = programs.begin(), end = programs.end(); it != end; ++it) processJSONProgram(it.key(), it.value().toObject()); const QJsonObject attrs = m_json.object().value(KEY_ACCESSORS).toObject(); for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) processJSONAccessor(it.key(), it.value().toObject()); const QJsonObject meshes = m_json.object().value(KEY_MESHES).toObject(); for (auto it = meshes.begin(), end = meshes.end(); it != end; ++it) processJSONMesh(it.key(), it.value().toObject()); const QJsonObject images = m_json.object().value(KEY_IMAGES).toObject(); for (auto it = images.begin(), end = images.end(); it != end; ++it) processJSONImage(it.key(), it.value().toObject()); const QJsonObject textures = m_json.object().value(KEY_TEXTURES).toObject(); for (auto it = textures.begin(), end = textures.end(); it != end; ++it) processJSONTexture(it.key(), it.value().toObject()); const QJsonObject extensions = m_json.object().value(KEY_EXTENSIONS).toObject(); for (auto it = extensions.begin(), end = extensions.end(); it != end; ++it) processJSONExtensions(it.key(), it.value().toObject()); const QJsonObject passes = m_json.object().value(KEY_RENDERPASSES).toObject(); for (auto it = passes.begin(), end = passes.end(); it != end; ++it) processJSONRenderPass(it.key(), it.value().toObject()); const QJsonObject techniques = m_json.object().value(KEY_TECHNIQUES).toObject(); for (auto it = techniques.begin(), end = techniques.end(); it != end; ++it) processJSONTechnique(it.key(), it.value().toObject()); const QJsonObject effects = m_json.object().value(KEY_EFFECTS).toObject(); for (auto it = effects.begin(), end = effects.end(); it != end; ++it) processJSONEffect(it.key(), it.value().toObject()); m_defaultScene = m_json.object().value(KEY_SCENE).toString(); } void GLTFImporter::parseV2() { int i; const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray(); for (i = 0; i < buffers.count(); i++) processJSONBuffer(QString::number(i), buffers[i].toObject()); const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray(); loadBufferData(); for (i = 0; i < views.count(); i++) processJSONBufferView(QString::number(i), views[i].toObject()); unloadBufferData(); const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray(); for (i = 0; i < accessors.count(); i++) processJSONAccessor(QString::number(i), accessors[i].toObject()); const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray(); for (i = 0; i < meshes.count(); i++) processJSONMesh(QString::number(i), meshes[i].toObject()); const QJsonArray images = m_json.object().value(KEY_IMAGES).toArray(); for (i = 0; i < images.count(); i++) processJSONImage(QString::number(i), images[i].toObject()); const QJsonArray textures = m_json.object().value(KEY_TEXTURES).toArray(); for (i = 0; i < textures.count(); i++) processJSONTexture(QString::number(i), textures[i].toObject()); m_defaultScene = QString::number(m_json.object().value(KEY_SCENE).toInt()); } namespace { template void delete_if_without_parent(const C &c) { for (const auto *e : c) { if (!e->parent()) delete e; } } } // unnamed namespace void GLTFImporter::cleanup() { m_meshDict.clear(); m_meshMaterialDict.clear(); m_accessorDict.clear(); delete_if_without_parent(m_materialCache); m_materialCache.clear(); m_bufferDatas.clear(); m_buffers.clear(); m_shaderPaths.clear(); delete_if_without_parent(m_programs); m_programs.clear(); for (const auto ¶ms : qAsConst(m_techniqueParameters)) delete_if_without_parent(params); m_techniqueParameters.clear(); delete_if_without_parent(m_techniques); m_techniques.clear(); delete_if_without_parent(m_textures); m_textures.clear(); m_imagePaths.clear(); m_imageData.clear(); m_defaultScene.clear(); m_parameterDataDict.clear(); delete_if_without_parent(m_renderPasses); m_renderPasses.clear(); delete_if_without_parent(m_effects); m_effects.clear(); } void GLTFImporter::processJSONAsset(const QJsonObject &json) { const QString version = json.value(KEY_VERSION).toString(); if (!version.isEmpty()) { const QStringList verTokens = version.split('.'); if (verTokens.length() >= 2) { m_majorVersion = verTokens[0].toInt(); m_minorVersion = verTokens[1].toInt(); } } } void GLTFImporter::processJSONBuffer(const QString &id, const QJsonObject& json) { // simply cache buffers for lookup by buffer-views m_bufferDatas[id] = BufferData(json); } void GLTFImporter::processJSONBufferView(const QString &id, const QJsonObject& json) { QString bufName; if (m_majorVersion > 1) { bufName = QString::number(json.value(KEY_BUFFER).toInt()); } else { bufName = json.value(KEY_BUFFER).toString(); } const auto it = qAsConst(m_bufferDatas).find(bufName); if (Q_UNLIKELY(it == m_bufferDatas.cend())) { qCWarning(GLTFImporterLog, "unknown buffer: %ls processing view: %ls", qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id)); return; } const auto &bufferData = *it; quint64 offset = 0; const auto byteOffset = json.value(KEY_BYTE_OFFSET); if (!byteOffset.isUndefined()) { offset = byteOffset.toInt(); qCDebug(GLTFImporterLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset); } quint64 len = json.value(KEY_BYTE_LENGTH).toInt(); QByteArray bytes = bufferData.data->mid(offset, len); if (Q_UNLIKELY(bytes.count() != int(len))) { qCWarning(GLTFImporterLog, "failed to read sufficient bytes from: %ls for view %ls", qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id)); } Qt3DCore::QBuffer *b = new Qt3DCore::QBuffer(); b->setData(bytes); m_buffers[id] = b; } void GLTFImporter::processJSONShader(const QString &id, const QJsonObject &jsonObject) { // shaders are trivial for the moment, defer the real work // to the program section QString path = jsonObject.value(KEY_URI).toString(); if (!isEmbeddedResource(path)) { QFileInfo info(m_basePath, path); if (Q_UNLIKELY(!info.exists())) { qCWarning(GLTFImporterLog, "can't find shader %ls from path %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(path)); return; } m_shaderPaths[id] = info.absoluteFilePath(); } else { const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1); m_shaderPaths[id] = QString(QByteArray::fromBase64(base64Data)); } } void GLTFImporter::processJSONProgram(const QString &id, const QJsonObject &jsonObject) { const QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString(); const QString vertName = jsonObject.value(KEY_VERTEX_SHADER).toString(); const auto fragIt = qAsConst(m_shaderPaths).find(fragName); const auto vertIt = qAsConst(m_shaderPaths).find(vertName); if (Q_UNLIKELY(fragIt == m_shaderPaths.cend() || vertIt == m_shaderPaths.cend())) { qCWarning(GLTFImporterLog, "program: %ls missing shader: %ls %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(fragName), qUtf16PrintableImpl(vertName)); return; } QShaderProgram* prog = new QShaderProgram; prog->setObjectName(id); prog->setFragmentShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(fragIt.value()))); prog->setVertexShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(vertIt.value()))); const QString tessCtrlName = jsonObject.value(KEY_TESS_CTRL_SHADER).toString(); if (!tessCtrlName.isEmpty()) { const auto it = qAsConst(m_shaderPaths).find(tessCtrlName); prog->setTessellationControlShaderCode( QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); } const QString tessEvalName = jsonObject.value(KEY_TESS_EVAL_SHADER).toString(); if (!tessEvalName.isEmpty()) { const auto it = qAsConst(m_shaderPaths).find(tessEvalName); prog->setTessellationEvaluationShaderCode( QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); } const QString geomName = jsonObject.value(KEY_GEOMETRY_SHADER).toString(); if (!geomName.isEmpty()) { const auto it = qAsConst(m_shaderPaths).find(geomName); prog->setGeometryShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); } const QString computeName = jsonObject.value(KEY_COMPUTE_SHADER).toString(); if (!computeName.isEmpty()) { const auto it = qAsConst(m_shaderPaths).find(computeName); prog->setComputeShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value()))); } m_programs[id] = prog; } void GLTFImporter::processJSONTechnique(const QString &id, const QJsonObject &jsonObject ) { QTechnique *t = new QTechnique; t->setObjectName(id); const QJsonObject gabifilter = jsonObject.value(KEY_GABIFILTER).toObject(); if (gabifilter.isEmpty()) { // Regular GLTF technique // Parameters QHash paramDict; const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); for (auto it = params.begin(), end = params.end(); it != end; ++it) { const QString pname = it.key(); const QJsonObject po = it.value().toObject(); QParameter *p = buildParameter(pname, po); m_parameterDataDict.insert(p, ParameterData(po)); // We don't want to insert invalid parameters to techniques themselves, but we // need to maintain link between all parameters and techniques so we can resolve // parameter type later when creating materials. if (p->value().isValid()) t->addParameter(p); paramDict[pname] = p; } // Program QRenderPass *pass = new QRenderPass; addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString()); // Attributes const QJsonObject attrs = jsonObject.value(KEY_ATTRIBUTES).toObject(); for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) { QString pname = it.value().toString(); QParameter *parameter = paramDict.value(pname, nullptr); QString attributeName = pname; if (Q_UNLIKELY(!parameter)) { qCWarning(GLTFImporterLog, "attribute %ls defined in instanceProgram but not as parameter", qUtf16PrintableImpl(pname)); continue; } //Check if the parameter has a standard attribute semantic const auto paramDataIt = m_parameterDataDict.find(parameter); QString standardAttributeName = standardAttributeNameFromSemantic(paramDataIt->semantic); if (!standardAttributeName.isNull()) { attributeName = standardAttributeName; t->removeParameter(parameter); m_parameterDataDict.erase(paramDataIt); paramDict.remove(pname); delete parameter; } } // of program-instance attributes // Uniforms const QJsonObject uniforms = jsonObject.value(KEY_UNIFORMS).toObject(); for (auto it = uniforms.begin(), end = uniforms.end(); it != end; ++it) { const QString pname = it.value().toString(); QParameter *parameter = paramDict.value(pname, nullptr); if (Q_UNLIKELY(!parameter)) { qCWarning(GLTFImporterLog, "uniform %ls defined in instanceProgram but not as parameter", qUtf16PrintableImpl(pname)); continue; } //Check if the parameter has a standard uniform semantic const auto paramDataIt = m_parameterDataDict.find(parameter); if (hasStandardUniformNameFromSemantic(paramDataIt->semantic)) { t->removeParameter(parameter); m_parameterDataDict.erase(paramDataIt); paramDict.remove(pname); delete parameter; } } // of program-instance uniforms m_techniqueParameters.insert(t, paramDict.values()); populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject()); t->addRenderPass(pass); } else { // Qt3D exported custom technique // Graphics API filter t->graphicsApiFilter()->setApi(QGraphicsApiFilter::Api(gabifilter.value(KEY_API).toInt())); t->graphicsApiFilter()->setMajorVersion(gabifilter.value(KEY_MAJORVERSION).toInt()); t->graphicsApiFilter()->setMinorVersion(gabifilter.value(KEY_MINORVERSION).toInt()); t->graphicsApiFilter()->setProfile(QGraphicsApiFilter::OpenGLProfile( gabifilter.value(KEY_PROFILE).toInt())); t->graphicsApiFilter()->setVendor(gabifilter.value(KEY_VENDOR).toString()); QStringList extensionList; QJsonArray extArray = gabifilter.value(KEY_EXTENSIONS).toArray(); for (const QJsonValue &extValue : extArray) extensionList << extValue.toString(); t->graphicsApiFilter()->setExtensions(extensionList); // Filter keys (we will assume filter keys are always strings or integers) const QJsonObject fkObj = jsonObject.value(KEY_FILTERKEYS).toObject(); for (auto it = fkObj.begin(), end = fkObj.end(); it != end; ++it) t->addFilterKey(buildFilterKey(it.key(), it.value())); t->setObjectName(jsonObject.value(KEY_NAME).toString()); // Parameters const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); for (auto it = params.begin(), end = params.end(); it != end; ++it) t->addParameter(buildParameter(it.key(), it.value().toObject())); // Render passes const QJsonArray passArray = jsonObject.value(KEY_RENDERPASSES).toArray(); for (const QJsonValue &passValue : passArray) { const QString passName = passValue.toString(); QRenderPass *pass = m_renderPasses.value(passName); if (pass) { t->addRenderPass(pass); } else { qCWarning(GLTFImporterLog, "Render pass %ls missing for technique %ls", qUtf16PrintableImpl(passName), qUtf16PrintableImpl(id)); } } } m_techniques[id] = t; } void GLTFImporter::processJSONAccessor(const QString &id, const QJsonObject& json) { m_accessorDict[id] = AccessorData(json, m_majorVersion, m_minorVersion); } void GLTFImporter::processJSONMesh(const QString &id, const QJsonObject &json) { const QString meshName = json.value(KEY_NAME).toString(); const QString meshType = json.value(KEY_TYPE).toString(); if (meshType.isEmpty()) { // Custom mesh const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray(); for (const QJsonValue &primitiveValue : primitivesArray) { const QJsonObject primitiveObject = primitiveValue.toObject(); const QJsonValue type = primitiveObject.value(KEY_MODE); const QJsonValue matValue = primitiveObject.value(KEY_MATERIAL); const QString material = (m_majorVersion > 1) ? QString::number(matValue.toInt()) : matValue.toString(); QGeometryRenderer *geometryRenderer = new QGeometryRenderer; QGeometryView *geometryView = new QGeometryView; QGeometry *meshGeometry = new QGeometry(geometryRenderer); //Set Primitive Type if (type.isUndefined()) { geometryRenderer->setPrimitiveType(QGeometryRenderer::Triangles); } else { geometryRenderer->setPrimitiveType(static_cast(type.toInt())); } //Save Material for mesh m_meshMaterialDict[geometryRenderer] = material; const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject(); for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) { const QString k = (m_majorVersion > 1) ? QString::number(it.value().toInt()) : it.value().toString(); const auto accessorIt = qAsConst(m_accessorDict).find(k); if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) { qCWarning(GLTFImporterLog, "unknown attribute accessor: %ls on mesh %ls", qUtf16PrintableImpl(k), qUtf16PrintableImpl(id)); continue; } const QString attrName = it.key(); QString attributeName = standardAttributeNameFromSemantic(attrName); if (attributeName.isEmpty()) attributeName = attrName; //Get buffer handle for accessor Qt3DCore::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr); if (Q_UNLIKELY(!buffer)) { qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls", qUtf16PrintableImpl(accessorIt->bufferViewName), qUtf16PrintableImpl(id)); continue; } QAttribute *attribute = new QAttribute(buffer, attributeName, accessorIt->type, accessorIt->dataSize, accessorIt->count, accessorIt->offset, accessorIt->stride); attribute->setAttributeType(QAttribute::VertexAttribute); meshGeometry->addAttribute(attribute); } const auto indices = primitiveObject.value(KEY_INDICES); if (!indices.isUndefined()) { const QString accIndex = (m_majorVersion > 1) ? QString::number(indices.toInt()) : indices.toString(); const auto accessorIt = qAsConst(m_accessorDict).find(accIndex); if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) { qCWarning(GLTFImporterLog, "unknown index accessor: %ls on mesh %ls", qUtf16PrintableImpl(accIndex), qUtf16PrintableImpl(id)); } else { //Get buffer handle for accessor Qt3DCore::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr); if (Q_UNLIKELY(!buffer)) { qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls", qUtf16PrintableImpl(accessorIt->bufferViewName), qUtf16PrintableImpl(id)); continue; } QAttribute *attribute = new QAttribute(buffer, accessorIt->type, accessorIt->dataSize, accessorIt->count, accessorIt->offset, accessorIt->stride); attribute->setAttributeType(QAttribute::IndexAttribute); meshGeometry->addAttribute(attribute); } } // of has indices geometryView->setGeometry(meshGeometry); geometryRenderer->setView(geometryView); geometryRenderer->setObjectName(meshName); m_meshDict.insert(id, geometryRenderer); } // of primitives iteration } else { QGeometryRenderer *mesh = nullptr; if (meshType == QStringLiteral("cone")) { mesh = new QConeMesh; } else if (meshType == QStringLiteral("cuboid")) { mesh = new QCuboidMesh; } else if (meshType == QStringLiteral("cylinder")) { mesh = new QCylinderMesh; } else if (meshType == QStringLiteral("plane")) { mesh = new QPlaneMesh; } else if (meshType == QStringLiteral("sphere")) { mesh = new QSphereMesh; } else if (meshType == QStringLiteral("torus")) { mesh = new QTorusMesh; } else { qCWarning(GLTFImporterLog, "Invalid mesh type: %ls for mesh: %ls", qUtf16PrintableImpl(meshType), qUtf16PrintableImpl(id)); } if (mesh) { // Read and set properties const QJsonObject propObj = json.value(KEY_PROPERTIES).toObject(); QObject *target = mesh; if (mesh->view()) target = mesh->view(); for (auto it = propObj.begin(), end = propObj.end(); it != end; ++it) { const QByteArray propName = it.key().toLatin1(); // Basic mesh types only have bool, int, float, and QSize type properties if (it.value().isBool()) { target->setProperty(propName.constData(), QVariant(it.value().toBool())); } else if (it.value().isArray()) { const QJsonArray valueArray = it.value().toArray(); if (valueArray.size() == 2) { QSize size; size.setWidth(valueArray.at(0).toInt()); size.setHeight(valueArray.at(1).toInt()); target->setProperty(propName.constData(), QVariant(size)); } } else { const QVariant::Type propType = target->property(propName.constData()).type(); if (propType == QVariant::Int) { target->setProperty(propName.constData(), QVariant(it.value().toInt())); } else { target->setProperty(propName.constData(), QVariant(float(it.value().toDouble()))); } } } mesh->setObjectName(meshName); mesh->view()->setObjectName(meshName); m_meshMaterialDict[mesh] = (m_majorVersion > 1) ? QString::number(json.value(KEY_MATERIAL).toInt()) : json.value(KEY_MATERIAL).toString(); m_meshDict.insert(id, mesh); } } } void GLTFImporter::processJSONImage(const QString &id, const QJsonObject &jsonObject) { QString path = jsonObject.value(KEY_URI).toString(); if (!isEmbeddedResource(path)) { QFileInfo info(m_basePath, path); if (Q_UNLIKELY(!info.exists())) { qCWarning(GLTFImporterLog, "can't find image %ls from path %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(path)); return; } m_imagePaths[id] = info.absoluteFilePath(); } else { const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1); QImage image; image.loadFromData(QByteArray::fromBase64(base64Data)); m_imageData[id] = image; } } void GLTFImporter::processJSONTexture(const QString &id, const QJsonObject &jsonObject) { QJsonValue jsonVal = jsonObject.value(KEY_TARGET); if (!jsonVal.isUndefined()) { int target = jsonVal.toInt(GL_TEXTURE_2D); //TODO: support other targets that GL_TEXTURE_2D (though the spec doesn't support anything else) if (Q_UNLIKELY(target != GL_TEXTURE_2D)) { qCWarning(GLTFImporterLog, "unsupported texture target: %d", target); return; } } QTexture2D* tex = new QTexture2D; // TODO: Choose suitable internal format - may vary on OpenGL context type //int pixelFormat = jsonObj.value(KEY_FORMAT).toInt(GL_RGBA); int internalFormat = GL_RGBA; jsonVal = jsonObject.value(KEY_INTERNAL_FORMAT); if (!jsonVal.isUndefined()) internalFormat = jsonObject.value(KEY_INTERNAL_FORMAT).toInt(GL_RGBA); tex->setFormat(static_cast(internalFormat)); QJsonValue srcValue = jsonObject.value(KEY_SOURCE); QString source = (m_majorVersion > 1) ? QString::number(srcValue.toInt()) : srcValue.toString(); const auto imagIt = qAsConst(m_imagePaths).find(source); if (Q_UNLIKELY(imagIt == m_imagePaths.cend())) { // if an image is not found in paths, it probably means // it was an embedded resource, referenced in m_imageData const auto embImgIt = qAsConst(m_imageData).find(source); if (Q_UNLIKELY(embImgIt == m_imageData.cend())) { qCWarning(GLTFImporterLog, "texture %ls references missing image %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(source)); return; } QImage img = embImgIt.value(); GLTFRawTextureImage *imageData = new GLTFRawTextureImage(); imageData->setImage(img); tex->addTextureImage(imageData); } else { QTextureImage *texImage = new QTextureImage(tex); texImage->setMirrored(false); texImage->setSource(QUrl::fromLocalFile(imagIt.value())); tex->addTextureImage(texImage); } setTextureSamplerInfo(id, jsonObject, tex); m_textures[id] = tex; } void GLTFImporter::processJSONExtensions(const QString &id, const QJsonObject &jsonObject) { // Lights are defined in "KHR_materials_common" property of "extensions" property of the top // level GLTF item. if (id == KEY_COMMON_MAT) { const auto lights = jsonObject.value(KEY_LIGHTS).toObject(); const auto keys = lights.keys(); for (const auto &lightKey : keys) { const auto light = lights.value(lightKey).toObject(); auto lightType = light.value(KEY_TYPE).toString(); const auto lightValues = light.value(lightType).toObject(); QAbstractLight *lightComp = nullptr; if (lightType == KEY_DIRECTIONAL_LIGHT) { auto dirLight = new QDirectionalLight; dirLight->setWorldDirection( jsonArrToVec3(lightValues.value(KEY_DIRECTION).toArray())); lightComp = dirLight; } else if (lightType == KEY_SPOT_LIGHT) { auto spotLight = new QSpotLight; spotLight->setLocalDirection( jsonArrToVec3(lightValues.value(KEY_DIRECTION).toArray())); spotLight->setConstantAttenuation( lightValues.value(KEY_CONST_ATTENUATION).toDouble()); spotLight->setLinearAttenuation( lightValues.value(KEY_LINEAR_ATTENUATION).toDouble()); spotLight->setQuadraticAttenuation( lightValues.value(KEY_QUAD_ATTENUATION).toDouble()); spotLight->setCutOffAngle( lightValues.value(KEY_FALLOFF_ANGLE).toDouble()); lightComp = spotLight; } else if (lightType == KEY_POINT_LIGHT) { auto pointLight = new QPointLight; pointLight->setConstantAttenuation( lightValues.value(KEY_CONST_ATTENUATION).toDouble()); pointLight->setLinearAttenuation( lightValues.value(KEY_LINEAR_ATTENUATION).toDouble()); pointLight->setQuadraticAttenuation( lightValues.value(KEY_QUAD_ATTENUATION).toDouble()); lightComp = pointLight; } else if (lightType == KEY_AMBIENT_LIGHT) { qCWarning(GLTFImporterLog, "Ambient lights are not supported."); } else { qCWarning(GLTFImporterLog, "Unknown light type: %ls", qUtf16PrintableImpl(lightType)); } if (lightComp) { auto colorVal = lightValues.value(KEY_COLOR); lightComp->setColor(vec4ToQColor(parameterValueFromJSON(GL_FLOAT_VEC4, colorVal))); lightComp->setIntensity(lightValues.value(KEY_INTENSITY).toDouble()); lightComp->setObjectName(light.value(KEY_NAME).toString()); m_lights.insert(lightKey, lightComp); } } } } void GLTFImporter::processJSONEffect(const QString &id, const QJsonObject &jsonObject) { QEffect *effect = new QEffect; renameFromJson(jsonObject, effect); const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); for (auto it = params.begin(), end = params.end(); it != end; ++it) effect->addParameter(buildParameter(it.key(), it.value().toObject())); const QJsonArray techArray = jsonObject.value(KEY_TECHNIQUES).toArray(); for (const QJsonValue &techValue : techArray) { const QString techName = techValue.toString(); QTechnique *tech = m_techniques.value(techName); if (tech) { effect->addTechnique(tech); } else { qCWarning(GLTFImporterLog, "Technique pass %ls missing for effect %ls", qUtf16PrintableImpl(techName), qUtf16PrintableImpl(id)); } } m_effects[id] = effect; } void GLTFImporter::processJSONRenderPass(const QString &id, const QJsonObject &jsonObject) { QRenderPass *pass = new QRenderPass; const QJsonObject passFkObj = jsonObject.value(KEY_FILTERKEYS).toObject(); for (auto it = passFkObj.begin(), end = passFkObj.end(); it != end; ++it) pass->addFilterKey(buildFilterKey(it.key(), it.value())); const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject(); for (auto it = params.begin(), end = params.end(); it != end; ++it) pass->addParameter(buildParameter(it.key(), it.value().toObject())); populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject()); addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString()); renameFromJson(jsonObject, pass); m_renderPasses[id] = pass; } /*! Loads raw data from the GLTF file into the buffer. */ void GLTFImporter::loadBufferData() { for (auto &bufferData : m_bufferDatas) { if (!bufferData.data) { bufferData.data = new QByteArray(resolveLocalData(bufferData.path)); } } } /*! Removes all data from the buffer. */ void GLTFImporter::unloadBufferData() { for (const auto &bufferData : qAsConst(m_bufferDatas)) { QByteArray *data = bufferData.data; delete data; } } QByteArray GLTFImporter::resolveLocalData(const QString &path) const { QDir d(m_basePath); Q_ASSERT(d.exists()); // check for embedded data if (isEmbeddedResource(path)) { const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1); return QByteArray::fromBase64(base64Data); } else { const QString absPath = d.absoluteFilePath(path); QFile f(absPath); f.open(QIODevice::ReadOnly); return f.readAll(); } } QVariant GLTFImporter::parameterValueFromJSON(int type, const QJsonValue &value) const { if (value.isBool()) { if (type == GL_BOOL) return QVariant(static_cast(value.toBool())); } else if (value.isString()) { if (type == GL_SAMPLER_2D) { //Textures are special because we need to do a lookup to return the //QAbstractTexture QString textureId = value.toString(); const auto it = m_textures.find(textureId); if (Q_UNLIKELY(it == m_textures.end())) { qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId)); return QVariant(); } else { return QVariant::fromValue(it.value()); } } } else if (value.isDouble()) { switch (type) { case GL_BYTE: return QVariant(static_cast(value.toInt())); case GL_UNSIGNED_BYTE: return QVariant(static_cast(value.toInt())); case GL_SHORT: return QVariant(static_cast(value.toInt())); case GL_UNSIGNED_SHORT: return QVariant(static_cast(value.toInt())); case GL_INT: return QVariant(static_cast(value.toInt())); case GL_UNSIGNED_INT: return QVariant(static_cast(value.toInt())); case GL_FLOAT: return QVariant(static_cast(value.toDouble())); default: break; } } else if (value.isArray()) { const QJsonArray valueArray = value.toArray(); QVector2D vector2D; QVector3D vector3D; QVector4D vector4D; QList dataMat2(4, 0.0f); QList dataMat3(9, 0.0f); switch (type) { case GL_BYTE: return QVariant(static_cast(valueArray.first().toInt())); case GL_UNSIGNED_BYTE: return QVariant(static_cast(valueArray.first().toInt())); case GL_SHORT: return QVariant(static_cast(valueArray.first().toInt())); case GL_UNSIGNED_SHORT: return QVariant(static_cast(valueArray.first().toInt())); case GL_INT: return QVariant(static_cast(valueArray.first().toInt())); case GL_UNSIGNED_INT: return QVariant(static_cast(valueArray.first().toInt())); case GL_FLOAT: return QVariant(static_cast(valueArray.first().toDouble())); case GL_FLOAT_VEC2: vector2D.setX(static_cast(valueArray.at(0).toDouble())); vector2D.setY(static_cast(valueArray.at(1).toDouble())); return QVariant(vector2D); case GL_FLOAT_VEC3: vector3D.setX(static_cast(valueArray.at(0).toDouble())); vector3D.setY(static_cast(valueArray.at(1).toDouble())); vector3D.setZ(static_cast(valueArray.at(2).toDouble())); return QVariant(vector3D); case GL_FLOAT_VEC4: vector4D.setX(static_cast(valueArray.at(0).toDouble())); vector4D.setY(static_cast(valueArray.at(1).toDouble())); vector4D.setZ(static_cast(valueArray.at(2).toDouble())); vector4D.setW(static_cast(valueArray.at(3).toDouble())); return QVariant(vector4D); case GL_INT_VEC2: vector2D.setX(static_cast(valueArray.at(0).toInt())); vector2D.setY(static_cast(valueArray.at(1).toInt())); return QVariant(vector2D); case GL_INT_VEC3: vector3D.setX(static_cast(valueArray.at(0).toInt())); vector3D.setY(static_cast(valueArray.at(1).toInt())); vector3D.setZ(static_cast(valueArray.at(2).toInt())); return QVariant(vector3D); case GL_INT_VEC4: vector4D.setX(static_cast(valueArray.at(0).toInt())); vector4D.setY(static_cast(valueArray.at(1).toInt())); vector4D.setZ(static_cast(valueArray.at(2).toInt())); vector4D.setW(static_cast(valueArray.at(3).toInt())); return QVariant(vector4D); case GL_BOOL: return QVariant(static_cast(valueArray.first().toBool())); case GL_BOOL_VEC2: vector2D.setX(static_cast(valueArray.at(0).toBool())); vector2D.setY(static_cast(valueArray.at(1).toBool())); return QVariant(vector2D); case GL_BOOL_VEC3: vector3D.setX(static_cast(valueArray.at(0).toBool())); vector3D.setY(static_cast(valueArray.at(1).toBool())); vector3D.setZ(static_cast(valueArray.at(2).toBool())); return QVariant(vector3D); case GL_BOOL_VEC4: vector4D.setX(static_cast(valueArray.at(0).toBool())); vector4D.setY(static_cast(valueArray.at(1).toBool())); vector4D.setZ(static_cast(valueArray.at(2).toBool())); vector4D.setW(static_cast(valueArray.at(3).toBool())); return QVariant(vector4D); case GL_FLOAT_MAT2: //Matrix2x2 is in Row Major ordering (so we need to convert) dataMat2[0] = static_cast(valueArray.at(0).toDouble()); dataMat2[1] = static_cast(valueArray.at(2).toDouble()); dataMat2[2] = static_cast(valueArray.at(1).toDouble()); dataMat2[3] = static_cast(valueArray.at(3).toDouble()); return QVariant::fromValue(QMatrix2x2(dataMat2.constData())); case GL_FLOAT_MAT3: //Matrix3x3 is in Row Major ordering (so we need to convert) dataMat3[0] = static_cast(valueArray.at(0).toDouble()); dataMat3[1] = static_cast(valueArray.at(3).toDouble()); dataMat3[2] = static_cast(valueArray.at(6).toDouble()); dataMat3[3] = static_cast(valueArray.at(1).toDouble()); dataMat3[4] = static_cast(valueArray.at(4).toDouble()); dataMat3[5] = static_cast(valueArray.at(7).toDouble()); dataMat3[6] = static_cast(valueArray.at(2).toDouble()); dataMat3[7] = static_cast(valueArray.at(5).toDouble()); dataMat3[8] = static_cast(valueArray.at(8).toDouble()); return QVariant::fromValue(QMatrix3x3(dataMat3.constData())); case GL_FLOAT_MAT4: //Matrix4x4 is Column Major ordering return QVariant(QMatrix4x4(static_cast(valueArray.at(0).toDouble()), static_cast(valueArray.at(1).toDouble()), static_cast(valueArray.at(2).toDouble()), static_cast(valueArray.at(3).toDouble()), static_cast(valueArray.at(4).toDouble()), static_cast(valueArray.at(5).toDouble()), static_cast(valueArray.at(6).toDouble()), static_cast(valueArray.at(7).toDouble()), static_cast(valueArray.at(8).toDouble()), static_cast(valueArray.at(9).toDouble()), static_cast(valueArray.at(10).toDouble()), static_cast(valueArray.at(11).toDouble()), static_cast(valueArray.at(12).toDouble()), static_cast(valueArray.at(13).toDouble()), static_cast(valueArray.at(14).toDouble()), static_cast(valueArray.at(15).toDouble()))); case GL_SAMPLER_2D: return QVariant(valueArray.at(0).toString()); } } return QVariant(); } QAttribute::VertexBaseType GLTFImporter::accessorTypeFromJSON(int componentType) { if (componentType == GL_BYTE) { return QAttribute::Byte; } else if (componentType == GL_UNSIGNED_BYTE) { return QAttribute::UnsignedByte; } else if (componentType == GL_SHORT) { return QAttribute::Short; } else if (componentType == GL_UNSIGNED_SHORT) { return QAttribute::UnsignedShort; } else if (componentType == GL_UNSIGNED_INT) { return QAttribute::UnsignedInt; } else if (componentType == GL_FLOAT) { return QAttribute::Float; } //There shouldn't be an invalid case here qCWarning(GLTFImporterLog, "unsupported accessor type %d", componentType); return QAttribute::Float; } uint GLTFImporter::accessorDataSizeFromJson(const QString &type) { QString typeName = type.toUpper(); if (typeName == QLatin1String("SCALAR")) return 1; if (typeName == QLatin1String("VEC2")) return 2; if (typeName == QLatin1String("VEC3")) return 3; if (typeName == QLatin1String("VEC4")) return 4; if (typeName == QLatin1String("MAT2")) return 4; if (typeName == QLatin1String("MAT3")) return 9; if (typeName == QLatin1String("MAT4")) return 16; return 0; } QRenderState *GLTFImporter::buildStateEnable(int state) { int type = 0; //By calling buildState with QJsonValue(), a Render State with //default values is created. switch (state) { case GL_BLEND: //It doesn't make sense to handle this state alone return nullptr; case GL_CULL_FACE: return buildState(QStringLiteral("cullFace"), QJsonValue(), type); case GL_DEPTH_TEST: return buildState(QStringLiteral("depthFunc"), QJsonValue(), type); case GL_POLYGON_OFFSET_FILL: return buildState(QStringLiteral("polygonOffset"), QJsonValue(), type); case GL_SAMPLE_ALPHA_TO_COVERAGE: return new QAlphaCoverage(); case GL_SCISSOR_TEST: return buildState(QStringLiteral("scissor"), QJsonValue(), type); case GL_DITHER: // Qt3D Custom return new QDithering(); case 0x809D: // GL_MULTISAMPLE - Qt3D Custom return new QMultiSampleAntiAliasing(); case 0x884F: // GL_TEXTURE_CUBE_MAP_SEAMLESS - Qt3D Custom return new QSeamlessCubemap(); default: break; } qCWarning(GLTFImporterLog, "unsupported render state: %d", state); return nullptr; } QRenderState* GLTFImporter::buildState(const QString& functionName, const QJsonValue &value, int &type) { type = -1; QJsonArray values = value.toArray(); if (functionName == QLatin1String("blendColor")) { type = GL_BLEND; //TODO: support render state blendColor qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName)); return nullptr; } if (functionName == QLatin1String("blendEquationSeparate")) { type = GL_BLEND; //TODO: support settings blendEquation alpha QBlendEquation *blendEquation = new QBlendEquation; blendEquation->setBlendFunction((QBlendEquation::BlendFunction)values.at(0).toInt(GL_FUNC_ADD)); return blendEquation; } if (functionName == QLatin1String("blendFuncSeparate")) { type = GL_BLEND; QBlendEquationArguments *blendArgs = new QBlendEquationArguments; blendArgs->setSourceRgb((QBlendEquationArguments::Blending)values.at(0).toInt(GL_ONE)); blendArgs->setSourceAlpha((QBlendEquationArguments::Blending)values.at(1).toInt(GL_ONE)); blendArgs->setDestinationRgb((QBlendEquationArguments::Blending)values.at(2).toInt(GL_ZERO)); blendArgs->setDestinationAlpha((QBlendEquationArguments::Blending)values.at(3).toInt(GL_ZERO)); blendArgs->setBufferIndex(values.at(4).toInt(-1)); return blendArgs; } if (functionName == QLatin1String("colorMask")) { QColorMask *colorMask = new QColorMask; colorMask->setRedMasked(values.at(0).toBool(true)); colorMask->setGreenMasked(values.at(1).toBool(true)); colorMask->setBlueMasked(values.at(2).toBool(true)); colorMask->setAlphaMasked(values.at(3).toBool(true)); return colorMask; } if (functionName == QLatin1String("cullFace")) { type = GL_CULL_FACE; QCullFace *cullFace = new QCullFace; cullFace->setMode((QCullFace::CullingMode)values.at(0).toInt(GL_BACK)); return cullFace; } if (functionName == QLatin1String("depthFunc")) { type = GL_DEPTH_TEST; QDepthTest *depthTest = new QDepthTest; depthTest->setDepthFunction((QDepthTest::DepthFunction)values.at(0).toInt(GL_LESS)); return depthTest; } if (functionName == QLatin1String("depthMask")) { if (!values.at(0).toBool(true)) { QNoDepthMask *depthMask = new QNoDepthMask; return depthMask; } return nullptr; } if (functionName == QLatin1String("depthRange")) { type = GL_DEPTH_RANGE; QDepthRange *depthRange = new QDepthRange; depthRange->setNearValue(values.at(0).toDouble(0.0)); depthRange->setFarValue(values.at(1).toDouble(1.0)); return depthRange; } if (functionName == QLatin1String("frontFace")) { QFrontFace *frontFace = new QFrontFace; frontFace->setDirection((QFrontFace::WindingDirection)values.at(0).toInt(GL_CCW)); return frontFace; } if (functionName == QLatin1String("lineWidth")) { //TODO: support render state lineWidth qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName)); return nullptr; } if (functionName == QLatin1String("polygonOffset")) { type = GL_POLYGON_OFFSET_FILL; QPolygonOffset *polygonOffset = new QPolygonOffset; polygonOffset->setScaleFactor((float)values.at(0).toDouble(0.0f)); polygonOffset->setDepthSteps((float)values.at(1).toDouble(0.0f)); return polygonOffset; } if (functionName == QLatin1String("scissor")) { type = GL_SCISSOR_TEST; QScissorTest *scissorTest = new QScissorTest; scissorTest->setLeft(values.at(0).toDouble(0.0f)); scissorTest->setBottom(values.at(1).toDouble(0.0f)); scissorTest->setWidth(values.at(2).toDouble(0.0f)); scissorTest->setHeight(values.at(3).toDouble(0.0f)); return scissorTest; } // Qt3D custom functions if (functionName == QLatin1String("alphaTest")) { QAlphaTest *args = new QAlphaTest; args->setAlphaFunction(QAlphaTest::AlphaFunction(values.at(0).toInt())); args->setReferenceValue(float(values.at(1).toDouble())); return args; } if (functionName == QLatin1String("clipPlane")) { QClipPlane *args = new QClipPlane; args->setPlaneIndex(values.at(0).toInt()); args->setNormal(QVector3D(float(values.at(1).toDouble()), float(values.at(2).toDouble()), float(values.at(3).toDouble()))); args->setDistance(float(values.at(4).toDouble())); return args; } if (functionName == QLatin1String("pointSize")) { QPointSize *pointSize = new QPointSize; pointSize->setSizeMode(QPointSize::SizeMode(values.at(0).toInt(QPointSize::Programmable))); pointSize->setValue(float(values.at(1).toDouble())); return pointSize; } if (functionName == QLatin1String("stencilMask")) { QStencilMask *stencilMask = new QStencilMask; stencilMask->setFrontOutputMask(uint(values.at(0).toInt())); stencilMask->setBackOutputMask(uint(values.at(1).toInt())); return stencilMask; } if (functionName == QLatin1String("stencilOperation")) { QStencilOperation *stencilOperation = new QStencilOperation; stencilOperation->front()->setStencilTestFailureOperation( QStencilOperationArguments::Operation(values.at(0).toInt( QStencilOperationArguments::Keep))); stencilOperation->front()->setDepthTestFailureOperation( QStencilOperationArguments::Operation(values.at(1).toInt( QStencilOperationArguments::Keep))); stencilOperation->front()->setAllTestsPassOperation( QStencilOperationArguments::Operation(values.at(2).toInt( QStencilOperationArguments::Keep))); stencilOperation->back()->setStencilTestFailureOperation( QStencilOperationArguments::Operation(values.at(3).toInt( QStencilOperationArguments::Keep))); stencilOperation->back()->setDepthTestFailureOperation( QStencilOperationArguments::Operation(values.at(4).toInt( QStencilOperationArguments::Keep))); stencilOperation->back()->setAllTestsPassOperation( QStencilOperationArguments::Operation(values.at(5).toInt( QStencilOperationArguments::Keep))); return stencilOperation; } if (functionName == QLatin1String("stencilTest")) { QStencilTest *stencilTest = new QStencilTest; stencilTest->front()->setComparisonMask(uint(values.at(0).toInt())); stencilTest->front()->setReferenceValue(values.at(1).toInt()); stencilTest->front()->setStencilFunction( QStencilTestArguments::StencilFunction(values.at(2).toInt( QStencilTestArguments::Never))); stencilTest->back()->setComparisonMask(uint(values.at(3).toInt())); stencilTest->back()->setReferenceValue(values.at(4).toInt()); stencilTest->back()->setStencilFunction( QStencilTestArguments::StencilFunction(values.at(5).toInt( QStencilTestArguments::Never))); return stencilTest; } qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName)); return nullptr; } QParameter *GLTFImporter::buildParameter(const QString &key, const QJsonObject ¶mObj) { QParameter *p = new QParameter; p->setName(key); QJsonValue value = paramObj.value(KEY_VALUE); if (!value.isUndefined()) { int dataType = paramObj.value(KEY_TYPE).toInt(); p->setValue(parameterValueFromJSON(dataType, value)); } return p; } void GLTFImporter::populateRenderStates(QRenderPass *pass, const QJsonObject &states) { // Process states to enable const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray(); QList enableStates; for (const QJsonValue &enableValue : enableStatesArray) enableStates.append(enableValue.toInt()); // Process the list of state functions const QJsonObject functions = states.value(KEY_FUNCTIONS).toObject(); for (auto it = functions.begin(), end = functions.end(); it != end; ++it) { int enableStateType = 0; QRenderState *renderState = buildState(it.key(), it.value(), enableStateType); if (renderState != nullptr) { //Remove the need to set a default state values for enableStateType enableStates.removeOne(enableStateType); pass->addRenderState(renderState); } } // Create render states with default values for any remaining enable states for (int enableState : qAsConst(enableStates)) { QRenderState *renderState = buildStateEnable(enableState); if (renderState != nullptr) pass->addRenderState(renderState); } } void GLTFImporter::addProgramToPass(QRenderPass *pass, const QString &progName) { const auto progIt = qAsConst(m_programs).find(progName); if (Q_UNLIKELY(progIt == m_programs.cend())) qCWarning(GLTFImporterLog, "missing program %ls", qUtf16PrintableImpl(progName)); else pass->setShaderProgram(progIt.value()); } void GLTFImporter::setTextureSamplerInfo(const QString &id, const QJsonObject &jsonObj, QTexture2D *tex) { QJsonObject sampler; const QJsonValue jsonValue = jsonObj.value(KEY_SAMPLER); if (jsonValue.isUndefined()) return; if (m_majorVersion > 1) { const int samplerId = jsonValue.toInt(); const QJsonArray sArray = m_json.object().value(KEY_SAMPLERS).toArray(); if (Q_UNLIKELY(samplerId >= sArray.count())) { qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %d", qUtf16PrintableImpl(id), samplerId); return; } sampler = sArray[samplerId].toObject(); } else { const QString samplerId = jsonValue.toString(); const QJsonValue samplersDictValue = m_json.object().value(KEY_SAMPLERS).toObject().value(samplerId); if (Q_UNLIKELY(samplersDictValue.isUndefined())) { qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %ls", qUtf16PrintableImpl(id), qUtf16PrintableImpl(samplerId)); return; } sampler = samplersDictValue.toObject(); } tex->setWrapMode(QTextureWrapMode(static_cast(sampler.value(KEY_WRAP_S).toInt()))); tex->setMinificationFilter(static_cast(sampler.value(KEY_MIN_FILTER).toInt())); if (tex->minificationFilter() == QAbstractTexture::NearestMipMapLinear || tex->minificationFilter() == QAbstractTexture::LinearMipMapNearest || tex->minificationFilter() == QAbstractTexture::NearestMipMapNearest || tex->minificationFilter() == QAbstractTexture::LinearMipMapLinear) { tex->setGenerateMipMaps(true); } tex->setMagnificationFilter(static_cast(sampler.value(KEY_MAG_FILTER).toInt())); } GLTFRawTextureImage::GLTFRawTextureImage(QNode *parent) : QAbstractTextureImage(parent) { } QTextureImageDataGeneratorPtr GLTFRawTextureImage::dataGenerator() const { return QTextureImageDataGeneratorPtr(new GLTFRawTextureImageFunctor(m_image)); } void GLTFRawTextureImage::setImage(const QImage &image) { if (image != m_image) { m_image = image; notifyDataGeneratorChanged(); } } GLTFRawTextureImage::GLTFRawTextureImageFunctor::GLTFRawTextureImageFunctor(const QImage &image) : QTextureImageDataGenerator() , m_image(image) { } QTextureImageDataPtr GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator()() { QTextureImageDataPtr dataPtr = QTextureImageDataPtr::create(); // Note: we assume 4 components per pixel and not compressed for now dataPtr->setImage(m_image); return dataPtr; } bool GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator ==(const QTextureImageDataGenerator &other) const { const GLTFRawTextureImageFunctor *otherFunctor = functor_cast(&other); return (otherFunctor != nullptr && otherFunctor->m_image == m_image); } } // namespace Qt3DRender QT_END_NAMESPACE #include "gltfimporter.moc"