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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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