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

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