diff options
author | Mike Krus <mike.krus@kdab.com> | 2021-06-15 11:32:49 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-06-20 19:26:05 +0000 |
commit | e54993f74193141f5ed786964807d1eced05275d (patch) | |
tree | 334c7061745246fdac4d6aa4a7d5fc3313480254 | |
parent | 162e720f94ade1493abf1b0bfe953312b94c064c (diff) |
Partially Revert "Remove custom gltf tool"
In b9994cd88925ca012d66e52d033cc9a3a909fc7a, we removed the tool and the parser.
This restores the parser, but the tool was unmaintained.
Change-Id: I168e720b7fdf65aafebb9652933d8093f5449bdc
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
(cherry picked from commit 3c02825fcedbdb0983775d0522af9c851be6c0cd)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/plugins/geometryloaders/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/plugins/geometryloaders/geometryloaders.pro | 1 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/gltf.json | 3 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/gltf.pro | 16 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp | 628 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/gltfgeometryloader.h | 194 | ||||
-rw-r--r-- | src/plugins/geometryloaders/gltf/main.cpp | 71 | ||||
-rw-r--r-- | tests/auto/render/geometryloaders/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/render/geometryloaders/cube.gltf | 263 | ||||
-rw-r--r-- | tests/auto/render/geometryloaders/geometryloaders.qrc | 1 | ||||
-rw-r--r-- | tests/auto/render/geometryloaders/tst_geometryloaders.cpp | 43 |
12 files changed, 1246 insertions, 0 deletions
diff --git a/src/plugins/geometryloaders/CMakeLists.txt b/src/plugins/geometryloaders/CMakeLists.txt index 91944f948..d2ccc5ae5 100644 --- a/src/plugins/geometryloaders/CMakeLists.txt +++ b/src/plugins/geometryloaders/CMakeLists.txt @@ -8,6 +8,7 @@ qt_feature_module_begin( include(configure.cmake) qt_feature_module_end(NO_MODULE) +add_subdirectory(gltf) if(QT_FEATURE_regularexpression) add_subdirectory(default) endif() diff --git a/src/plugins/geometryloaders/geometryloaders.pro b/src/plugins/geometryloaders/geometryloaders.pro index c462493a4..764c615da 100644 --- a/src/plugins/geometryloaders/geometryloaders.pro +++ b/src/plugins/geometryloaders/geometryloaders.pro @@ -1,3 +1,4 @@ TEMPLATE = subdirs qtConfig(regularexpression) : SUBDIRS += default +SUBDIRS += gltf qtConfig(qt3d-fbxsdk) : SUBDIRS += fbx diff --git a/src/plugins/geometryloaders/gltf/CMakeLists.txt b/src/plugins/geometryloaders/gltf/CMakeLists.txt new file mode 100644 index 000000000..aa8f80c19 --- /dev/null +++ b/src/plugins/geometryloaders/gltf/CMakeLists.txt @@ -0,0 +1,24 @@ +# Generated from gltf.pro. + +##################################################################### +## GLTFGeometryLoaderPlugin Plugin: +##################################################################### + +qt_internal_add_plugin(GLTFGeometryLoaderPlugin + OUTPUT_NAME gltfgeometryloader + TYPE geometryloaders + SOURCES + gltfgeometryloader.cpp gltfgeometryloader.h + main.cpp + PUBLIC_LIBRARIES + Qt::3DCore + Qt::3DCorePrivate + Qt::3DRender + Qt::3DRenderPrivate + Qt::Core + Qt::CorePrivate + Qt::Gui +) + +#### Keys ignored in scope 1:.:.:gltf.pro:<TRUE>: +# DISTFILES = "gltf.json" diff --git a/src/plugins/geometryloaders/gltf/gltf.json b/src/plugins/geometryloaders/gltf/gltf.json new file mode 100644 index 000000000..9ad97ce1a --- /dev/null +++ b/src/plugins/geometryloaders/gltf/gltf.json @@ -0,0 +1,3 @@ +{ + "Keys": ["gltf", "json", "qgltf"] +} diff --git a/src/plugins/geometryloaders/gltf/gltf.pro b/src/plugins/geometryloaders/gltf/gltf.pro new file mode 100644 index 000000000..815226b4e --- /dev/null +++ b/src/plugins/geometryloaders/gltf/gltf.pro @@ -0,0 +1,16 @@ +TARGET = gltfgeometryloader +QT += core-private 3dcore 3dcore-private 3drender 3drender-private + +HEADERS += \ + gltfgeometryloader.h \ + +SOURCES += \ + main.cpp \ + gltfgeometryloader.cpp \ + +DISTFILES += \ + gltf.json + +PLUGIN_TYPE = geometryloaders +PLUGIN_CLASS_NAME = GLTFGeometryLoaderPlugin +load(qt_plugin) diff --git a/src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp b/src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp new file mode 100644 index 000000000..9f13ca2c6 --- /dev/null +++ b/src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp @@ -0,0 +1,628 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** 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 "gltfgeometryloader.h" + +#include <QtCore/QDir> +#include <QtCore/QJsonArray> +#include <QtCore/QJsonObject> +#include <QtCore/QVersionNumber> + +#include <QOpenGLTexture> + +#include <Qt3DRender/private/renderlogging_p.h> +#include <Qt3DCore/QGeometry> +#include <Qt3DCore/private/qloadgltf_p.h> + +QT_BEGIN_NAMESPACE + +#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature +# define qUtf16PrintableImpl(string) \ + static_cast<const wchar_t*>(static_cast<const void*>(string.utf16())) +#endif + +using namespace Qt3DCore; + +namespace Qt3DRender { + +Q_LOGGING_CATEGORY(GLTFGeometryLoaderLog, "Qt3D.GLTFGeometryLoader", QtWarningMsg) + +#define KEY_ASSET QLatin1String("asset") +#define KEY_ACCESSORS QLatin1String("accessors") +#define KEY_ATTRIBUTES QLatin1String("attributes") +#define KEY_BUFFER QLatin1String("buffer") +#define KEY_BUFFERS QLatin1String("buffers") +#define KEY_BYTE_LENGTH QLatin1String("byteLength") +#define KEY_BYTE_OFFSET QLatin1String("byteOffset") +#define KEY_BYTE_STRIDE QLatin1String("byteStride") +#define KEY_COUNT QLatin1String("count") +#define KEY_INDICES QLatin1String("indices") +#define KEY_MATERIAL QLatin1String("material") +#define KEY_MESHES QLatin1String("meshes") +#define KEY_NAME QLatin1String("name") +#define KEY_PRIMITIVES QLatin1String("primitives") +#define KEY_TARGET QLatin1String("target") +#define KEY_TYPE QLatin1String("type") +#define KEY_URI QLatin1String("uri") +#define KEY_VERSION QLatin1String("version") + +#define KEY_BUFFER_VIEW QLatin1String("bufferView") +#define KEY_BUFFER_VIEWS QLatin1String("bufferViews") +#define KEY_COMPONENT_TYPE QLatin1String("componentType") + +GLTFGeometryLoader::GLTFGeometryLoader() + : m_geometry(nullptr) +{ +} + +GLTFGeometryLoader::~GLTFGeometryLoader() +{ + cleanup(); +} + +QGeometry *GLTFGeometryLoader::geometry() const +{ + return m_geometry; +} + +bool GLTFGeometryLoader::load(QIODevice *ioDev, const QString &subMesh) +{ + Q_UNUSED(subMesh); + + if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) { + qCWarning(GLTFGeometryLoaderLog, "not a JSON document"); + return false; + } + + auto file = qobject_cast<QFile*>(ioDev); + if (file) { + QFileInfo finfo(file->fileName()); + setBasePath(finfo.dir().absolutePath()); + } + + m_mesh = subMesh; + + parse(); + + return true; +} + +GLTFGeometryLoader::BufferData::BufferData() + : length(0) + , data(nullptr) +{ +} + +GLTFGeometryLoader::BufferData::BufferData(const QJsonObject &json) + : length(json.value(KEY_BYTE_LENGTH).toInt()) + , path(json.value(KEY_URI).toString()) + , data(nullptr) +{ +} + +GLTFGeometryLoader::AccessorData::AccessorData() + : bufferViewIndex(0) + , type(QAttribute::Float) + , dataSize(0) + , count(0) + , offset(0) + , stride(0) +{ + +} + +GLTFGeometryLoader::AccessorData::AccessorData(const QJsonObject &json) + : bufferViewName(json.value(KEY_BUFFER_VIEW).toString()) + , bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(-1)) + , 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(); +} + +void GLTFGeometryLoader::setBasePath(const QString &path) +{ + m_basePath = path; +} + +bool GLTFGeometryLoader::setJSON(const QJsonDocument &json ) +{ + if (!json.isObject()) + return false; + + m_json = json; + + cleanup(); + + return true; +} + +QString GLTFGeometryLoader::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("JOINTS"))) + return QAttribute::defaultJointIndicesAttributeName(); + if (semantic.startsWith(QLatin1String("WEIGHTS"))) + return QAttribute::defaultJointWeightsAttributeName(); + + return QString(); +} + +void GLTFGeometryLoader::parse() +{ + // Find the glTF version + const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject(); + const QString versionString = asset.value(KEY_VERSION).toString(); + const auto version = QVersionNumber::fromString(versionString); + switch (version.majorVersion()) { + case 1: + parseGLTF1(); + break; + + case 2: + parseGLTF2(); + break; + + default: + qWarning() << "Unsupported version of glTF" << versionString; + } +} + +void GLTFGeometryLoader::parseGLTF1() +{ + 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 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 && !m_geometry; ++it) { + const QJsonObject &mesh = it.value().toObject(); + if (m_mesh.isEmpty() || + m_mesh.compare(mesh.value(KEY_NAME).toString(), Qt::CaseInsensitive) == 0) + processJSONMesh(it.key(), mesh); + } +} + +void GLTFGeometryLoader::parseGLTF2() +{ + const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray(); + for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it) + processJSONBufferV2(it->toObject()); + + const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray(); + loadBufferDataV2(); + for (auto it = views.begin(), end = views.end(); it != end; ++it) + processJSONBufferViewV2(it->toObject()); + unloadBufferDataV2(); + + const QJsonArray attrs = m_json.object().value(KEY_ACCESSORS).toArray(); + for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) + processJSONAccessorV2(it->toObject()); + + const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray(); + for (auto it = meshes.begin(), end = meshes.end(); it != end && !m_geometry; ++it) { + const QJsonObject &mesh = it->toObject(); + if (m_mesh.isEmpty() || m_mesh.compare(mesh.value(KEY_NAME).toString(), Qt::CaseInsensitive) == 0) + processJSONMeshV2(mesh); + } +} + +void GLTFGeometryLoader::cleanup() +{ + m_geometry = nullptr; + m_gltf1.m_accessorDict.clear(); + m_gltf1.m_buffers.clear(); +} + +void GLTFGeometryLoader::processJSONBuffer(const QString &id, const QJsonObject &json) +{ + // simply cache buffers for lookup by buffer-views + m_gltf1.m_bufferDatas[id] = BufferData(json); +} + +void GLTFGeometryLoader::processJSONBufferV2(const QJsonObject &json) +{ + // simply cache buffers for lookup by buffer-views + m_gltf2.m_bufferDatas.push_back(BufferData(json)); +} + +void GLTFGeometryLoader::processJSONBufferView(const QString &id, const QJsonObject &json) +{ + QString bufName = json.value(KEY_BUFFER).toString(); + const auto it = qAsConst(m_gltf1.m_bufferDatas).find(bufName); + if (Q_UNLIKELY(it == m_gltf1.m_bufferDatas.cend())) { + qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %ls processing view: %ls", + qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id)); + return; + } + const auto &bufferData = *it; + + int target = json.value(KEY_TARGET).toInt(); + + switch (target) { + case GL_ARRAY_BUFFER: + case GL_ELEMENT_ARRAY_BUFFER: + break; + default: + qCWarning(GLTFGeometryLoaderLog, "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(GLTFGeometryLoaderLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset); + } + + const quint64 len = json.value(KEY_BYTE_LENGTH).toInt(); + + QByteArray bytes = bufferData.data->mid(offset, len); + if (Q_UNLIKELY(bytes.count() != int(len))) { + qCWarning(GLTFGeometryLoaderLog, "failed to read sufficient bytes from: %ls for view %ls", + qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id)); + } + + Qt3DCore::QBuffer *b = new Qt3DCore::QBuffer(); + b->setData(bytes); + m_gltf1.m_buffers[id] = b; +} + +void GLTFGeometryLoader::processJSONBufferViewV2(const QJsonObject &json) +{ + const int bufferIndex = json.value(KEY_BUFFER).toInt(); + if (Q_UNLIKELY(bufferIndex) >= m_gltf2.m_bufferDatas.size()) { + qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %d processing view", bufferIndex); + return; + } + const auto bufferData = m_gltf2.m_bufferDatas[bufferIndex]; + + int target = json.value(KEY_TARGET).toInt(); + switch (target) { + case GL_ARRAY_BUFFER: + case GL_ELEMENT_ARRAY_BUFFER: + break; + default: + return; + } + + quint64 offset = 0; + const auto byteOffset = json.value(KEY_BYTE_OFFSET); + if (!byteOffset.isUndefined()) { + offset = byteOffset.toInt(); + qCDebug(GLTFGeometryLoaderLog, "bufferview has offset: %lld", offset); + } + + const quint64 len = json.value(KEY_BYTE_LENGTH).toInt(); + QByteArray bytes = bufferData.data->mid(offset, len); + if (Q_UNLIKELY(bytes.count() != int(len))) { + qCWarning(GLTFGeometryLoaderLog, "failed to read sufficient bytes from: %ls for view", + qUtf16PrintableImpl(bufferData.path)); + } + + auto b = new Qt3DCore::QBuffer; + b->setData(bytes); + m_gltf2.m_buffers.push_back(b); +} + +void GLTFGeometryLoader::processJSONAccessor(const QString &id, const QJsonObject &json) +{ + m_gltf1.m_accessorDict[id] = AccessorData(json); +} + +void GLTFGeometryLoader::processJSONAccessorV2(const QJsonObject &json) +{ + m_gltf2.m_accessors.push_back(AccessorData(json)); +} + +void GLTFGeometryLoader::processJSONMesh(const QString &id, const QJsonObject &json) +{ + const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray(); + for (const QJsonValue &primitiveValue : primitivesArray) { + QJsonObject primitiveObject = primitiveValue.toObject(); + QString material = primitiveObject.value(KEY_MATERIAL).toString(); + + if (Q_UNLIKELY(material.isEmpty())) { + qCWarning(GLTFGeometryLoaderLog, "malformed primitive on %ls, missing material value %ls", + qUtf16PrintableImpl(id), qUtf16PrintableImpl(material)); + continue; + } + + QGeometry *meshGeometry = new QGeometry; + + 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_gltf1.m_accessorDict).find(k); + if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) { + qCWarning(GLTFGeometryLoaderLog, "unknown attribute accessor: %ls on mesh %ls", + qUtf16PrintableImpl(k), qUtf16PrintableImpl(id)); + continue; + } + + const QString attrName = it.key(); + QString attributeName = standardAttributeNameFromSemantic(attrName); + if (attributeName.isEmpty()) + attributeName = attrName; + + //Get buffer handle for accessor + Qt3DCore::QBuffer *buffer = m_gltf1.m_buffers.value(accessorIt->bufferViewName, nullptr); + if (Q_UNLIKELY(!buffer)) { + qCWarning(GLTFGeometryLoaderLog, "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_gltf1.m_accessorDict).find(k); + if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) { + qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %ls on mesh %ls", + qUtf16PrintableImpl(k), qUtf16PrintableImpl(id)); + } else { + //Get buffer handle for accessor + Qt3DCore::QBuffer *buffer = m_gltf1.m_buffers.value(accessorIt->bufferViewName, nullptr); + if (Q_UNLIKELY(!buffer)) { + qCWarning(GLTFGeometryLoaderLog, "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 + + m_geometry = meshGeometry; + + break; + } // of primitives iteration +} + +void GLTFGeometryLoader::processJSONMeshV2(const QJsonObject &json) +{ + const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray(); + for (const QJsonValue &primitiveValue : primitivesArray) { + QJsonObject primitiveObject = primitiveValue.toObject(); + + QGeometry *meshGeometry = new QGeometry; + + const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject(); + for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) { + const int accessorIndex = it.value().toInt(); + if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) { + qCWarning(GLTFGeometryLoaderLog, "unknown attribute accessor: %d on mesh %ls", + accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString())); + continue; + } + const auto &accessor = m_gltf2.m_accessors[accessorIndex]; + + const QString attrName = it.key(); + QString attributeName = standardAttributeNameFromSemantic(attrName); + if (attributeName.isEmpty()) + attributeName = attrName; + + // Get buffer handle for accessor + if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) { + qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls", + accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString())); + continue; + } + Qt3DCore::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex]; + + QAttribute *attribute = new QAttribute(buffer, + attributeName, + accessor.type, + accessor.dataSize, + accessor.count, + accessor.offset, + accessor.stride); + attribute->setAttributeType(QAttribute::VertexAttribute); + meshGeometry->addAttribute(attribute); + } + + const auto indices = primitiveObject.value(KEY_INDICES); + if (!indices.isUndefined()) { + const int accessorIndex = indices.toInt(); + if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) { + qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %d on mesh %ls", + accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString())); + } else { + const auto &accessor = m_gltf2.m_accessors[accessorIndex]; + + //Get buffer handle for accessor + if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) { + qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls", + accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString())); + continue; + } + Qt3DCore::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex]; + + QAttribute *attribute = new QAttribute(buffer, + accessor.type, + accessor.dataSize, + accessor.count, + accessor.offset, + accessor.stride); + attribute->setAttributeType(QAttribute::IndexAttribute); + meshGeometry->addAttribute(attribute); + } + } // of has indices + + m_geometry = meshGeometry; + + break; + } // of primitives iteration +} + +void GLTFGeometryLoader::loadBufferData() +{ + for (auto &bufferData : m_gltf1.m_bufferDatas) { + if (!bufferData.data) { + bufferData.data = new QByteArray(resolveLocalData(bufferData.path)); + } + } +} + +void GLTFGeometryLoader::unloadBufferData() +{ + for (const auto &bufferData : qAsConst(m_gltf1.m_bufferDatas)) { + QByteArray *data = bufferData.data; + delete data; + } +} + +void GLTFGeometryLoader::loadBufferDataV2() +{ + for (auto &bufferData : m_gltf2.m_bufferDatas) { + if (!bufferData.data) + bufferData.data = new QByteArray(resolveLocalData(bufferData.path)); + } +} + +void GLTFGeometryLoader::unloadBufferDataV2() +{ + for (const auto &bufferData : qAsConst(m_gltf2.m_bufferDatas)) { + QByteArray *data = bufferData.data; + delete data; + } +} + +QByteArray GLTFGeometryLoader::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(); +} + +QAttribute::VertexBaseType GLTFGeometryLoader::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(GLTFGeometryLoaderLog, "unsupported accessor type %d", componentType); + return QAttribute::Float; +} + +uint GLTFGeometryLoader::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; +} + +} // namespace Qt3DRender + +QT_END_NAMESPACE diff --git a/src/plugins/geometryloaders/gltf/gltfgeometryloader.h b/src/plugins/geometryloaders/gltf/gltfgeometryloader.h new file mode 100644 index 000000000..36ac0f174 --- /dev/null +++ b/src/plugins/geometryloaders/gltf/gltfgeometryloader.h @@ -0,0 +1,194 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** 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$ +** +****************************************************************************/ + +#ifndef GLTFGEOMETRYLOADER_H +#define GLTFGEOMETRYLOADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QJsonDocument> + +#include <Qt3DRender/private/qgeometryloaderinterface_p.h> +#include <Qt3DCore/qattribute.h> +#include <Qt3DCore/qbuffer.h> + +#include <private/qlocale_tools_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DCore { +class QGeometry; +} + +namespace Qt3DRender { + +#define GLTFGEOMETRYLOADER_EXT QLatin1String("gltf") +#define JSONGEOMETRYLOADER_EXT QLatin1String("json") +#define QGLTFGEOMETRYLOADER_EXT QLatin1String("qgltf") + +class QCamera; +class QCameraLens; +class QMaterial; +class QShaderProgram; +class QEffect; +class QAbstractTexture; +class QRenderState; +class QTechnique; +class QParameter; +class QGeometryRenderer; + +class GLTFGeometryLoader : public QGeometryLoaderInterface +{ + class BufferData + { + public: + BufferData(); + explicit BufferData(const QJsonObject &json); + + quint64 length; + QString path; + QByteArray *data; + // type if ever useful + }; + + class ParameterData + { + public: + ParameterData(); + explicit ParameterData(const QJsonObject &json); + + QString semantic; + int type; + }; + + class AccessorData + { + public: + AccessorData(); + explicit AccessorData(const QJsonObject &json); + + QString bufferViewName; + int bufferViewIndex; + Qt3DCore::QAttribute::VertexBaseType type; + uint dataSize; + int count; + int offset; + int stride; + }; + + struct Gltf1 + { + QHash<QString, AccessorData> m_accessorDict; + QHash<QString, BufferData> m_bufferDatas; + QHash<QString, Qt3DCore::QBuffer*> m_buffers; + }; + + struct Gltf2 + { + QVector<BufferData> m_bufferDatas; + QVector<Qt3DCore::QBuffer*> m_buffers; + QVector<AccessorData> m_accessors; + }; + + Q_OBJECT +public: + GLTFGeometryLoader(); + ~GLTFGeometryLoader(); + + Qt3DCore::QGeometry *geometry() const final; + + bool load(QIODevice *ioDev, const QString &subMesh = QString()) final; + +protected: + void setBasePath(const QString &path); + bool setJSON(const QJsonDocument &json); + + static QString standardAttributeNameFromSemantic(const QString &semantic); + + void parse(); + void parseGLTF1(); + void parseGLTF2(); + void cleanup(); + + void processJSONBuffer(const QString &id, const QJsonObject &json); + void processJSONBufferView(const QString &id, const QJsonObject &json); + void processJSONAccessor(const QString &id, const QJsonObject &json); + void processJSONMesh(const QString &id, const QJsonObject &json); + + void loadBufferData(); + void unloadBufferData(); + + void processJSONBufferV2(const QJsonObject &json); + void processJSONBufferViewV2(const QJsonObject &json); + void processJSONAccessorV2(const QJsonObject &json); + void processJSONMeshV2(const QJsonObject &json); + + void loadBufferDataV2(); + void unloadBufferDataV2(); + + QByteArray resolveLocalData(const QString &path) const; + + static Qt3DCore::QAttribute::VertexBaseType accessorTypeFromJSON(int componentType); + static uint accessorDataSizeFromJson(const QString &type); + +private: + QJsonDocument m_json; + QString m_basePath; + QString m_mesh; + + Gltf1 m_gltf1; + Gltf2 m_gltf2; + + Qt3DCore::QGeometry *m_geometry; +}; + +} // namespace Qt3DRender + +QT_END_NAMESPACE + +#endif // GLTFGEOMETRYLOADER_H diff --git a/src/plugins/geometryloaders/gltf/main.cpp b/src/plugins/geometryloaders/gltf/main.cpp new file mode 100644 index 000000000..ee27e1325 --- /dev/null +++ b/src/plugins/geometryloaders/gltf/main.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 <Qt3DRender/private/qgeometryloaderfactory_p.h> + +#include "gltfgeometryloader.h" + +QT_BEGIN_NAMESPACE + +class GLTFGeometryLoaderPlugin : public Qt3DRender::QGeometryLoaderFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QGeometryLoaderFactory_iid FILE "gltf.json") +public: + + QStringList keys() const override + { + return QStringList() << GLTFGEOMETRYLOADER_EXT + << JSONGEOMETRYLOADER_EXT + << QGLTFGEOMETRYLOADER_EXT; + } + + Qt3DRender::QGeometryLoaderInterface *create(const QString &ext) override + { + if ((ext.compare(GLTFGEOMETRYLOADER_EXT, Qt::CaseInsensitive) == 0) || + (ext.compare(JSONGEOMETRYLOADER_EXT, Qt::CaseInsensitive) == 0) || + (ext.compare(QGLTFGEOMETRYLOADER_EXT, Qt::CaseInsensitive) == 0)) + return new Qt3DRender::GLTFGeometryLoader; + return nullptr; + } +}; + +QT_END_NAMESPACE + +#include "main.moc" diff --git a/tests/auto/render/geometryloaders/CMakeLists.txt b/tests/auto/render/geometryloaders/CMakeLists.txt index 1897c27b1..251c1ae03 100644 --- a/tests/auto/render/geometryloaders/CMakeLists.txt +++ b/tests/auto/render/geometryloaders/CMakeLists.txt @@ -19,6 +19,7 @@ qt_internal_add_test(tst_geometryloaders # Resources: set(geometryloaders_resource_files "cube.fbx" + "cube.gltf" "cube.obj" "cube.ply" "cube.stl" diff --git a/tests/auto/render/geometryloaders/cube.gltf b/tests/auto/render/geometryloaders/cube.gltf new file mode 100644 index 000000000..c60ae4ad9 --- /dev/null +++ b/tests/auto/render/geometryloaders/cube.gltf @@ -0,0 +1,263 @@ +{ + "accessors": { + "accessor_index_0": { + "bufferView": "bufferView_1", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "min": [ + 0 + ], + "max": [ + 23 + ] + }, + "accessor_position": { + "bufferView": "bufferView_0", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1.0000009536743164 + ], + "type": "VEC3" + }, + "accessor_normal": { + "bufferView": "bufferView_0", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3", + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ] + } + }, + "asset": { + "generator": "OBJ2GLTF", + "premultipliedAlpha": true, + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "cube_buffer": { + "type": "arraybuffer", + "byteLength": 648, + "uri": "cube_buffer.bin" + } + }, + "bufferViews": { + "bufferView_0": { + "buffer": "cube_buffer", + "byteLength": 576, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_1": { + "buffer": "cube_buffer", + "byteLength": 72, + "byteOffset": 576, + "target": 34963 + } + }, + "images": {}, + "materials": { + "material_czmDefaultMat": { + "name": "czmDefaultMat", + "extensions": {}, + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0, + 0, + 0, + 1 + ], + "shininess": 0, + "transparency": 1 + }, + "technique": "technique0" + } + }, + "meshes": { + "mesh_cube": { + "name": "cube", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_position", + "NORMAL": "accessor_normal" + }, + "indices": "accessor_index_0", + "material": "material_czmDefaultMat", + "mode": 4 + } + ] + } + }, + "nodes": { + "rootNode": { + "children": [], + "meshes": [ + "mesh_cube" + ], + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ] + } + }, + "samplers": {}, + "scene": "scene_cube", + "scenes": { + "scene_cube": { + "nodes": [ + "rootNode" + ] + } + }, + "textures": {}, + "extensionsUsed": [], + "animations": {}, + "cameras": {}, + "techniques": { + "technique0": { + "attributes": { + "a_position": "position", + "a_normal": "normal" + }, + "parameters": { + "modelViewMatrix": { + "semantic": "MODELVIEW", + "type": 35676 + }, + "projectionMatrix": { + "semantic": "PROJECTION", + "type": 35676 + }, + "normalMatrix": { + "semantic": "MODELVIEWINVERSETRANSPOSE", + "type": 35675 + }, + "ambient": { + "type": 35666 + }, + "diffuse": { + "type": 35666 + }, + "emission": { + "type": 35666 + }, + "specular": { + "type": 35666 + }, + "shininess": { + "type": 5126 + }, + "transparency": { + "type": 5126 + }, + "position": { + "semantic": "POSITION", + "type": 35665 + }, + "normal": { + "semantic": "NORMAL", + "type": 35665 + } + }, + "program": "program0", + "states": { + "enable": [ + 2884, + 2929 + ] + }, + "uniforms": { + "u_modelViewMatrix": "modelViewMatrix", + "u_projectionMatrix": "projectionMatrix", + "u_normalMatrix": "normalMatrix", + "u_ambient": "ambient", + "u_diffuse": "diffuse", + "u_emission": "emission", + "u_specular": "specular", + "u_shininess": "shininess", + "u_transparency": "transparency" + } + } + }, + "programs": { + "program0": { + "attributes": [ + "a_position", + "a_normal" + ], + "fragmentShader": "fragmentShader0", + "vertexShader": "vertexShader0" + } + }, + "shaders": { + "vertexShader0": { + "type": 35633, + "uri": "vertexShader0.glsl" + }, + "fragmentShader0": { + "type": 35632, + "uri": "fragmentShader0.glsl" + } + }, + "skins": {}, + "extensions": {} +} diff --git a/tests/auto/render/geometryloaders/geometryloaders.qrc b/tests/auto/render/geometryloaders/geometryloaders.qrc index a15332941..8f98f5a14 100644 --- a/tests/auto/render/geometryloaders/geometryloaders.qrc +++ b/tests/auto/render/geometryloaders/geometryloaders.qrc @@ -4,6 +4,7 @@ <file>cube2.obj</file> <file>cube.ply</file> <file>cube.stl</file> + <file>cube.gltf</file> <file>cube_buffer.bin</file> <file>cube.fbx</file> </qresource> diff --git a/tests/auto/render/geometryloaders/tst_geometryloaders.cpp b/tests/auto/render/geometryloaders/tst_geometryloaders.cpp index e0141a51d..7fc914cda 100644 --- a/tests/auto/render/geometryloaders/tst_geometryloaders.cpp +++ b/tests/auto/render/geometryloaders/tst_geometryloaders.cpp @@ -65,6 +65,7 @@ private Q_SLOTS: void testOBJLoader(); void testPLYLoader(); void testSTLLoader(); + void testGLTFLoader(); #ifdef QT_3DGEOMETRYLOADERS_FBX void testFBXLoader(); #endif @@ -193,6 +194,48 @@ void tst_geometryloaders::testSTLLoader() file.close(); } +void tst_geometryloaders::testGLTFLoader() +{ + QScopedPointer<QGeometryLoaderInterface> loader; + loader.reset(qLoadPlugin<QGeometryLoaderInterface, QGeometryLoaderFactory>(geometryLoader(), QStringLiteral("gltf"))); + QVERIFY(loader); + if (!loader) + return; + + QFile file(QStringLiteral(":/cube.gltf")); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug("Could not open test file for reading"); + return; + } + + bool loaded = loader->load(&file, QStringLiteral("Cube")); + QVERIFY(loaded); + if (!loaded) + return; + + QGeometry *geometry = loader->geometry(); + QVERIFY(geometry); + if (!geometry) + return; + + QCOMPARE(geometry->attributes().count(), 3); + for (QAttribute *attr : geometry->attributes()) { + switch (attr->attributeType()) { + case QAttribute::IndexAttribute: + QCOMPARE(attr->count(), 36u); + break; + case QAttribute::VertexAttribute: + QCOMPARE(attr->count(), 24u); + break; + default: + Q_UNREACHABLE(); + break; + } + } + + file.close(); +} + #ifdef QT_3DGEOMETRYLOADERS_FBX void tst_geometryloaders::testFBXLoader() { |