1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "gltfgeometryloader.h"
5
6#include <QtCore/QDir>
7#include <QtCore/QJsonArray>
8#include <QtCore/QJsonObject>
9#include <QtCore/QVersionNumber>
10
11#include <QOpenGLTexture>
12
13#include <Qt3DRender/private/renderlogging_p.h>
14#include <Qt3DCore/QGeometry>
15#include <Qt3DCore/private/qloadgltf_p.h>
16
17QT_BEGIN_NAMESPACE
18
19#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature
20# define qUtf16PrintableImpl(string) \
21 static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
22#endif
23
24using namespace Qt3DCore;
25
26namespace Qt3DRender {
27
28Q_LOGGING_CATEGORY(GLTFGeometryLoaderLog, "Qt3D.GLTFGeometryLoader", QtWarningMsg)
29
30#define KEY_ASSET QLatin1String("asset")
31#define KEY_ACCESSORS QLatin1String("accessors")
32#define KEY_ATTRIBUTES QLatin1String("attributes")
33#define KEY_BUFFER QLatin1String("buffer")
34#define KEY_BUFFERS QLatin1String("buffers")
35#define KEY_BYTE_LENGTH QLatin1String("byteLength")
36#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
37#define KEY_BYTE_STRIDE QLatin1String("byteStride")
38#define KEY_COUNT QLatin1String("count")
39#define KEY_INDICES QLatin1String("indices")
40#define KEY_MATERIAL QLatin1String("material")
41#define KEY_MESHES QLatin1String("meshes")
42#define KEY_NAME QLatin1String("name")
43#define KEY_PRIMITIVES QLatin1String("primitives")
44#define KEY_TARGET QLatin1String("target")
45#define KEY_TYPE QLatin1String("type")
46#define KEY_URI QLatin1String("uri")
47#define KEY_VERSION QLatin1String("version")
48
49#define KEY_BUFFER_VIEW QLatin1String("bufferView")
50#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
51#define KEY_COMPONENT_TYPE QLatin1String("componentType")
52
53GLTFGeometryLoader::GLTFGeometryLoader()
54 : m_geometry(nullptr)
55{
56}
57
58GLTFGeometryLoader::~GLTFGeometryLoader()
59{
60 cleanup();
61}
62
63QGeometry *GLTFGeometryLoader::geometry() const
64{
65 return m_geometry;
66}
67
68bool GLTFGeometryLoader::load(QIODevice *ioDev, const QString &subMesh)
69{
70 Q_UNUSED(subMesh);
71
72 if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) {
73 qCWarning(GLTFGeometryLoaderLog, "not a JSON document");
74 return false;
75 }
76
77 auto file = qobject_cast<QFile*>(object: ioDev);
78 if (file) {
79 QFileInfo finfo(file->fileName());
80 setBasePath(finfo.dir().absolutePath());
81 }
82
83 m_mesh = subMesh;
84
85 parse();
86
87 return true;
88}
89
90GLTFGeometryLoader::BufferData::BufferData()
91 : length(0)
92 , data(nullptr)
93{
94}
95
96GLTFGeometryLoader::BufferData::BufferData(const QJsonObject &json)
97 : length(json.value(KEY_BYTE_LENGTH).toInt())
98 , path(json.value(KEY_URI).toString())
99 , data(nullptr)
100{
101}
102
103GLTFGeometryLoader::AccessorData::AccessorData()
104 : bufferViewIndex(0)
105 , type(QAttribute::Float)
106 , dataSize(0)
107 , count(0)
108 , offset(0)
109 , stride(0)
110{
111
112}
113
114GLTFGeometryLoader::AccessorData::AccessorData(const QJsonObject &json)
115 : bufferViewName(json.value(KEY_BUFFER_VIEW).toString())
116 , bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(defaultValue: -1))
117 , type(accessorTypeFromJSON(componentType: json.value(KEY_COMPONENT_TYPE).toInt()))
118 , dataSize(accessorDataSizeFromJson(type: json.value(KEY_TYPE).toString()))
119 , count(json.value(KEY_COUNT).toInt())
120 , offset(0)
121 , stride(0)
122{
123 const auto byteOffset = json.value(KEY_BYTE_OFFSET);
124 if (!byteOffset.isUndefined())
125 offset = byteOffset.toInt();
126 const auto byteStride = json.value(KEY_BYTE_STRIDE);
127 if (!byteStride.isUndefined())
128 stride = byteStride.toInt();
129}
130
131void GLTFGeometryLoader::setBasePath(const QString &path)
132{
133 m_basePath = path;
134}
135
136bool GLTFGeometryLoader::setJSON(const QJsonDocument &json )
137{
138 if (!json.isObject())
139 return false;
140
141 m_json = json;
142
143 cleanup();
144
145 return true;
146}
147
148QString GLTFGeometryLoader::standardAttributeNameFromSemantic(const QString &semantic)
149{
150 //Standard Attributes
151 if (semantic.startsWith(s: QLatin1String("POSITION")))
152 return QAttribute::defaultPositionAttributeName();
153 if (semantic.startsWith(s: QLatin1String("NORMAL")))
154 return QAttribute::defaultNormalAttributeName();
155 if (semantic.startsWith(s: QLatin1String("TEXCOORD")))
156 return QAttribute::defaultTextureCoordinateAttributeName();
157 if (semantic.startsWith(s: QLatin1String("COLOR")))
158 return QAttribute::defaultColorAttributeName();
159 if (semantic.startsWith(s: QLatin1String("TANGENT")))
160 return QAttribute::defaultTangentAttributeName();
161 if (semantic.startsWith(s: QLatin1String("JOINTS")))
162 return QAttribute::defaultJointIndicesAttributeName();
163 if (semantic.startsWith(s: QLatin1String("WEIGHTS")))
164 return QAttribute::defaultJointWeightsAttributeName();
165
166 return QString();
167}
168
169void GLTFGeometryLoader::parse()
170{
171 // Find the glTF version
172 const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject();
173 const QString versionString = asset.value(KEY_VERSION).toString();
174 const auto version = QVersionNumber::fromString(string: versionString);
175 switch (version.majorVersion()) {
176 case 1:
177 parseGLTF1();
178 break;
179
180 case 2:
181 parseGLTF2();
182 break;
183
184 default:
185 qWarning() << "Unsupported version of glTF" << versionString;
186 }
187}
188
189void GLTFGeometryLoader::parseGLTF1()
190{
191 const QJsonObject buffers = m_json.object().value(KEY_BUFFERS).toObject();
192 for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it)
193 processJSONBuffer(id: it.key(), json: it.value().toObject());
194
195 const QJsonObject views = m_json.object().value(KEY_BUFFER_VIEWS).toObject();
196 loadBufferData();
197 for (auto it = views.begin(), end = views.end(); it != end; ++it)
198 processJSONBufferView(id: it.key(), json: it.value().toObject());
199 unloadBufferData();
200
201 const QJsonObject attrs = m_json.object().value(KEY_ACCESSORS).toObject();
202 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
203 processJSONAccessor(id: it.key(), json: it.value().toObject());
204
205 const QJsonObject meshes = m_json.object().value(KEY_MESHES).toObject();
206 for (auto it = meshes.begin(), end = meshes.end(); it != end && !m_geometry; ++it) {
207 const QJsonObject &mesh = it.value().toObject();
208 if (m_mesh.isEmpty() ||
209 m_mesh.compare(s: mesh.value(KEY_NAME).toString(), cs: Qt::CaseInsensitive) == 0)
210 processJSONMesh(id: it.key(), json: mesh);
211 }
212}
213
214void GLTFGeometryLoader::parseGLTF2()
215{
216 const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
217 for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it)
218 processJSONBufferV2(json: it->toObject());
219
220 const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
221 loadBufferDataV2();
222 for (auto it = views.begin(), end = views.end(); it != end; ++it)
223 processJSONBufferViewV2(json: it->toObject());
224 unloadBufferDataV2();
225
226 const QJsonArray attrs = m_json.object().value(KEY_ACCESSORS).toArray();
227 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
228 processJSONAccessorV2(json: it->toObject());
229
230 const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray();
231 for (auto it = meshes.begin(), end = meshes.end(); it != end && !m_geometry; ++it) {
232 const QJsonObject &mesh = it->toObject();
233 if (m_mesh.isEmpty() || m_mesh.compare(s: mesh.value(KEY_NAME).toString(), cs: Qt::CaseInsensitive) == 0)
234 processJSONMeshV2(json: mesh);
235 }
236}
237
238void GLTFGeometryLoader::cleanup()
239{
240 m_geometry = nullptr;
241 m_gltf1.m_accessorDict.clear();
242 m_gltf1.m_buffers.clear();
243}
244
245void GLTFGeometryLoader::processJSONBuffer(const QString &id, const QJsonObject &json)
246{
247 // simply cache buffers for lookup by buffer-views
248 m_gltf1.m_bufferDatas[id] = BufferData(json);
249}
250
251void GLTFGeometryLoader::processJSONBufferV2(const QJsonObject &json)
252{
253 // simply cache buffers for lookup by buffer-views
254 m_gltf2.m_bufferDatas.push_back(t: BufferData(json));
255}
256
257void GLTFGeometryLoader::processJSONBufferView(const QString &id, const QJsonObject &json)
258{
259 QString bufName = json.value(KEY_BUFFER).toString();
260 const auto it = std::as_const(t&: m_gltf1.m_bufferDatas).find(key: bufName);
261 if (Q_UNLIKELY(it == m_gltf1.m_bufferDatas.cend())) {
262 qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %ls processing view: %ls",
263 qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id));
264 return;
265 }
266 const auto &bufferData = *it;
267
268 int target = json.value(KEY_TARGET).toInt();
269
270 switch (target) {
271 case GL_ARRAY_BUFFER:
272 case GL_ELEMENT_ARRAY_BUFFER:
273 break;
274 default:
275 qCWarning(GLTFGeometryLoaderLog, "buffer %ls unsupported target: %d",
276 qUtf16PrintableImpl(id), target);
277 return;
278 }
279
280 quint64 offset = 0;
281 const auto byteOffset = json.value(KEY_BYTE_OFFSET);
282 if (!byteOffset.isUndefined()) {
283 offset = byteOffset.toInt();
284 qCDebug(GLTFGeometryLoaderLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset);
285 }
286
287 const quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
288
289 QByteArray bytes = bufferData.data->mid(index: offset, len);
290 if (Q_UNLIKELY(bytes.size() != qsizetype(len))) {
291 qCWarning(GLTFGeometryLoaderLog, "failed to read sufficient bytes from: %ls for view %ls",
292 qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id));
293 }
294
295 Qt3DCore::QBuffer *b = new Qt3DCore::QBuffer();
296 b->setData(bytes);
297 m_gltf1.m_buffers[id] = b;
298}
299
300void GLTFGeometryLoader::processJSONBufferViewV2(const QJsonObject &json)
301{
302 const int bufferIndex = json.value(KEY_BUFFER).toInt();
303 if (Q_UNLIKELY(bufferIndex) >= m_gltf2.m_bufferDatas.size()) {
304 qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %d processing view", bufferIndex);
305 return;
306 }
307 const auto bufferData = m_gltf2.m_bufferDatas[bufferIndex];
308
309 quint64 offset = 0;
310 const auto byteOffset = json.value(KEY_BYTE_OFFSET);
311 if (!byteOffset.isUndefined()) {
312 offset = byteOffset.toInt();
313 qCDebug(GLTFGeometryLoaderLog, "bufferview has offset: %lld", offset);
314 }
315
316 const quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
317 QByteArray bytes = bufferData.data->mid(index: offset, len);
318 if (Q_UNLIKELY(bytes.size() != qsizetype(len))) {
319 qCWarning(GLTFGeometryLoaderLog, "failed to read sufficient bytes from: %ls for view",
320 qUtf16PrintableImpl(bufferData.path));
321 }
322
323 auto b = new Qt3DCore::QBuffer;
324 b->setData(bytes);
325 m_gltf2.m_buffers.push_back(t: b);
326}
327
328void GLTFGeometryLoader::processJSONAccessor(const QString &id, const QJsonObject &json)
329{
330 m_gltf1.m_accessorDict[id] = AccessorData(json);
331}
332
333void GLTFGeometryLoader::processJSONAccessorV2(const QJsonObject &json)
334{
335 m_gltf2.m_accessors.push_back(t: AccessorData(json));
336}
337
338void GLTFGeometryLoader::processJSONMesh(const QString &id, const QJsonObject &json)
339{
340 const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
341 for (const QJsonValue &primitiveValue : primitivesArray) {
342 QJsonObject primitiveObject = primitiveValue.toObject();
343 QString material = primitiveObject.value(KEY_MATERIAL).toString();
344
345 if (Q_UNLIKELY(material.isEmpty())) {
346 qCWarning(GLTFGeometryLoaderLog, "malformed primitive on %ls, missing material value %ls",
347 qUtf16PrintableImpl(id), qUtf16PrintableImpl(material));
348 continue;
349 }
350
351 QGeometry *meshGeometry = new QGeometry;
352
353 const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
354 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
355 QString k = it.value().toString();
356 const auto accessorIt = std::as_const(t&: m_gltf1.m_accessorDict).find(key: k);
357 if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) {
358 qCWarning(GLTFGeometryLoaderLog, "unknown attribute accessor: %ls on mesh %ls",
359 qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
360 continue;
361 }
362
363 const QString attrName = it.key();
364 QString attributeName = standardAttributeNameFromSemantic(semantic: attrName);
365 if (attributeName.isEmpty())
366 attributeName = attrName;
367
368 //Get buffer handle for accessor
369 Qt3DCore::QBuffer *buffer = m_gltf1.m_buffers.value(key: accessorIt->bufferViewName, defaultValue: nullptr);
370 if (Q_UNLIKELY(!buffer)) {
371 qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %ls processing accessor: %ls",
372 qUtf16PrintableImpl(accessorIt->bufferViewName), qUtf16PrintableImpl(id));
373 continue;
374 }
375
376 QAttribute *attribute = new QAttribute(buffer,
377 attributeName,
378 accessorIt->type,
379 accessorIt->dataSize,
380 accessorIt->count,
381 accessorIt->offset,
382 accessorIt->stride);
383 attribute->setAttributeType(QAttribute::VertexAttribute);
384 meshGeometry->addAttribute(attribute);
385 }
386
387 const auto indices = primitiveObject.value(KEY_INDICES);
388 if (!indices.isUndefined()) {
389 QString k = indices.toString();
390 const auto accessorIt = std::as_const(t&: m_gltf1.m_accessorDict).find(key: k);
391 if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) {
392 qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %ls on mesh %ls",
393 qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
394 } else {
395 //Get buffer handle for accessor
396 Qt3DCore::QBuffer *buffer = m_gltf1.m_buffers.value(key: accessorIt->bufferViewName, defaultValue: nullptr);
397 if (Q_UNLIKELY(!buffer)) {
398 qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %ls processing accessor: %ls",
399 qUtf16PrintableImpl(accessorIt->bufferViewName), qUtf16PrintableImpl(id));
400 continue;
401 }
402
403 QAttribute *attribute = new QAttribute(buffer,
404 accessorIt->type,
405 accessorIt->dataSize,
406 accessorIt->count,
407 accessorIt->offset,
408 accessorIt->stride);
409 attribute->setAttributeType(QAttribute::IndexAttribute);
410 meshGeometry->addAttribute(attribute);
411 }
412 } // of has indices
413
414 m_geometry = meshGeometry;
415
416 break;
417 } // of primitives iteration
418}
419
420void GLTFGeometryLoader::processJSONMeshV2(const QJsonObject &json)
421{
422 const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
423 for (const QJsonValue &primitiveValue : primitivesArray) {
424 QJsonObject primitiveObject = primitiveValue.toObject();
425
426 QGeometry *meshGeometry = new QGeometry;
427
428 const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
429 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
430 const int accessorIndex = it.value().toInt();
431 if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) {
432 qCWarning(GLTFGeometryLoaderLog, "unknown attribute accessor: %d on mesh %ls",
433 accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
434 continue;
435 }
436 const auto &accessor = m_gltf2.m_accessors[accessorIndex];
437
438 const QString attrName = it.key();
439 QString attributeName = standardAttributeNameFromSemantic(semantic: attrName);
440 if (attributeName.isEmpty())
441 attributeName = attrName;
442
443 // Get buffer handle for accessor
444 if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) {
445 qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls",
446 accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
447 continue;
448 }
449 Qt3DCore::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex];
450
451 QAttribute *attribute = new QAttribute(buffer,
452 attributeName,
453 accessor.type,
454 accessor.dataSize,
455 accessor.count,
456 accessor.offset,
457 accessor.stride);
458 attribute->setAttributeType(QAttribute::VertexAttribute);
459 meshGeometry->addAttribute(attribute);
460 }
461
462 const auto indices = primitiveObject.value(KEY_INDICES);
463 if (!indices.isUndefined()) {
464 const int accessorIndex = indices.toInt();
465 if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) {
466 qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %d on mesh %ls",
467 accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
468 } else {
469 const auto &accessor = m_gltf2.m_accessors[accessorIndex];
470
471 //Get buffer handle for accessor
472 if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) {
473 qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls",
474 accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
475 continue;
476 }
477 Qt3DCore::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex];
478
479 QAttribute *attribute = new QAttribute(buffer,
480 accessor.type,
481 accessor.dataSize,
482 accessor.count,
483 accessor.offset,
484 accessor.stride);
485 attribute->setAttributeType(QAttribute::IndexAttribute);
486 meshGeometry->addAttribute(attribute);
487 }
488 } // of has indices
489
490 m_geometry = meshGeometry;
491
492 break;
493 } // of primitives iteration
494}
495
496void GLTFGeometryLoader::loadBufferData()
497{
498 for (auto &bufferData : m_gltf1.m_bufferDatas) {
499 if (!bufferData.data) {
500 bufferData.data = new QByteArray(resolveLocalData(path: bufferData.path));
501 }
502 }
503}
504
505void GLTFGeometryLoader::unloadBufferData()
506{
507 for (const auto &bufferData : std::as_const(t&: m_gltf1.m_bufferDatas)) {
508 QByteArray *data = bufferData.data;
509 delete data;
510 }
511}
512
513void GLTFGeometryLoader::loadBufferDataV2()
514{
515 for (auto &bufferData : m_gltf2.m_bufferDatas) {
516 if (!bufferData.data)
517 bufferData.data = new QByteArray(resolveLocalData(path: bufferData.path));
518 }
519}
520
521void GLTFGeometryLoader::unloadBufferDataV2()
522{
523 for (const auto &bufferData : std::as_const(t&: m_gltf2.m_bufferDatas)) {
524 QByteArray *data = bufferData.data;
525 delete data;
526 }
527}
528
529QByteArray GLTFGeometryLoader::resolveLocalData(const QString &path) const
530{
531 QDir d(m_basePath);
532 Q_ASSERT(d.exists());
533
534 QString absPath = d.absoluteFilePath(fileName: path);
535 QFile f(absPath);
536 f.open(flags: QIODevice::ReadOnly);
537 return f.readAll();
538}
539
540QAttribute::VertexBaseType GLTFGeometryLoader::accessorTypeFromJSON(int componentType)
541{
542 if (componentType == GL_BYTE)
543 return QAttribute::Byte;
544 else if (componentType == GL_UNSIGNED_BYTE)
545 return QAttribute::UnsignedByte;
546 else if (componentType == GL_SHORT)
547 return QAttribute::Short;
548 else if (componentType == GL_UNSIGNED_SHORT)
549 return QAttribute::UnsignedShort;
550 else if (componentType == GL_UNSIGNED_INT)
551 return QAttribute::UnsignedInt;
552 else if (componentType == GL_FLOAT)
553 return QAttribute::Float;
554
555 //There shouldn't be an invalid case here
556 qCWarning(GLTFGeometryLoaderLog, "unsupported accessor type %d", componentType);
557 return QAttribute::Float;
558}
559
560uint GLTFGeometryLoader::accessorDataSizeFromJson(const QString &type)
561{
562 QString typeName = type.toUpper();
563 if (typeName == QLatin1String("SCALAR"))
564 return 1;
565 if (typeName == QLatin1String("VEC2"))
566 return 2;
567 if (typeName == QLatin1String("VEC3"))
568 return 3;
569 if (typeName == QLatin1String("VEC4"))
570 return 4;
571 if (typeName == QLatin1String("MAT2"))
572 return 4;
573 if (typeName == QLatin1String("MAT3"))
574 return 9;
575 if (typeName == QLatin1String("MAT4"))
576 return 16;
577
578 return 0;
579}
580
581} // namespace Qt3DRender
582
583QT_END_NAMESPACE
584
585#include "moc_gltfgeometryloader.cpp"
586

source code of qt3d/src/plugins/geometryloaders/gltf/gltfgeometryloader.cpp