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 "gltfskeletonloader_p.h"
5
6#include <qopengl.h>
7#include <QtCore/qdir.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qfileinfo.h>
10#include <QtCore/qiodevice.h>
11#include <QtCore/qjsonarray.h>
12#include <QtCore/qjsonobject.h>
13#include <QtCore/qjsonvalue.h>
14#include <QtCore/qversionnumber.h>
15
16#include <Qt3DRender/private/renderlogging_p.h>
17#include <Qt3DCore/private/qmath3d_p.h>
18#include <Qt3DCore/private/qloadgltf_p.h>
19
20#include <QtGui/qquaternion.h>
21
22QT_BEGIN_NAMESPACE
23
24using namespace Qt3DCore;
25
26namespace {
27
28void jsonArrayToSqt(const QJsonArray &jsonArray, Qt3DCore::Sqt &sqt)
29{
30 Q_ASSERT(jsonArray.size() == 16);
31 QMatrix4x4 m;
32 float *data = m.data();
33 int i = 0;
34 for (const auto element : jsonArray)
35 *(data + i++) = static_cast<float>(element.toDouble());
36
37 decomposeQMatrix4x4(m, sqt);
38}
39
40void jsonArrayToVector3D(const QJsonArray &jsonArray, QVector3D &v)
41{
42 Q_ASSERT(jsonArray.size() == 3);
43 v.setX(static_cast<float>(jsonArray.at(i: 0).toDouble()));
44 v.setY(static_cast<float>(jsonArray.at(i: 1).toDouble()));
45 v.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble()));
46}
47
48void jsonArrayToQuaternion(const QJsonArray &jsonArray, QQuaternion &q)
49{
50 Q_ASSERT(jsonArray.size() == 4);
51 q.setX(static_cast<float>(jsonArray.at(i: 0).toDouble()));
52 q.setY(static_cast<float>(jsonArray.at(i: 1).toDouble()));
53 q.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble()));
54 q.setScalar(static_cast<float>(jsonArray.at(i: 3).toDouble()));
55}
56
57}
58
59namespace Qt3DRender {
60namespace Render {
61
62#define KEY_ACCESSORS QLatin1String("accessors")
63#define KEY_ASSET QLatin1String("asset")
64#define KEY_BUFFER QLatin1String("buffer")
65#define KEY_BUFFERS QLatin1String("buffers")
66#define KEY_BUFFER_VIEW QLatin1String("bufferView")
67#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
68#define KEY_BYTE_LENGTH QLatin1String("byteLength")
69#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
70#define KEY_BYTE_STRIDE QLatin1String("byteStride")
71#define KEY_CAMERA QLatin1String("camera")
72#define KEY_CHILDREN QLatin1String("children")
73#define KEY_COMPONENT_TYPE QLatin1String("componentType")
74#define KEY_COUNT QLatin1String("count")
75#define KEY_JOINTS QLatin1String("joints")
76#define KEY_INVERSE_BIND_MATRICES QLatin1String("inverseBindMatrices")
77#define KEY_MATRIX QLatin1String("matrix")
78#define KEY_MESH QLatin1String("mesh")
79#define KEY_NAME QLatin1String("name")
80#define KEY_NODES QLatin1String("nodes")
81#define KEY_ROTATION QLatin1String("rotation")
82#define KEY_SCALE QLatin1String("scale")
83#define KEY_SKIN QLatin1String("skin")
84#define KEY_SKINS QLatin1String("skins")
85#define KEY_TARGET QLatin1String("target")
86#define KEY_TRANSLATION QLatin1String("translation")
87#define KEY_TYPE QLatin1String("type")
88#define KEY_URI QLatin1String("uri")
89#define KEY_VERSION QLatin1String("version")
90
91GLTFSkeletonLoader::BufferData::BufferData()
92 : byteLength(0)
93 , data()
94{
95}
96
97GLTFSkeletonLoader::BufferData::BufferData(const QJsonObject &json)
98 : byteLength(json.value(KEY_BYTE_LENGTH).toInt())
99 , path(json.value(KEY_URI).toString())
100 , data()
101{
102}
103
104GLTFSkeletonLoader::BufferView::BufferView()
105 : bufferIndex(-1)
106 , byteOffset(0)
107 , byteLength(0)
108 , target(0)
109{
110}
111
112GLTFSkeletonLoader::BufferView::BufferView(const QJsonObject &json)
113 : bufferIndex(json.value(KEY_BUFFER).toInt())
114 , byteOffset(json.value(KEY_BYTE_OFFSET).toInt())
115 , byteLength(json.value(KEY_BYTE_LENGTH).toInt())
116 , target(0)
117{
118 const auto targetValue = json.value(KEY_TARGET);
119 if (!targetValue.isUndefined())
120 target = targetValue.toInt();
121}
122
123GLTFSkeletonLoader::AccessorData::AccessorData()
124 : type(QAttribute::Float)
125 , dataSize(0)
126 , count(0)
127 , byteOffset(0)
128 , byteStride(0)
129{
130}
131
132GLTFSkeletonLoader::AccessorData::AccessorData(const QJsonObject &json)
133 : bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(defaultValue: -1))
134 , type(accessorTypeFromJSON(componentType: json.value(KEY_COMPONENT_TYPE).toInt()))
135 , dataSize(accessorDataSizeFromJson(type: json.value(KEY_TYPE).toString()))
136 , count(json.value(KEY_COUNT).toInt())
137 , byteOffset(0)
138 , byteStride(0)
139{
140 const auto byteOffsetValue = json.value(KEY_BYTE_OFFSET);
141 if (!byteOffsetValue.isUndefined())
142 byteOffset = byteOffsetValue.toInt();
143 const auto byteStrideValue = json.value(KEY_BYTE_STRIDE);
144 if (!byteStrideValue.isUndefined())
145 byteStride = byteStrideValue.toInt();
146}
147
148GLTFSkeletonLoader::Skin::Skin()
149 : inverseBindAccessorIndex(-1)
150 , jointNodeIndices()
151{
152}
153
154GLTFSkeletonLoader::Skin::Skin(const QJsonObject &json)
155 : name(json.value(KEY_NAME).toString())
156 , inverseBindAccessorIndex(json.value(KEY_INVERSE_BIND_MATRICES).toInt())
157{
158 QJsonArray jointNodes = json.value(KEY_JOINTS).toArray();
159 jointNodeIndices.reserve(n: jointNodes.size());
160 for (const auto jointNodeValue : jointNodes)
161 jointNodeIndices.push_back(x: jointNodeValue.toInt());
162}
163
164GLTFSkeletonLoader::Node::Node()
165 : localTransform()
166 , childNodeIndices()
167 , name()
168 , parentNodeIndex(-1)
169 , cameraIndex(-1)
170 , meshIndex(-1)
171 , skinIndex(-1)
172{
173}
174
175GLTFSkeletonLoader::Node::Node(const QJsonObject &json)
176 : localTransform()
177 , childNodeIndices()
178 , name(json.value(KEY_NAME).toString())
179 , parentNodeIndex(-1)
180 , cameraIndex(-1)
181 , meshIndex(-1)
182 , skinIndex(-1)
183{
184 // Child nodes - we setup the parent links in a later pass
185 QJsonArray childNodes = json.value(KEY_CHILDREN).toArray();
186 childNodeIndices.reserve(n: childNodes.size());
187 for (const auto childNodeValue : childNodes)
188 childNodeIndices.push_back(x: childNodeValue.toInt());
189
190 // Local transform - matrix or scale, rotation, translation
191 const auto matrixValue = json.value(KEY_MATRIX);
192 if (!matrixValue.isUndefined()) {
193 jsonArrayToSqt(jsonArray: matrixValue.toArray(), sqt&: localTransform);
194 } else {
195 const auto scaleValue = json.value(KEY_SCALE);
196 const auto rotationValue = json.value(KEY_ROTATION);
197 const auto translationValue = json.value(KEY_TRANSLATION);
198
199 if (!scaleValue.isUndefined())
200 jsonArrayToVector3D(jsonArray: scaleValue.toArray(), v&: localTransform.scale);
201
202 if (!rotationValue.isUndefined())
203 jsonArrayToQuaternion(jsonArray: json.value(KEY_ROTATION).toArray(), q&: localTransform.rotation);
204
205 if (!translationValue.isUndefined())
206 jsonArrayToVector3D(jsonArray: json.value(KEY_TRANSLATION).toArray(), v&: localTransform.translation);
207 }
208
209 // Referenced objects
210 const auto cameraValue = json.value(KEY_CAMERA);
211 if (!cameraValue.isUndefined())
212 cameraIndex = cameraValue.toInt();
213
214 const auto meshValue = json.value(KEY_MESH);
215 if (!meshValue.isUndefined())
216 meshIndex = meshValue.toInt();
217
218 const auto skinValue = json.value(KEY_SKIN);
219 if (!skinValue.isUndefined())
220 skinIndex = skinValue.toInt();
221}
222
223QAttribute::VertexBaseType GLTFSkeletonLoader::accessorTypeFromJSON(int componentType)
224{
225 if (componentType == GL_BYTE)
226 return QAttribute::Byte;
227 else if (componentType == GL_UNSIGNED_BYTE)
228 return QAttribute::UnsignedByte;
229 else if (componentType == GL_SHORT)
230 return QAttribute::Short;
231 else if (componentType == GL_UNSIGNED_SHORT)
232 return QAttribute::UnsignedShort;
233 else if (componentType == GL_UNSIGNED_INT)
234 return QAttribute::UnsignedInt;
235 else if (componentType == GL_FLOAT)
236 return QAttribute::Float;
237
238 // There shouldn't be an invalid case here
239 qCWarning(Jobs, "unsupported accessor type %d", componentType);
240 return QAttribute::Float;
241}
242
243uint GLTFSkeletonLoader::accessorTypeSize(QAttribute::VertexBaseType componentType)
244{
245 switch (componentType) {
246 case QAttribute::Byte:
247 case QAttribute::UnsignedByte:
248 return 1;
249
250 case QAttribute::Short:
251 case QAttribute::UnsignedShort:
252 return 2;
253
254 case QAttribute::Int:
255 case QAttribute::Float:
256 return 4;
257
258 default:
259 qCWarning(Jobs, "Unhandled accessor data type %d", componentType);
260 return 0;
261 }
262}
263
264uint GLTFSkeletonLoader::accessorDataSizeFromJson(const QString &type)
265{
266 QString typeName = type.toUpper();
267 if (typeName == QLatin1String("SCALAR"))
268 return 1;
269 if (typeName == QLatin1String("VEC2"))
270 return 2;
271 if (typeName == QLatin1String("VEC3"))
272 return 3;
273 if (typeName == QLatin1String("VEC4"))
274 return 4;
275 if (typeName == QLatin1String("MAT2"))
276 return 4;
277 if (typeName == QLatin1String("MAT3"))
278 return 9;
279 if (typeName == QLatin1String("MAT4"))
280 return 16;
281
282 return 0;
283}
284
285GLTFSkeletonLoader::GLTFSkeletonLoader()
286{
287}
288
289bool GLTFSkeletonLoader::load(QIODevice *ioDev)
290{
291 if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) {
292 qCWarning(Jobs, "not a JSON document");
293 return false;
294 }
295
296 auto file = qobject_cast<QFile*>(object: ioDev);
297 if (file) {
298 QFileInfo finfo(file->fileName());
299 setBasePath(finfo.dir().absolutePath());
300 }
301
302 return parse();
303}
304
305SkeletonData GLTFSkeletonLoader::createSkeleton(const QString &skeletonName)
306{
307 if (m_skins.empty()) {
308 qCWarning(Jobs, "glTF file does not contain any skins");
309 return SkeletonData();
310 }
311
312 auto skin = m_skins.begin();
313 if (!skeletonName.isNull()) {
314 const auto result = std::find_if(first: m_skins.begin(), last: m_skins.end(),
315 pred: [skeletonName](const Skin &skin) { return skin.name == skeletonName; });
316 if (result != m_skins.end())
317 skin = result;
318 }
319
320 Q_ASSERT(skin != m_skins.end());
321 return createSkeletonFromSkin(skin: *skin);
322}
323
324SkeletonData GLTFSkeletonLoader::createSkeletonFromSkin(const Skin &skin) const
325{
326 SkeletonData skel;
327
328 const int jointCount = int(skin.jointNodeIndices.size());
329 skel.reserve(size: jointCount);
330
331 QHash<const Node *, int> jointIndexMap;
332 for (int i = 0; i < jointCount; ++i) {
333 // Get a pointer to the node for this joint and store it in
334 // a map to the JointInfo index. We can later use this to set
335 // the parent indices of the joints
336 const Node *node = &m_nodes[skin.jointNodeIndices[i]];
337 jointIndexMap.insert(key: node, value: i);
338
339 JointInfo joint;
340 joint.inverseBindPose = inverseBindMatrix(skin, jointIndex: i);
341 joint.parentIndex = jointIndexMap.value(key: &m_nodes[node->parentNodeIndex], defaultValue: -1);
342 if (joint.parentIndex == -1 && i != 0)
343 qCDebug(Jobs) << "Cannot find parent joint for joint" << i;
344
345 skel.joints.push_back(t: joint);
346 skel.localPoses.push_back(t: node->localTransform);
347 skel.jointNames.push_back(t: node->name);
348 }
349
350 return skel;
351}
352
353QMatrix4x4 GLTFSkeletonLoader::inverseBindMatrix(const Skin &skin, int jointIndex) const
354{
355 // Create a matrix and copy the data into it
356 RawData rawData = accessorData(accessorIndex: skin.inverseBindAccessorIndex, index: jointIndex);
357 QMatrix4x4 m;
358 memcpy(dest: m.data(), src: rawData.data, n: rawData.byteLength);
359 return m;
360}
361
362GLTFSkeletonLoader::RawData GLTFSkeletonLoader::accessorData(int accessorIndex, int index) const
363{
364 const AccessorData &accessor = m_accessors[accessorIndex];
365 const BufferView &bufferView = m_bufferViews[accessor.bufferViewIndex];
366 const BufferData &bufferData = m_bufferDatas[bufferView.bufferIndex];
367 const QByteArray &ba = bufferData.data;
368 const char *rawData = ba.constData() + bufferView.byteOffset + accessor.byteOffset;
369
370 const uint typeSize = accessorTypeSize(componentType: accessor.type);
371 const int stride = (accessor.byteStride == 0)
372 ? accessor.dataSize * typeSize
373 : accessor.byteStride;
374
375 const char* data = rawData + index * stride;
376 if (data - rawData > ba.size()) {
377 qCWarning(Jobs, "Attempting to access data beyond end of buffer");
378 return RawData{ .data: nullptr, .byteLength: 0 };
379 }
380
381 const quint64 byteLength = accessor.dataSize * typeSize;
382 RawData rd{ .data: data, .byteLength: byteLength };
383
384 return rd;
385}
386
387void GLTFSkeletonLoader::setBasePath(const QString &path)
388{
389 m_basePath = path;
390}
391
392bool GLTFSkeletonLoader::setJSON(const QJsonDocument &json)
393{
394 if (!json.isObject())
395 return false;
396 m_json = json;
397 cleanup();
398 return true;
399}
400
401bool GLTFSkeletonLoader::parse()
402{
403 // Find the glTF version
404 const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject();
405 const QString versionString = asset.value(KEY_VERSION).toString();
406 const auto version = QVersionNumber::fromString(string: versionString);
407 switch (version.majorVersion()) {
408 case 2:
409 return parseGLTF2();
410
411 default:
412 qWarning() << "Unsupported version of glTF" << versionString;
413 return false;
414 }
415}
416
417bool GLTFSkeletonLoader::parseGLTF2()
418{
419 bool success = true;
420 const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
421 for (const auto bufferValue : buffers)
422 success &= processJSONBuffer(json: bufferValue.toObject());
423
424 const QJsonArray bufferViews = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
425 for (const auto bufferViewValue : bufferViews)
426 success &= processJSONBufferView(json: bufferViewValue.toObject());
427
428 const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
429 for (const auto accessorValue : accessors)
430 success &= processJSONAccessor(json: accessorValue.toObject());
431
432 const QJsonArray skins = m_json.object().value(KEY_SKINS).toArray();
433 for (const auto skinValue : skins)
434 success &= processJSONSkin(json: skinValue.toObject());
435
436 const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
437 for (const auto nodeValue : nodes)
438 success &= processJSONNode(json: nodeValue.toObject());
439 setupNodeParentLinks();
440
441 // TODO: Make a complete GLTF 2 parser by extending to other top level elements:
442 // scenes, animations, meshes etc.
443
444 return success;
445}
446
447void GLTFSkeletonLoader::cleanup()
448{
449 m_accessors.clear();
450 m_bufferViews.clear();
451 m_bufferDatas.clear();
452}
453
454bool GLTFSkeletonLoader::processJSONBuffer(const QJsonObject &json)
455{
456 // Store buffer details and load data into memory
457 BufferData buffer(json);
458 buffer.data = resolveLocalData(path: buffer.path);
459 if (buffer.data.isEmpty())
460 return false;
461
462 m_bufferDatas.push_back(x: buffer);
463 return true;
464}
465
466bool GLTFSkeletonLoader::processJSONBufferView(const QJsonObject &json)
467{
468 BufferView bufferView(json);
469
470 // Perform sanity checks
471 const auto bufferIndex = bufferView.bufferIndex;
472 if (Q_UNLIKELY(bufferIndex >= int(m_bufferDatas.size()))) {
473 qCWarning(Jobs, "Unknown buffer %d when processing buffer view", bufferIndex);
474 return false;
475 }
476
477 const auto &bufferData = m_bufferDatas[bufferIndex];
478 if (bufferView.byteOffset > bufferData.byteLength) {
479 qCWarning(Jobs, "Bufferview has offset greater than buffer %d length", bufferIndex);
480 return false;
481 }
482
483 if (Q_UNLIKELY(bufferView.byteOffset + bufferView.byteLength > bufferData.byteLength)) {
484 qCWarning(Jobs, "BufferView extends beyond end of buffer %d", bufferIndex);
485 return false;
486 }
487
488 m_bufferViews.push_back(x: bufferView);
489 return true;
490}
491
492bool GLTFSkeletonLoader::processJSONAccessor(const QJsonObject &json)
493{
494 AccessorData accessor(json);
495
496 // TODO: Perform sanity checks
497
498 m_accessors.push_back(x: accessor);
499 return true;
500}
501
502bool GLTFSkeletonLoader::processJSONSkin(const QJsonObject &json)
503{
504 Skin skin(json);
505
506 // TODO: Perform sanity checks
507
508 m_skins.push_back(x: skin);
509 return true;
510}
511
512bool GLTFSkeletonLoader::processJSONNode(const QJsonObject &json)
513{
514 Node node(json);
515
516 // TODO: Perform sanity checks
517
518 m_nodes.push_back(x: node);
519 return true;
520}
521
522void GLTFSkeletonLoader::setupNodeParentLinks()
523{
524 const size_t nodeCount = m_nodes.size();
525 for (size_t i = 0; i < nodeCount; ++i) {
526 const Node &node = m_nodes[i];
527 const std::vector<int> &childNodeIndices = node.childNodeIndices;
528 for (const auto childNodeIndex : childNodeIndices) {
529 Q_ASSERT(childNodeIndex < int(m_nodes.size()));
530 Node &childNode = m_nodes[size_t(childNodeIndex)];
531 Q_ASSERT(childNode.parentNodeIndex == -1);
532 childNode.parentNodeIndex = int(i);
533 }
534 }
535}
536
537QByteArray GLTFSkeletonLoader::resolveLocalData(const QString &path) const
538{
539 QDir d(m_basePath);
540 Q_ASSERT(d.exists());
541
542 QString absPath = d.absoluteFilePath(fileName: path);
543 QFile f(absPath);
544 if (!f.open(flags: QIODevice::ReadOnly))
545 return QByteArray();
546 return f.readAll();
547}
548
549} // namespace Render
550} // namespace Qt3DRender
551
552QT_END_NAMESPACE
553

source code of qt3d/src/render/geometry/gltfskeletonloader.cpp