| /**************************************************************************** |
| ** |
| ** 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 ¶meterName) |
| { |
| 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" |