summaryrefslogtreecommitdiffstats
path: root/src/plugins/sceneparsers/gltf/gltfio.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/sceneparsers/gltf/gltfio.cpp')
-rw-r--r--src/plugins/sceneparsers/gltf/gltfio.cpp1588
1 files changed, 1588 insertions, 0 deletions
diff --git a/src/plugins/sceneparsers/gltf/gltfio.cpp b/src/plugins/sceneparsers/gltf/gltfio.cpp
new file mode 100644
index 000000000..c7c7a00dd
--- /dev/null
+++ b/src/plugins/sceneparsers/gltf/gltfio.cpp
@@ -0,0 +1,1588 @@
+/****************************************************************************
+**
+** 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 <Qt3DCore/QEntity>
+#include <Qt3DCore/QTransform>
+#include <Qt3DExtras/QDiffuseMapMaterial>
+#include <Qt3DExtras/QDiffuseSpecularMapMaterial>
+#include <Qt3DExtras/QNormalDiffuseMapMaterial>
+#include <Qt3DExtras/QNormalDiffuseSpecularMapMaterial>
+#include <Qt3DExtras/QPhongMaterial>
+#include <Qt3DRender/QAlphaCoverage>
+#include <Qt3DRender/QBlendEquation>
+#include <Qt3DRender/QBlendEquationArguments>
+#include <Qt3DRender/QCameraLens>
+#include <Qt3DRender/QColorMask>
+#include <Qt3DRender/QCullFace>
+#include <Qt3DRender/QDepthTest>
+#include <Qt3DRender/QEffect>
+#include <Qt3DRender/QFrontFace>
+#include <Qt3DRender/QGeometry>
+#include <Qt3DRender/QGeometryRenderer>
+#include <Qt3DRender/QGraphicsApiFilter>
+#include <Qt3DRender/QMaterial>
+#include <Qt3DRender/QNoDepthMask>
+#include <Qt3DRender/QParameter>
+#include <Qt3DRender/QPolygonOffset>
+#include <Qt3DRender/QRenderState>
+#include <Qt3DRender/QScissorTest>
+#include <Qt3DRender/QShaderProgram>
+#include <Qt3DRender/QTechnique>
+#include <Qt3DRender/QTexture>
+#include <QtCore/QDir>
+#include <QtCore/QFileInfo>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonObject>
+#include <QtGui/QVector2D>
+
+#include "gltfio.h"
+
+#include <Qt3DRender/private/qurlhelper_p.h>
+
+#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature
+# define qUtf16PrintableImpl(string) \
+ static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt3DCore;
+using namespace Qt3DExtras;
+
+namespace Qt3DRender {
+
+Q_LOGGING_CATEGORY(GLTFIOLog, "Qt3D.GLTFIO")
+
+#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_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_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_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_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_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_TECHNIQUE_CORE QLatin1String("techniqueCore")
+#define KEY_TECHNIQUE_GL2 QLatin1String("techniqueGL2")
+
+GLTFIO::GLTFIO() : QSceneIOHandler(),
+ m_parseDone(false)
+{
+}
+
+GLTFIO::~GLTFIO()
+{
+
+}
+
+void GLTFIO::setBasePath(const QString& path)
+{
+ m_basePath = path;
+}
+
+bool GLTFIO::setJSON(const QJsonDocument &json )
+{
+ if ( !json.isObject() ) {
+ return false;
+ }
+
+ m_json = json;
+ m_parseDone = false;
+
+ cleanup();
+
+ return true;
+}
+
+/*!
+ * Sets the \a path used by the parser to load the scene file.
+ * If the file is valid, parsing is automatically triggered.
+ */
+void GLTFIO::setSource(const QUrl &source)
+{
+ const QString path = QUrlHelper::urlToLocalFileOrQrc(source);
+ QFileInfo finfo(path);
+ if (Q_UNLIKELY(!finfo.exists())) {
+ qCWarning(GLTFIOLog, "missing file: %ls", qUtf16PrintableImpl(path));
+ return;
+ }
+ QFile f(path);
+ f.open(QIODevice::ReadOnly);
+
+ QByteArray jsonData = f.readAll();
+ QJsonDocument sceneDocument = QJsonDocument::fromBinaryData(jsonData);
+ if (sceneDocument.isNull())
+ sceneDocument = QJsonDocument::fromJson(jsonData);
+
+ if (Q_UNLIKELY(!setJSON(sceneDocument))) {
+ qCWarning(GLTFIOLog, "not a JSON document");
+ return;
+ }
+
+ setBasePath(finfo.dir().absolutePath());
+}
+
+/*!
+ * Returns true if the extension of \a path is supported by the
+ * GLTF parser.
+ */
+bool GLTFIO::isFileTypeSupported(const QUrl &source) const
+{
+ const QString path = QUrlHelper::urlToLocalFileOrQrc(source);
+ return GLTFIO::isGLTFPath(path);
+}
+
+Qt3DCore::QEntity* GLTFIO::node(const QString &id)
+{
+ QJsonObject nodes = m_json.object().value(KEY_NODES).toObject();
+ const auto jsonVal = nodes.value(id);
+ if (Q_UNLIKELY(jsonVal.isUndefined())) {
+ qCWarning(GLTFIOLog, "unknown node %ls in GLTF file %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
+ return NULL;
+ }
+
+ 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
+ {
+ QVector<QEntity *> entities;
+
+ const auto meshes = jsonObj.value(KEY_MESHES).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(GLTFIOLog, "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);
+ }
+ }
+
+ //If the entity contains no meshes, results will still be null here
+ if (result == nullptr)
+ result = new QEntity;
+
+ {
+ const auto children = jsonObj.value(KEY_CHILDREN).toArray();
+ for (const QJsonValue &c : children) {
+ QEntity* child = node(c.toString());
+ if (!child)
+ continue;
+ child->setParent(result);
+ }
+ }
+
+ renameFromJson(jsonObj, result);
+
+
+ // Node Transforms
+ Qt3DCore::QTransform *trans = nullptr;
+ const auto matrix = jsonObj.value(KEY_MATRIX);
+ 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;
+ }
+
+ // ADD MATRIX TRANSFORM COMPONENT TO ENTITY
+ if (trans == nullptr)
+ trans = new Qt3DCore::QTransform;
+ trans->setMatrix(m);
+ }
+
+ // Rotation quaternion
+ const auto rotation = jsonObj.value(KEY_ROTATION);
+ if (!rotation.isUndefined()) {
+ if (!trans)
+ trans = new Qt3DCore::QTransform;
+
+ const QJsonArray quaternionValues = rotation.toArray();
+ QQuaternion quaternion(quaternionValues[0].toDouble(),
+ quaternionValues[1].toDouble(),
+ quaternionValues[2].toDouble(),
+ quaternionValues[3].toDouble());
+ trans->setRotation(quaternion);
+ }
+
+ // Translation
+ const auto translation = jsonObj.value(KEY_TRANSLATION);
+ if (!translation.isUndefined()) {
+ if (!trans)
+ trans = new Qt3DCore::QTransform;
+
+ const QJsonArray translationValues = translation.toArray();
+ trans->setTranslation(QVector3D(translationValues[0].toDouble(),
+ translationValues[1].toDouble(),
+ translationValues[2].toDouble()));
+ }
+
+ // Scale
+ const auto scale = jsonObj.value(KEY_SCALE);
+ if (!scale.isUndefined()) {
+ if (!trans)
+ trans = new Qt3DCore::QTransform;
+
+ const QJsonArray scaleValues = scale.toArray();
+ trans->setScale3D(QVector3D(scaleValues[0].toDouble(),
+ scaleValues[1].toDouble(),
+ scaleValues[2].toDouble()));
+ }
+
+ // Add the Transform component
+ if (trans != nullptr)
+ result->addComponent(trans);
+
+ const auto cameraVal = jsonObj.value(KEY_CAMERA);
+ if (!cameraVal.isUndefined()) {
+ QCameraLens* cam = camera(cameraVal.toString());
+ if (Q_UNLIKELY(!cam)) {
+ qCWarning(GLTFIOLog) << "failed to build camera:" << cameraVal
+ << "on node" << id;
+ } else {
+ result->addComponent(cam);
+ }
+ } // of have camera attribute
+
+ return result;
+}
+
+Qt3DCore::QEntity* GLTFIO::scene(const QString &id)
+{
+ parse();
+
+ 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(GLTFIOLog, "GLTF: no such scene %ls in file %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
+ return defaultScene();
+ }
+
+ QJsonObject sceneObj = sceneVal.toObject();
+ QEntity* 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);
+ }
+
+ return sceneEntity;
+}
+
+GLTFIO::BufferData::BufferData()
+ : length(0)
+ , data(nullptr)
+{
+}
+
+GLTFIO::BufferData::BufferData(const QJsonObject &json)
+ : length(json.value(KEY_BYTE_LENGTH).toInt()),
+ path(json.value(KEY_URI).toString()),
+ data(nullptr)
+{
+}
+
+GLTFIO::ParameterData::ParameterData() :
+ type(0)
+{
+
+}
+
+GLTFIO::ParameterData::ParameterData(const QJsonObject &json)
+ : semantic(json.value(KEY_SEMANTIC).toString()),
+ type(json.value(KEY_TYPE).toInt())
+{
+}
+
+GLTFIO::AccessorData::AccessorData()
+ : type(QAttribute::Float)
+ , dataSize(0)
+ , count(0)
+ , offset(0)
+ , stride(0)
+{
+
+}
+
+GLTFIO::AccessorData::AccessorData(const QJsonObject &json)
+ : bufferViewName(json.value(KEY_BUFFER_VIEW).toString()),
+ 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)
+{
+ 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 GLTFIO::isGLTFPath(const QString& path)
+{
+ QFileInfo finfo(path);
+ if (!finfo.exists())
+ return false;
+
+ // might need to detect other things in the future, but would
+ // prefer to avoid doing a full parse.
+ QString suffix = finfo.suffix().toLower();
+ return suffix == QLatin1String("json") || suffix == QLatin1String("gltf") || suffix == QLatin1String("qgltf");
+}
+
+void GLTFIO::renameFromJson(const QJsonObject &json, QObject * const object)
+{
+ const auto name = json.value(KEY_NAME);
+ if (!name.isUndefined())
+ object->setObjectName(name.toString());
+}
+
+bool GLTFIO::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 GLTFIO::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 *GLTFIO::parameterFromTechnique(QTechnique *technique, const QString &parameterName)
+{
+ const auto parameters = technique->parameters();
+ for (QParameter *parameter : parameters) {
+ if (parameter->name() == parameterName) {
+ return parameter;
+ }
+ }
+
+ return nullptr;
+}
+
+Qt3DCore::QEntity* GLTFIO::defaultScene()
+{
+ if (Q_UNLIKELY(m_defaultScene.isEmpty())) {
+ qCWarning(GLTFIOLog, "no default scene");
+ return NULL;
+ }
+
+ return scene(m_defaultScene);
+}
+
+QMaterial *GLTFIO::materialWithCustomShader(const QString &id, const QJsonObject &jsonObj)
+{
+ //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(GLTFIOLog, "unknown technique %ls for material %ls in GLTF file %ls",
+ qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
+ return NULL;
+ }
+ 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(GLTFIOLog, "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(GLTFIOLog, "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(GLTFIOLog, "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;
+}
+
+static inline QVariant vec4ToRgb(const QVariant &vec4Var)
+{
+ const QVector4D v = vec4Var.value<QVector4D>();
+ return QVariant(QColor::fromRgbF(v.x(), v.y(), v.z()));
+}
+
+QMaterial *GLTFIO::commonMaterial(const QJsonObject &jsonObj)
+{
+ QVariantHash params;
+ bool hasDiffuseMap = false;
+ bool hasSpecularMap = false;
+ bool hasNormalMap = false;
+
+ const QJsonObject values = jsonObj.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 = vec4ToRgb(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 = vec4ToRgb(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 = vec4ToRgb(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")) {
+ qCWarning(GLTFIOLog, "Semi-transparent common materials are not currently supported, ignoring alpha");
+ }
+ if (var.isValid())
+ params[propertyName] = var;
+ }
+
+ QMaterial *mat = nullptr;
+ if (hasNormalMap) {
+ if (hasSpecularMap) {
+ mat = new QNormalDiffuseSpecularMapMaterial;
+ } else {
+ if (Q_UNLIKELY(!hasDiffuseMap))
+ qCWarning(GLTFIOLog, "Common material with normal and specular maps needs a diffuse map as well");
+ else
+ mat = new QNormalDiffuseMapMaterial;
+ }
+ } else {
+ if (hasSpecularMap) {
+ if (Q_UNLIKELY(!hasDiffuseMap))
+ qCWarning(GLTFIOLog, "Common material with specular map needs a diffuse map as well");
+ else
+ mat = new QDiffuseSpecularMapMaterial;
+ } else if (hasDiffuseMap) {
+ mat = new QDiffuseMapMaterial;
+ } else {
+ mat = new QPhongMaterial;
+ }
+ }
+
+ if (Q_UNLIKELY(!mat)) {
+ qCWarning(GLTFIOLog, "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());
+ }
+
+ return mat;
+}
+
+QMaterial* GLTFIO::material(const QString &id)
+{
+ const auto it = qAsConst(m_materialCache).find(id);
+ if (it != m_materialCache.cend())
+ return it.value();
+
+ QJsonObject mats = m_json.object().value(KEY_MATERIALS).toObject();
+ const auto jsonVal = mats.value(id);
+ if (Q_UNLIKELY(jsonVal.isUndefined())) {
+ qCWarning(GLTFIOLog, "unknown material %ls in GLTF file %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
+ return NULL;
+ }
+
+ const QJsonObject jsonObj = jsonVal.toObject();
+
+ QMaterial *mat = nullptr;
+
+ // Prefer common materials over custom shaders.
+ const auto extensionMat = jsonObj.value(KEY_EXTENSIONS).toObject().value(KEY_COMMON_MAT);
+ if (!extensionMat.isUndefined())
+ mat = commonMaterial(extensionMat.toObject());
+
+ if (!mat)
+ mat = materialWithCustomShader(id, jsonObj);
+
+ m_materialCache[id] = mat;
+ return mat;
+}
+
+QCameraLens* GLTFIO::camera(const QString &id) const
+{
+ const auto jsonVal = m_json.object().value(KEY_CAMERAS).toObject().value(id);
+ if (Q_UNLIKELY(jsonVal.isUndefined())) {
+ qCWarning(GLTFIOLog, "unknown camera %ls in GLTF file %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
+ return nullptr;
+ }
+
+ QJsonObject 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(GLTFIOLog, "camera: %ls missing 'perspective' object",
+ qUtf16PrintableImpl(id));
+ return nullptr;
+ }
+
+ 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();
+
+ QCameraLens* result = new QCameraLens;
+ result->setPerspectiveProjection(yfov, aspectRatio, frustumNear, frustumFar);
+ return result;
+ } else if (camTy == QLatin1String("orthographic")) {
+ qCWarning(GLTFIOLog, "implement me");
+
+ return nullptr;
+ } else {
+ qCWarning(GLTFIOLog, "camera: %ls has unsupported type: %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(camTy));
+ return nullptr;
+ }
+}
+
+
+void GLTFIO::parse()
+{
+ if (m_parseDone)
+ return;
+
+ 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 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 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());
+
+ m_defaultScene = m_json.object().value(KEY_SCENE).toString();
+ m_parseDone = true;
+}
+
+namespace {
+template <typename C>
+void delete_if_without_parent(const C &c)
+{
+ for (const auto *e : c) {
+ if (!e->parent())
+ delete e;
+ }
+}
+} // unnamed namespace
+
+void GLTFIO::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();
+ delete_if_without_parent(m_techniques);
+ m_techniques.clear();
+ delete_if_without_parent(m_textures);
+ m_textures.clear();
+ m_imagePaths.clear();
+ m_defaultScene.clear();
+ m_parameterDataDict.clear();
+}
+
+void GLTFIO::processJSONBuffer(const QString &id, const QJsonObject& json)
+{
+ // simply cache buffers for lookup by buffer-views
+ m_bufferDatas[id] = BufferData(json);
+}
+
+void GLTFIO::processJSONBufferView(const QString &id, const QJsonObject& json)
+{
+ QString bufName = json.value(KEY_BUFFER).toString();
+ const auto it = qAsConst(m_bufferDatas).find(bufName);
+ if (Q_UNLIKELY(it == m_bufferDatas.cend())) {
+ qCWarning(GLTFIOLog, "unknown buffer: %ls processing view: %ls",
+ qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id));
+ return;
+ }
+ const auto &bufferData = *it;
+
+ int target = json.value(KEY_TARGET).toInt();
+ Qt3DRender::QBuffer::BufferType ty(Qt3DRender::QBuffer::VertexBuffer);
+
+ switch (target) {
+ case GL_ARRAY_BUFFER: ty = Qt3DRender::QBuffer::VertexBuffer; break;
+ case GL_ELEMENT_ARRAY_BUFFER: ty = Qt3DRender::QBuffer::IndexBuffer; break;
+ default:
+ qCWarning(GLTFIOLog, "buffer %ls unsupported target: %d",
+ qUtf16PrintableImpl(id), target);
+ return;
+ }
+
+ quint64 offset = 0;
+ const auto byteOffset = json.value(KEY_BYTE_OFFSET);
+ if (!byteOffset.isUndefined()) {
+ offset = byteOffset.toInt();
+ qCDebug(GLTFIOLog, "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(GLTFIOLog, "failed to read sufficient bytes from: %ls for view %ls",
+ qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id));
+ }
+
+ Qt3DRender::QBuffer *b(new Qt3DRender::QBuffer(ty));
+ b->setData(bytes);
+ m_buffers[id] = b;
+}
+
+void GLTFIO::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();
+
+ QFileInfo info(m_basePath, path);
+ if (Q_UNLIKELY(!info.exists())) {
+ qCWarning(GLTFIOLog, "can't find shader %ls from path %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
+ return;
+ }
+
+ m_shaderPaths[id] = info.absoluteFilePath();
+}
+
+void GLTFIO::processJSONProgram(const QString &id, const QJsonObject &jsonObject)
+{
+ QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString(),
+ vertName = jsonObject.value(KEY_VERTEX_SHADER).toString();
+ const auto fragIt = qAsConst(m_shaderPaths).find(fragName),
+ vertIt = qAsConst(m_shaderPaths).find(vertName);
+ if (Q_UNLIKELY(fragIt == m_shaderPaths.cend() || vertIt == m_shaderPaths.cend())) {
+ qCWarning(GLTFIOLog, "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())));
+ m_programs[id] = prog;
+}
+
+void GLTFIO::processJSONTechnique(const QString &id, const QJsonObject &jsonObject )
+{
+ QTechnique *t = new QTechnique;
+ t->setObjectName(id);
+
+ // Parameters
+ QHash<QString, QParameter*> 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();
+
+ //QString semantic = po.value(KEY_SEMANTIC).toString();
+ QParameter *p = new QParameter(t);
+ p->setName(pname);
+ m_parameterDataDict.insert(p, ParameterData(po));
+
+ //If the parameter has default value, set it
+ QJsonValue value = po.value(KEY_VALUE);
+ if (!value.isUndefined()) {
+ int dataType = po.value(KEY_TYPE).toInt();
+ p->setValue(parameterValueFromJSON(dataType, value));
+ }
+
+ t->addParameter(p);
+
+ paramDict[pname] = p;
+ } // of parameters iteration
+
+ // Program
+ QRenderPass* pass = new QRenderPass;
+ QString programName = jsonObject.value(KEY_PROGRAM).toString();
+ const auto progIt = qAsConst(m_programs).find(programName);
+ if (Q_UNLIKELY(progIt == m_programs.cend())) {
+ qCWarning(GLTFIOLog, "technique %ls: missing program %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(programName));
+ } else {
+ pass->setShaderProgram(progIt.value());
+ }
+
+ // 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(GLTFIOLog, "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);
+ 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(GLTFIOLog, "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);
+ delete parameter;
+ }
+ } // of program-instance uniforms
+
+
+ // States
+ QJsonObject states = jsonObject.value(KEY_STATES).toObject();
+
+ //Process states to enable
+ const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray();
+ QVector<int> 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);
+ }
+
+
+ t->addRenderPass(pass);
+
+ m_techniques[id] = t;
+}
+
+void GLTFIO::processJSONAccessor( const QString &id, const QJsonObject& json )
+{
+ m_accessorDict[id] = AccessorData(json);
+}
+
+void GLTFIO::processJSONMesh(const QString &id, const QJsonObject &json)
+{
+ const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
+ for (const QJsonValue &primitiveValue : primitivesArray) {
+ QJsonObject primitiveObject = primitiveValue.toObject();
+ int type = primitiveObject.value(KEY_MODE).toInt();
+ QString material = primitiveObject.value(KEY_MATERIAL).toString();
+
+ if (Q_UNLIKELY(material.isEmpty())) {
+ qCWarning(GLTFIOLog, "malformed primitive on %ls, missing material value %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(material));
+ continue;
+ }
+
+ QGeometryRenderer *geometryRenderer = new QGeometryRenderer;
+ QGeometry *meshGeometry = new QGeometry(geometryRenderer);
+
+ //Set Primitive Type
+ geometryRenderer->setPrimitiveType(static_cast<QGeometryRenderer::PrimitiveType>(type));
+
+ //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) {
+ QString k = it.value().toString();
+ const auto accessorIt = qAsConst(m_accessorDict).find(k);
+ if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
+ qCWarning(GLTFIOLog, "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
+ Qt3DRender::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr);
+ if (Q_UNLIKELY(!buffer)) {
+ qCWarning(GLTFIOLog, "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()) {
+ QString k = indices.toString();
+ const auto accessorIt = qAsConst(m_accessorDict).find(k);
+ if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
+ qCWarning(GLTFIOLog, "unknown index accessor: %ls on mesh %ls",
+ qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
+ } else {
+ //Get buffer handle for accessor
+ Qt3DRender::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr);
+ if (Q_UNLIKELY(!buffer)) {
+ qCWarning(GLTFIOLog, "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
+
+ geometryRenderer->setGeometry(meshGeometry);
+
+ m_meshDict.insert( id, geometryRenderer);
+ } // of primitives iteration
+}
+
+void GLTFIO::processJSONImage(const QString &id, const QJsonObject &jsonObject)
+{
+ QString path = jsonObject.value(KEY_URI).toString();
+ QFileInfo info(m_basePath, path);
+ if (Q_UNLIKELY(!info.exists())) {
+ qCWarning(GLTFIOLog, "can't find image %ls from path %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
+ return;
+ }
+
+ m_imagePaths[id] = info.absoluteFilePath();
+}
+
+void GLTFIO::processJSONTexture(const QString &id, const QJsonObject &jsonObject)
+{
+ int target = jsonObject.value(KEY_TARGET).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(GLTFIOLog, "unsupported texture target: %d", target);
+ return;
+ }
+
+ QTextureLoader* tex = new QTextureLoader;
+ tex->setMirrored(false);
+
+ QString samplerId = jsonObject.value(KEY_SAMPLER).toString();
+ QString source = jsonObject.value(KEY_SOURCE).toString();
+ const auto imagIt = qAsConst(m_imagePaths).find(source);
+ if (Q_UNLIKELY(imagIt == m_imagePaths.cend())) {
+ qCWarning(GLTFIOLog, "texture %ls references missing image %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(source));
+ return;
+ }
+
+ tex->setSource(QUrl::fromLocalFile(imagIt.value()));
+
+ const auto samplersDictValue = m_json.object().value(KEY_SAMPLERS).toObject().value(samplerId);
+ if (Q_UNLIKELY(samplersDictValue.isUndefined())) {
+ qCWarning(GLTFIOLog, "texture %ls references unknown sampler %ls",
+ qUtf16PrintableImpl(id), qUtf16PrintableImpl(samplerId));
+ return;
+ }
+
+ QJsonObject sampler = samplersDictValue.toObject();
+
+ tex->setWrapMode(QTextureWrapMode(static_cast<QTextureWrapMode::WrapMode>(sampler.value(KEY_WRAP_S).toInt())));
+ tex->setMinificationFilter(static_cast<QAbstractTexture::Filter>(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<QAbstractTexture::Filter>(sampler.value(KEY_MAG_FILTER).toInt()));
+
+ m_textures[id] = tex;
+}
+
+void GLTFIO::loadBufferData()
+{
+ for (auto &bufferData : m_bufferDatas) {
+ if (!bufferData.data) {
+ bufferData.data = new QByteArray(resolveLocalData(bufferData.path));
+ }
+ }
+}
+
+void GLTFIO::unloadBufferData()
+{
+ for (const auto &bufferData : qAsConst(m_bufferDatas)) {
+ QByteArray *data = bufferData.data;
+ delete data;
+ }
+}
+
+QByteArray GLTFIO::resolveLocalData(const QString &path) const
+{
+ QDir d(m_basePath);
+ Q_ASSERT(d.exists());
+
+ QString absPath = d.absoluteFilePath(path);
+ QFile f(absPath);
+ f.open(QIODevice::ReadOnly);
+ return f.readAll();
+}
+
+QVariant GLTFIO::parameterValueFromJSON(int type, const QJsonValue &value) const
+{
+ if (value.isBool()) {
+ if (type == GL_BOOL)
+ return QVariant(static_cast<GLboolean>(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(GLTFIOLog, "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<GLbyte>(value.toInt()));
+ case GL_UNSIGNED_BYTE:
+ return QVariant(static_cast<GLubyte>(value.toInt()));
+ case GL_SHORT:
+ return QVariant(static_cast<GLshort>(value.toInt()));
+ case GL_UNSIGNED_SHORT:
+ return QVariant(static_cast<GLushort>(value.toInt()));
+ case GL_INT:
+ return QVariant(static_cast<GLint>(value.toInt()));
+ case GL_UNSIGNED_INT:
+ return QVariant(static_cast<GLuint>(value.toInt()));
+ case GL_FLOAT:
+ return QVariant(static_cast<GLfloat>(value.toDouble()));
+ }
+ } else if (value.isArray()) {
+
+ const QJsonArray valueArray = value.toArray();
+
+ QVector2D vector2D;
+ QVector3D vector3D;
+ QVector4D vector4D;
+ QVector<float> dataMat2(4, 0.0f);
+ QVector<float> dataMat3(9, 0.0f);
+
+ switch (type) {
+ case GL_BYTE:
+ return QVariant(static_cast<GLbyte>(valueArray.first().toInt()));
+ case GL_UNSIGNED_BYTE:
+ return QVariant(static_cast<GLubyte>(valueArray.first().toInt()));
+ case GL_SHORT:
+ return QVariant(static_cast<GLshort>(valueArray.first().toInt()));
+ case GL_UNSIGNED_SHORT:
+ return QVariant(static_cast<GLushort>(valueArray.first().toInt()));
+ case GL_INT:
+ return QVariant(static_cast<GLint>(valueArray.first().toInt()));
+ case GL_UNSIGNED_INT:
+ return QVariant(static_cast<GLuint>(valueArray.first().toInt()));
+ case GL_FLOAT:
+ return QVariant(static_cast<GLfloat>(valueArray.first().toDouble()));
+ case GL_FLOAT_VEC2:
+ vector2D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
+ vector2D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
+ return QVariant(vector2D);
+ case GL_FLOAT_VEC3:
+ vector3D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
+ vector3D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
+ vector3D.setZ(static_cast<GLfloat>(valueArray.at(2).toDouble()));
+ return QVariant(vector3D);
+ case GL_FLOAT_VEC4:
+ vector4D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
+ vector4D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
+ vector4D.setZ(static_cast<GLfloat>(valueArray.at(2).toDouble()));
+ vector4D.setW(static_cast<GLfloat>(valueArray.at(3).toDouble()));
+ return QVariant(vector4D);
+ case GL_INT_VEC2:
+ vector2D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
+ vector2D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
+ return QVariant(vector2D);
+ case GL_INT_VEC3:
+ vector3D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
+ vector3D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
+ vector3D.setZ(static_cast<GLint>(valueArray.at(2).toInt()));
+ return QVariant(vector3D);
+ case GL_INT_VEC4:
+ vector4D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
+ vector4D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
+ vector4D.setZ(static_cast<GLint>(valueArray.at(2).toInt()));
+ vector4D.setW(static_cast<GLint>(valueArray.at(3).toInt()));
+ return QVariant(vector4D);
+ case GL_BOOL:
+ return QVariant(static_cast<GLboolean>(valueArray.first().toBool()));
+ case GL_BOOL_VEC2:
+ vector2D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
+ vector2D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
+ return QVariant(vector2D);
+ case GL_BOOL_VEC3:
+ vector3D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
+ vector3D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
+ vector3D.setZ(static_cast<GLboolean>(valueArray.at(2).toBool()));
+ return QVariant(vector3D);
+ case GL_BOOL_VEC4:
+ vector4D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
+ vector4D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
+ vector4D.setZ(static_cast<GLboolean>(valueArray.at(2).toBool()));
+ vector4D.setW(static_cast<GLboolean>(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<GLfloat>(valueArray.at(0).toDouble());
+ dataMat2[1] = static_cast<GLfloat>(valueArray.at(2).toDouble());
+ dataMat2[2] = static_cast<GLfloat>(valueArray.at(1).toDouble());
+ dataMat2[3] = static_cast<GLfloat>(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<GLfloat>(valueArray.at(0).toDouble());
+ dataMat3[1] = static_cast<GLfloat>(valueArray.at(3).toDouble());
+ dataMat3[2] = static_cast<GLfloat>(valueArray.at(6).toDouble());
+ dataMat3[3] = static_cast<GLfloat>(valueArray.at(1).toDouble());
+ dataMat3[4] = static_cast<GLfloat>(valueArray.at(4).toDouble());
+ dataMat3[5] = static_cast<GLfloat>(valueArray.at(7).toDouble());
+ dataMat3[6] = static_cast<GLfloat>(valueArray.at(2).toDouble());
+ dataMat3[7] = static_cast<GLfloat>(valueArray.at(5).toDouble());
+ dataMat3[8] = static_cast<GLfloat>(valueArray.at(8).toDouble());
+ return QVariant::fromValue(QMatrix3x3(dataMat3.constData()));
+ case GL_FLOAT_MAT4:
+ //Matrix4x4 is Column Major ordering
+ return QVariant(QMatrix4x4(static_cast<GLfloat>(valueArray.at(0).toDouble()),
+ static_cast<GLfloat>(valueArray.at(1).toDouble()),
+ static_cast<GLfloat>(valueArray.at(2).toDouble()),
+ static_cast<GLfloat>(valueArray.at(3).toDouble()),
+ static_cast<GLfloat>(valueArray.at(4).toDouble()),
+ static_cast<GLfloat>(valueArray.at(5).toDouble()),
+ static_cast<GLfloat>(valueArray.at(6).toDouble()),
+ static_cast<GLfloat>(valueArray.at(7).toDouble()),
+ static_cast<GLfloat>(valueArray.at(8).toDouble()),
+ static_cast<GLfloat>(valueArray.at(9).toDouble()),
+ static_cast<GLfloat>(valueArray.at(10).toDouble()),
+ static_cast<GLfloat>(valueArray.at(11).toDouble()),
+ static_cast<GLfloat>(valueArray.at(12).toDouble()),
+ static_cast<GLfloat>(valueArray.at(13).toDouble()),
+ static_cast<GLfloat>(valueArray.at(14).toDouble()),
+ static_cast<GLfloat>(valueArray.at(15).toDouble())));
+ case GL_SAMPLER_2D:
+ return QVariant(valueArray.at(0).toString());
+ }
+ }
+ return QVariant();
+}
+
+QAttribute::VertexBaseType GLTFIO::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(GLTFIOLog, "unsupported accessor type %d", componentType);
+ return QAttribute::Float;
+}
+
+uint GLTFIO::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 *GLTFIO::buildStateEnable(int state)
+{
+ int type = 0;
+ //By calling buildState with QJsonValue(), a Render State with
+ //default values is created.
+
+ if (state == GL_BLEND) {
+ //It doesn't make sense to handle this state alone
+ return nullptr;
+ }
+
+ if (state == GL_CULL_FACE) {
+ return buildState(QStringLiteral("cullFace"), QJsonValue(), type);
+ }
+
+ if (state == GL_DEPTH_TEST) {
+ return buildState(QStringLiteral("depthFunc"), QJsonValue(), type);
+ }
+
+ if (state == GL_POLYGON_OFFSET_FILL) {
+ return buildState(QStringLiteral("polygonOffset"), QJsonValue(), type);
+ }
+
+ if (state == GL_SAMPLE_ALPHA_TO_COVERAGE) {
+ return new QAlphaCoverage();
+ }
+
+ if (state == GL_SCISSOR_TEST) {
+ return buildState(QStringLiteral("scissor"), QJsonValue(), type);
+ }
+
+ qCWarning(GLTFIOLog, "unsupported render state: %d", state);
+
+ return nullptr;
+}
+
+QRenderState* GLTFIO::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(GLTFIOLog, "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));
+ 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")) {
+ //TODO: support render state depthRange
+ qCWarning(GLTFIOLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
+ return nullptr;
+ }
+
+ 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(GLTFIOLog, "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;
+ }
+
+ qCWarning(GLTFIOLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
+ return nullptr;
+}
+
+} // namespace Qt3DRender
+
+QT_END_NAMESPACE
+
+#include "moc_gltfio.cpp"