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 "gltfimporter_p.h"
5
6#include <Qt3DCore/private/qloadgltf_p.h>
7
8#include <Qt3DAnimation/private/animationlogging_p.h>
9#include <Qt3DAnimation/private/fcurve_p.h>
10#include <Qt3DAnimation/private/keyframe_p.h>
11
12#include <qopengl.h>
13#include <QtGui/qquaternion.h>
14#include <QtGui/qvector2d.h>
15#include <QtGui/qvector3d.h>
16#include <QtGui/qvector4d.h>
17#include <QtCore/qdir.h>
18#include <QtCore/qfile.h>
19#include <QtCore/qfileinfo.h>
20#include <QtCore/qiodevice.h>
21#include <QtCore/qversionnumber.h>
22
23QT_BEGIN_NAMESPACE
24
25namespace Qt3DAnimation {
26namespace Animation {
27
28namespace {
29
30QString gltfTargetPropertyToChannelName(const QString &propertyName)
31{
32 if (propertyName == QLatin1String("rotation"))
33 return QLatin1String("Rotation");
34 else if (propertyName == QLatin1String("translation"))
35 return QLatin1String("Location");
36 else if (propertyName == QLatin1String("scale"))
37 return QLatin1String("Scale");
38
39 qWarning() << "Unknown target property name";
40 return QString();
41}
42
43QKeyFrame::InterpolationType gltfToQKeyFrameInterpolation(GLTFImporter::Sampler::InterpolationMode mode)
44{
45 switch (mode) {
46 case GLTFImporter::Sampler::Linear:
47 return QKeyFrame::LinearInterpolation;
48 case GLTFImporter::Sampler::Step:
49 return QKeyFrame::ConstantInterpolation;
50 case GLTFImporter::Sampler::CubicSpline:
51 return QKeyFrame::BezierInterpolation;
52 case GLTFImporter::Sampler::CatmullRomSpline:
53 // TODO: Implement this interpolation type
54 qWarning() << "Unhandled interpolation type";
55 return QKeyFrame::LinearInterpolation;
56 }
57
58 return QKeyFrame::LinearInterpolation;
59}
60
61void jsonArrayToSqt(const QJsonArray &jsonArray, Qt3DCore::Sqt &sqt)
62{
63 Q_ASSERT(jsonArray.size() == 16);
64 QMatrix4x4 m;
65 float *data = m.data();
66 int i = 0;
67 for (const auto element : jsonArray)
68 *(data + i++) = static_cast<float>(element.toDouble());
69
70 decomposeQMatrix4x4(m, sqt);
71}
72
73void jsonArrayToVector3D(const QJsonArray &jsonArray, QVector3D &v)
74{
75 Q_ASSERT(jsonArray.size() == 3);
76 v.setX(static_cast<float>(jsonArray.at(i: 0).toDouble()));
77 v.setY(static_cast<float>(jsonArray.at(i: 1).toDouble()));
78 v.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble()));
79}
80
81void jsonArrayToQuaternion(const QJsonArray &jsonArray, QQuaternion &q)
82{
83 Q_ASSERT(jsonArray.size() == 4);
84 q.setX(static_cast<float>(jsonArray.at(i: 0).toDouble()));
85 q.setY(static_cast<float>(jsonArray.at(i: 1).toDouble()));
86 q.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble()));
87 q.setScalar(static_cast<float>(jsonArray.at(i: 3).toDouble()));
88}
89
90}
91
92#define KEY_ACCESSORS QLatin1String("accessors")
93#define KEY_ANIMATIONS QLatin1String("animations")
94#define KEY_ASSET QLatin1String("asset")
95#define KEY_BUFFER QLatin1String("buffer")
96#define KEY_BUFFERS QLatin1String("buffers")
97#define KEY_BUFFER_VIEW QLatin1String("bufferView")
98#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
99#define KEY_BYTE_LENGTH QLatin1String("byteLength")
100#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
101#define KEY_BYTE_STRIDE QLatin1String("byteStride")
102#define KEY_CAMERA QLatin1String("camera")
103#define KEY_CHANNELS QLatin1String("channels")
104#define KEY_CHILDREN QLatin1String("children")
105#define KEY_COMPONENT_TYPE QLatin1String("componentType")
106#define KEY_COUNT QLatin1String("count")
107#define KEY_JOINTS QLatin1String("joints")
108#define KEY_INPUT QLatin1String("input")
109#define KEY_INTERPOLATION QLatin1String("interpolation")
110#define KEY_INVERSE_BIND_MATRICES QLatin1String("inverseBindMatrices")
111#define KEY_MATRIX QLatin1String("matrix")
112#define KEY_MESH QLatin1String("mesh")
113#define KEY_NAME QLatin1String("name")
114#define KEY_NODE QLatin1String("node")
115#define KEY_NODES QLatin1String("nodes")
116#define KEY_OUTPUT QLatin1String("output")
117#define KEY_PATH QLatin1String("path")
118#define KEY_ROTATION QLatin1String("rotation")
119#define KEY_SAMPLER QLatin1String("sampler")
120#define KEY_SAMPLERS QLatin1String("samplers")
121#define KEY_SCALE QLatin1String("scale")
122#define KEY_SKIN QLatin1String("skin")
123#define KEY_SKINS QLatin1String("skins")
124#define KEY_TARGET QLatin1String("target")
125#define KEY_TRANSLATION QLatin1String("translation")
126#define KEY_TYPE QLatin1String("type")
127#define KEY_URI QLatin1String("uri")
128#define KEY_VERSION QLatin1String("version")
129
130GLTFImporter::BufferData::BufferData()
131 : byteLength(0)
132 , data()
133{
134}
135
136GLTFImporter::BufferData::BufferData(const QJsonObject &json)
137 : byteLength(json.value(KEY_BYTE_LENGTH).toInt())
138 , path(json.value(KEY_URI).toString())
139 , data()
140{
141}
142
143GLTFImporter::BufferView::BufferView()
144 : byteOffset(0)
145 , byteLength(0)
146 , bufferIndex(-1)
147 , target(0)
148{
149}
150
151GLTFImporter::BufferView::BufferView(const QJsonObject &json)
152 : byteOffset(json.value(KEY_BYTE_OFFSET).toInt())
153 , byteLength(json.value(KEY_BYTE_LENGTH).toInt())
154 , bufferIndex(json.value(KEY_BUFFER).toInt())
155 , target(0)
156{
157 const auto targetValue = json.value(KEY_TARGET);
158 if (!targetValue.isUndefined())
159 target = targetValue.toInt();
160}
161
162GLTFImporter::AccessorData::AccessorData()
163 : type(Qt3DCore::QAttribute::Float)
164 , dataSize(0)
165 , count(0)
166 , byteOffset(0)
167 , byteStride(0)
168{
169}
170
171GLTFImporter::AccessorData::AccessorData(const QJsonObject &json)
172 : bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(defaultValue: -1))
173 , type(accessorTypeFromJSON(componentType: json.value(KEY_COMPONENT_TYPE).toInt()))
174 , dataSize(accessorDataSizeFromJson(type: json.value(KEY_TYPE).toString()))
175 , count(json.value(KEY_COUNT).toInt())
176 , byteOffset(0)
177 , byteStride(0)
178{
179 const auto byteOffsetValue = json.value(KEY_BYTE_OFFSET);
180 if (!byteOffsetValue.isUndefined())
181 byteOffset = byteOffsetValue.toInt();
182 const auto byteStrideValue = json.value(KEY_BYTE_STRIDE);
183 if (!byteStrideValue.isUndefined())
184 byteStride = byteStrideValue.toInt();
185}
186
187GLTFImporter::Skin::Skin()
188 : inverseBindAccessorIndex(-1)
189 , jointNodeIndices()
190{
191}
192
193GLTFImporter::Skin::Skin(const QJsonObject &json)
194 : name(json.value(KEY_NAME).toString())
195 , inverseBindAccessorIndex(json.value(KEY_INVERSE_BIND_MATRICES).toInt())
196{
197 QJsonArray jointNodes = json.value(KEY_JOINTS).toArray();
198 jointNodeIndices.reserve(asize: jointNodes.size());
199 for (const auto jointNodeValue : jointNodes)
200 jointNodeIndices.push_back(t: jointNodeValue.toInt());
201}
202
203GLTFImporter::Channel::Channel()
204 : samplerIndex(-1)
205 , targetNodeIndex(-1)
206 , targetProperty()
207{
208}
209
210GLTFImporter::Channel::Channel(const QJsonObject &json)
211 : samplerIndex(json.value(KEY_SAMPLER).toInt())
212 , targetNodeIndex(-1)
213 , targetProperty()
214{
215 const auto targetJson = json.value(KEY_TARGET).toObject();
216 targetNodeIndex = targetJson.value(KEY_NODE).toInt();
217 targetProperty = targetJson.value(KEY_PATH).toString();
218}
219
220GLTFImporter::Sampler::Sampler()
221 : inputAccessorIndex(-1)
222 , outputAccessorIndex(-1)
223 , interpolationMode(Linear)
224{
225}
226
227GLTFImporter::Sampler::Sampler(const QJsonObject &json)
228 : inputAccessorIndex(json.value(KEY_INPUT).toInt())
229 , outputAccessorIndex(json.value(KEY_OUTPUT).toInt())
230 , interpolationMode(Linear)
231{
232 const auto interpolation = json.value(KEY_INTERPOLATION).toString();
233 if (interpolation == QLatin1String("LINEAR"))
234 interpolationMode = Linear;
235 else if (interpolation == QLatin1String("STEP"))
236 interpolationMode = Step;
237 else if (interpolation == QLatin1String("CATMULLROMSPLINE"))
238 interpolationMode = CatmullRomSpline;
239 else if (interpolation == QLatin1String("CUBICSPLINE"))
240 interpolationMode = CubicSpline;
241}
242
243QString GLTFImporter::Sampler::interpolationModeString() const
244{
245 switch (interpolationMode) {
246 case Linear: return QLatin1String("LINEAR");
247 case Step: return QLatin1String("STEP");
248 case CatmullRomSpline: return QLatin1String("CATMULLROMSPLINE");
249 case CubicSpline: return QLatin1String("CUBICSPLINE");
250 }
251
252 return QLatin1String("Unknown");
253}
254
255GLTFImporter::Animation::Animation()
256 : name()
257 , channels()
258 , samplers()
259{
260}
261
262GLTFImporter::Animation::Animation(const QJsonObject &json)
263 : name(json.value(KEY_NAME).toString())
264{
265 QJsonArray channelsArray = json.value(KEY_CHANNELS).toArray();
266 channels.reserve(asize: channelsArray.size());
267 for (const auto channelValue : channelsArray) {
268 Channel channel(channelValue.toObject());
269 channels.push_back(t: channel);
270 }
271
272 QJsonArray samplersArray = json.value(KEY_SAMPLERS).toArray();
273 samplers.reserve(asize: samplersArray.size());
274 for (const auto samplerValue : samplersArray) {
275 Sampler sampler(samplerValue.toObject());
276 samplers.push_back(t: sampler);
277 }
278}
279
280GLTFImporter::Node::Node()
281 : localTransform()
282 , childNodeIndices()
283 , name()
284 , parentNodeIndex(-1)
285 , cameraIndex(-1)
286 , meshIndex(-1)
287 , skinIndex(-1)
288{
289}
290
291GLTFImporter::Node::Node(const QJsonObject &json)
292 : localTransform()
293 , childNodeIndices()
294 , name(json.value(KEY_NAME).toString())
295 , parentNodeIndex(-1)
296 , cameraIndex(-1)
297 , meshIndex(-1)
298 , skinIndex(-1)
299{
300 // Child nodes - we setup the parent links in a later pass
301 QJsonArray childNodes = json.value(KEY_CHILDREN).toArray();
302 childNodeIndices.reserve(asize: childNodes.size());
303 for (const auto childNodeValue : childNodes)
304 childNodeIndices.push_back(t: childNodeValue.toInt());
305
306 // Local transform - matrix or scale, rotation, translation
307 const auto matrixValue = json.value(KEY_MATRIX);
308 if (!matrixValue.isUndefined()) {
309 jsonArrayToSqt(jsonArray: matrixValue.toArray(), sqt&: localTransform);
310 } else {
311 const auto scaleValue = json.value(KEY_SCALE);
312 const auto rotationValue = json.value(KEY_ROTATION);
313 const auto translationValue = json.value(KEY_TRANSLATION);
314
315 if (!scaleValue.isUndefined())
316 jsonArrayToVector3D(jsonArray: scaleValue.toArray(), v&: localTransform.scale);
317
318 if (!rotationValue.isUndefined())
319 jsonArrayToQuaternion(jsonArray: json.value(KEY_ROTATION).toArray(), q&: localTransform.rotation);
320
321 if (!translationValue.isUndefined())
322 jsonArrayToVector3D(jsonArray: json.value(KEY_TRANSLATION).toArray(), v&: localTransform.translation);
323 }
324
325 // Referenced objects
326 const auto cameraValue = json.value(KEY_CAMERA);
327 if (!cameraValue.isUndefined())
328 cameraIndex = cameraValue.toInt();
329
330 const auto meshValue = json.value(KEY_MESH);
331 if (!meshValue.isUndefined())
332 meshIndex = meshValue.toInt();
333
334 const auto skinValue = json.value(KEY_SKIN);
335 if (!skinValue.isUndefined())
336 skinIndex = skinValue.toInt();
337}
338
339Qt3DCore::QAttribute::VertexBaseType GLTFImporter::accessorTypeFromJSON(int componentType)
340{
341 if (componentType == GL_BYTE)
342 return Qt3DCore::QAttribute::Byte;
343 else if (componentType == GL_UNSIGNED_BYTE)
344 return Qt3DCore::QAttribute::UnsignedByte;
345 else if (componentType == GL_SHORT)
346 return Qt3DCore::QAttribute::Short;
347 else if (componentType == GL_UNSIGNED_SHORT)
348 return Qt3DCore::QAttribute::UnsignedShort;
349 else if (componentType == GL_UNSIGNED_INT)
350 return Qt3DCore::QAttribute::UnsignedInt;
351 else if (componentType == GL_FLOAT)
352 return Qt3DCore::QAttribute::Float;
353
354 // There shouldn't be an invalid case here
355 qWarning(msg: "unsupported accessor type %d", componentType);
356 return Qt3DCore::QAttribute::Float;
357}
358
359uint GLTFImporter::accessorTypeSize(Qt3DCore::QAttribute::VertexBaseType componentType)
360{
361 switch (componentType) {
362 case Qt3DCore::QAttribute::Byte:
363 case Qt3DCore::QAttribute::UnsignedByte:
364 return 1;
365
366 case Qt3DCore::QAttribute::Short:
367 case Qt3DCore::QAttribute::UnsignedShort:
368 return 2;
369
370 case Qt3DCore::QAttribute::Int:
371 case Qt3DCore::QAttribute::Float:
372 return 4;
373
374 default:
375 qWarning(msg: "Unhandled accessor data type %d", componentType);
376 return 0;
377 }
378}
379
380uint GLTFImporter::accessorDataSizeFromJson(const QString &type)
381{
382 QString typeName = type.toUpper();
383 if (typeName == QLatin1String("SCALAR"))
384 return 1;
385 if (typeName == QLatin1String("VEC2"))
386 return 2;
387 if (typeName == QLatin1String("VEC3"))
388 return 3;
389 if (typeName == QLatin1String("VEC4"))
390 return 4;
391 if (typeName == QLatin1String("MAT2"))
392 return 4;
393 if (typeName == QLatin1String("MAT3"))
394 return 9;
395 if (typeName == QLatin1String("MAT4"))
396 return 16;
397
398 return 0;
399}
400
401GLTFImporter::GLTFImporter()
402{
403}
404
405bool GLTFImporter::load(QIODevice *ioDev)
406{
407 if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) {
408 qWarning(msg: "not a JSON document");
409 return false;
410 }
411
412 auto file = qobject_cast<QFile*>(object: ioDev);
413 if (file) {
414 QFileInfo finfo(file->fileName());
415 setBasePath(finfo.dir().absolutePath());
416 }
417
418 return parse();
419}
420
421QHash<qsizetype, qsizetype> GLTFImporter::createNodeIndexToJointIndexMap(const Skin &skin) const
422{
423 const qsizetype jointCount = skin.jointNodeIndices.size();
424 QHash<qsizetype, qsizetype> nodeIndexToJointIndexMap;
425 nodeIndexToJointIndexMap.reserve(size: jointCount);
426 for (qsizetype i = 0; i < jointCount; ++i)
427 nodeIndexToJointIndexMap.insert(key: skin.jointNodeIndices[i], value: i);
428 return nodeIndexToJointIndexMap;
429}
430
431GLTFImporter::AnimationNameAndChannels GLTFImporter::createAnimationData(qsizetype animationIndex, const QString &animationName) const
432{
433 AnimationNameAndChannels nameAndChannels;
434 if (m_animations.isEmpty()) {
435 qCWarning(Jobs) << "File does not contain any animation data";
436 return nameAndChannels;
437 }
438
439 if (m_animations.size() == 1) {
440 animationIndex = 0;
441 } else if (animationIndex < 0 && !animationName.isEmpty()) {
442 for (qsizetype i = 0; i < m_animations.size(); ++i) {
443 if (m_animations[i].name == animationName) {
444 animationIndex = i;
445 break;
446 }
447 }
448 }
449
450 if (animationIndex < 0 || animationIndex >= m_animations.size()) {
451 qCWarning(Jobs) << "Invalid animation index. Skipping.";
452 return nameAndChannels;
453 }
454 const Animation &animation = m_animations[animationIndex];
455 nameAndChannels.name = animation.name;
456
457 // Create node index to joint index lookup tables for each skin
458 QList<QHash<qsizetype, qsizetype>> nodeIndexToJointIndexMaps;
459 nodeIndexToJointIndexMaps.reserve(asize: m_skins.size());
460 for (const auto &skin : m_skins)
461 nodeIndexToJointIndexMaps.push_back(t: createNodeIndexToJointIndexMap(skin));
462
463 int channelIndex = 0;
464 for (const auto &channel : animation.channels) {
465 Qt3DAnimation::Animation::Channel outputChannel;
466 outputChannel.name = gltfTargetPropertyToChannelName(propertyName: channel.targetProperty);
467
468 // Find the node index to joint index map that contains the target node and
469 // look up the joint index from it. If no such map is found, the target joint
470 // is not part of a skeleton and so we can just set the jointIndex to -1.
471 qsizetype jointIndex = -1;
472 for (const auto &map : nodeIndexToJointIndexMaps) {
473 const auto result = map.find(key: channel.targetNodeIndex);
474 if (result != map.cend()) {
475 jointIndex = result.value();
476 break;
477 }
478 }
479 outputChannel.jointIndex = jointIndex;
480
481 const auto &sampler = animation.samplers[channel.samplerIndex];
482 const auto interpolationType = gltfToQKeyFrameInterpolation(mode: sampler.interpolationMode);
483
484 if (sampler.inputAccessorIndex == -1 || sampler.outputAccessorIndex == -1) {
485 qWarning() << "Skipping channel due to invalid accessor indices in the sampler" << Qt::endl;
486 continue;
487 }
488
489 const auto &inputAccessor = m_accessors[sampler.inputAccessorIndex];
490 const auto &outputAccessor = m_accessors[sampler.outputAccessorIndex];
491
492 if (inputAccessor.type != Qt3DCore::QAttribute::Float) {
493 qWarning() << "Input accessor has wrong data type. Skipping channel.";
494 continue;
495 }
496
497 if (outputAccessor.type != Qt3DCore::QAttribute::Float) {
498 qWarning() << "Output accessor has wrong data type. Skipping channel.";
499 continue;
500 }
501
502 if (inputAccessor.count != outputAccessor.count) {
503 qWarning() << "Warning!!! Input accessor has" << inputAccessor.count
504 << "entries and output accessor has" << outputAccessor.count
505 << "entries. They should match. Please check your data.";
506 continue;
507 }
508
509 // TODO: Allow Qt 3D animation data to share timestamps between multiple
510 // channel components. I.e. allow key frame values of composite types.
511 // Doesn't give as much freedom but more efficient at runtime.
512
513 // Get the key frame times first as these are common to all components of the
514 // key frame values.
515 const int keyFrameCount = inputAccessor.count;
516 QVector<float> keyframeTimes(keyFrameCount);
517 for (int i = 0; i < keyFrameCount; ++i) {
518 const auto rawTimestamp = accessorData(accessorIndex: sampler.inputAccessorIndex, index: i);
519 keyframeTimes[i] = *reinterpret_cast<const float*>(rawTimestamp.data);
520 }
521
522 // Create a ChannelComponent for each component of the output sampler and
523 // populate it with data.
524 switch (outputAccessor.dataSize) {
525 // TODO: Handle other types as needed
526 case 3: {
527 // vec3
528 const int componentCount = 3;
529
530 // Construct the channel component names and add component to the channel
531 const QStringList suffixes
532 = (QStringList() << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z"));
533 outputChannel.channelComponents.resize(size: componentCount);
534 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
535 outputChannel.channelComponents[componentIndex].name
536 = QString(QLatin1String("%1 %2")).arg(args&: outputChannel.name,
537 args: suffixes[componentIndex]);
538 }
539
540 // Populate the fcurves in the channel components
541 for (int i = 0; i < keyFrameCount; ++i) {
542 const auto rawKeyframeValue = accessorData(accessorIndex: sampler.outputAccessorIndex, index: i);
543 QVector3D v;
544 memcpy(dest: &v, src: rawKeyframeValue.data, n: rawKeyframeValue.byteLength);
545
546 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
547 Keyframe keyFrame;
548 keyFrame.interpolation = interpolationType;
549 keyFrame.value = v[componentIndex];
550 outputChannel.channelComponents[componentIndex].fcurve.appendKeyframe(localTime: keyframeTimes[i], keyframe: keyFrame);
551 }
552 }
553
554 break;
555 } // case 3
556
557 case 4: {
558 // vec4 or quaternion
559 const int componentCount = 4;
560
561 // Construct the channel component names and add component to the channel
562 const QStringList rotationSuffixes = (QStringList()
563 << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z") << QLatin1String("W"));
564 const QStringList standardSuffixes = (QStringList()
565 << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z"));
566 const QStringList suffixes = (channel.targetProperty == QLatin1String("rotation"))
567 ? rotationSuffixes : standardSuffixes;
568 outputChannel.channelComponents.resize(size: componentCount);
569 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
570 outputChannel.channelComponents[componentIndex].name
571 = QString(QLatin1String("%1 %2")).arg(args&: outputChannel.name,
572 args: suffixes[componentIndex]);
573 }
574
575 // Populate the fcurves in the channel components
576 for (int i = 0; i < keyFrameCount; ++i) {
577 const auto rawKeyframeValue = accessorData(accessorIndex: sampler.outputAccessorIndex, index: i);
578 QVector4D v;
579 memcpy(dest: &v, src: rawKeyframeValue.data, n: rawKeyframeValue.byteLength);
580
581 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
582 Keyframe keyFrame;
583 keyFrame.interpolation = interpolationType;
584 keyFrame.value = v[componentIndex];
585 outputChannel.channelComponents[componentIndex].fcurve.appendKeyframe(localTime: keyframeTimes[i], keyframe: keyFrame);
586 }
587 }
588
589 break;
590 } // case 4
591 }
592
593 nameAndChannels.channels.push_back(t: outputChannel);
594 ++channelIndex;
595 }
596
597 return nameAndChannels;
598}
599
600GLTFImporter::RawData GLTFImporter::accessorData(int accessorIndex, int index) const
601{
602 const AccessorData &accessor = m_accessors[accessorIndex];
603 const BufferView &bufferView = m_bufferViews[accessor.bufferViewIndex];
604 const BufferData &bufferData = m_bufferDatas[bufferView.bufferIndex];
605 const QByteArray &ba = bufferData.data;
606 const char *rawData = ba.constData() + bufferView.byteOffset + accessor.byteOffset;
607
608 const uint typeSize = accessorTypeSize(componentType: accessor.type);
609 const qsizetype stride = (accessor.byteStride == 0)
610 ? accessor.dataSize * typeSize
611 : accessor.byteStride;
612
613 const char* data = rawData + index * stride;
614 if (data - rawData > ba.size()) {
615 qWarning(msg: "Attempting to access data beyond end of buffer");
616 return RawData{ .data: nullptr, .byteLength: 0 };
617 }
618
619 const quint64 byteLength = accessor.dataSize * typeSize;
620 RawData rd{ .data: data, .byteLength: byteLength };
621
622 return rd;
623}
624
625void GLTFImporter::setBasePath(const QString &path)
626{
627 m_basePath = path;
628}
629
630bool GLTFImporter::setJSON(const QJsonDocument &json)
631{
632 if (!json.isObject())
633 return false;
634 m_json = json;
635 cleanup();
636 return true;
637}
638
639bool GLTFImporter::parse()
640{
641 // Find the glTF version
642 const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject();
643 const QString versionString = asset.value(KEY_VERSION).toString();
644 const auto version = QVersionNumber::fromString(string: versionString);
645 switch (version.majorVersion()) {
646 case 2:
647 return parseGLTF2();
648
649 default:
650 qWarning() << "Unsupported version of glTF" << versionString;
651 return false;
652 }
653}
654
655bool GLTFImporter::parseGLTF2()
656{
657 bool success = true;
658 const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
659 for (const auto bufferValue : buffers)
660 success &= processJSONBuffer(json: bufferValue.toObject());
661
662 const QJsonArray bufferViews = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
663 for (const auto bufferViewValue : bufferViews)
664 success &= processJSONBufferView(json: bufferViewValue.toObject());
665
666 const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
667 for (const auto accessorValue : accessors)
668 success &= processJSONAccessor(json: accessorValue.toObject());
669
670 const QJsonArray skins = m_json.object().value(KEY_SKINS).toArray();
671 for (const auto skinValue : skins)
672 success &= processJSONSkin(json: skinValue.toObject());
673
674 const QJsonArray animations = m_json.object().value(KEY_ANIMATIONS).toArray();
675 for (const auto animationValue : animations)
676 success &= processJSONAnimation(json: animationValue.toObject());
677
678 const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
679 for (const auto nodeValue : nodes)
680 success &= processJSONNode(json: nodeValue.toObject());
681 setupNodeParentLinks();
682
683 // TODO: Make a complete GLTF 2 parser by extending to other top level elements:
684 // scenes, animations, meshes etc.
685
686 return success;
687}
688
689void GLTFImporter::cleanup()
690{
691 m_accessors.clear();
692 m_bufferViews.clear();
693 m_bufferDatas.clear();
694}
695
696bool GLTFImporter::processJSONBuffer(const QJsonObject &json)
697{
698 // Store buffer details and load data into memory
699 BufferData buffer(json);
700 buffer.data = resolveLocalData(path: buffer.path);
701 if (buffer.data.isEmpty())
702 return false;
703
704 m_bufferDatas.push_back(t: buffer);
705 return true;
706}
707
708bool GLTFImporter::processJSONBufferView(const QJsonObject &json)
709{
710 BufferView bufferView(json);
711
712 // Perform sanity checks
713 const auto bufferIndex = bufferView.bufferIndex;
714 if (Q_UNLIKELY(bufferIndex) >= m_bufferDatas.size()) {
715 qWarning(msg: "Unknown buffer %d when processing buffer view", bufferIndex);
716 return false;
717 }
718
719 const auto &bufferData = m_bufferDatas[bufferIndex];
720 if (bufferView.byteOffset > bufferData.byteLength) {
721 qWarning(msg: "Bufferview has offset greater than buffer %d length", bufferIndex);
722 return false;
723 }
724
725 if (Q_UNLIKELY(bufferView.byteOffset + bufferView.byteLength > bufferData.byteLength)) {
726 qWarning(msg: "BufferView extends beyond end of buffer %d", bufferIndex);
727 return false;
728 }
729
730 m_bufferViews.push_back(t: bufferView);
731 return true;
732}
733
734bool GLTFImporter::processJSONAccessor(const QJsonObject &json)
735{
736 AccessorData accessor(json);
737
738 // TODO: Perform sanity checks
739
740 m_accessors.push_back(t: accessor);
741 return true;
742}
743
744bool GLTFImporter::processJSONSkin(const QJsonObject &json)
745{
746 Skin skin(json);
747
748 // TODO: Perform sanity checks
749
750 m_skins.push_back(t: skin);
751 return true;
752}
753
754bool GLTFImporter::processJSONAnimation(const QJsonObject &json)
755{
756 const Animation animation(json);
757
758 for (const auto &channel : animation.channels) {
759 if (channel.samplerIndex == -1)
760 qWarning() << "Invalid sampler index in animation"
761 << animation.name << "for channel targeting node"
762 << channel.targetNodeIndex << " and property"
763 << channel.targetProperty;
764 }
765
766 for (const auto &sampler : animation.samplers) {
767 if (sampler.inputAccessorIndex == -1) {
768 qWarning() << "Sampler for animaton" << animation.name
769 << "references has an invalid input accessor index";
770 }
771
772 if (sampler.outputAccessorIndex == -1) {
773 qWarning() << "Sampler for animaton" << animation.name
774 << "references has an invalid output accessor index";
775 }
776 }
777
778 m_animations.push_back(t: animation);
779 return true;
780}
781
782bool GLTFImporter::processJSONNode(const QJsonObject &json)
783{
784 Node node(json);
785
786 // TODO: Perform sanity checks
787
788 m_nodes.push_back(t: node);
789 return true;
790}
791
792void GLTFImporter::setupNodeParentLinks()
793{
794 const qsizetype nodeCount = m_nodes.size();
795 for (qsizetype i = 0; i < nodeCount; ++i) {
796 const Node &node = m_nodes[i];
797 const QList<qsizetype> &childNodeIndices = node.childNodeIndices;
798 for (const auto childNodeIndex : childNodeIndices) {
799 Q_ASSERT(childNodeIndex < m_nodes.size());
800 Node &childNode = m_nodes[childNodeIndex];
801 Q_ASSERT(childNode.parentNodeIndex == -1);
802 childNode.parentNodeIndex = i;
803 }
804 }
805}
806
807QByteArray GLTFImporter::resolveLocalData(const QString &path) const
808{
809 QDir d(m_basePath);
810 Q_ASSERT(d.exists());
811
812 QString absPath = d.absoluteFilePath(fileName: path);
813 QFile f(absPath);
814 f.open(flags: QIODevice::ReadOnly);
815 return f.readAll();
816}
817
818} // namespace Animation
819} // namespace Qt3DAnimation
820
821QT_END_NAMESPACE
822

source code of qt3d/src/animation/backend/gltfimporter.cpp