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

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