summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Krus <mike.krus@kdab.com>2021-06-15 11:32:49 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-06-20 19:26:05 +0000
commite54993f74193141f5ed786964807d1eced05275d (patch)
tree334c7061745246fdac4d6aa4a7d5fc3313480254
parent162e720f94ade1493abf1b0bfe953312b94c064c (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.txt1
-rw-r--r--src/plugins/geometryloaders/geometryloaders.pro1
-rw-r--r--src/plugins/geometryloaders/gltf/CMakeLists.txt24
-rw-r--r--src/plugins/geometryloaders/gltf/gltf.json3
-rw-r--r--src/plugins/geometryloaders/gltf/gltf.pro16
-rw-r--r--src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp628
-rw-r--r--src/plugins/geometryloaders/gltf/gltfgeometryloader.h194
-rw-r--r--src/plugins/geometryloaders/gltf/main.cpp71
-rw-r--r--tests/auto/render/geometryloaders/CMakeLists.txt1
-rw-r--r--tests/auto/render/geometryloaders/cube.gltf263
-rw-r--r--tests/auto/render/geometryloaders/geometryloaders.qrc1
-rw-r--r--tests/auto/render/geometryloaders/tst_geometryloaders.cpp43
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()
{