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 for (const auto &channel : animation.channels) {
464 Qt3DAnimation::Animation::Channel outputChannel;
465 outputChannel.name = gltfTargetPropertyToChannelName(propertyName: channel.targetProperty);
466
467 // Find the node index to joint index map that contains the target node and
468 // look up the joint index from it. If no such map is found, the target joint
469 // is not part of a skeleton and so we can just set the jointIndex to -1.
470 qsizetype jointIndex = -1;
471 for (const auto &map : nodeIndexToJointIndexMaps) {
472 const auto result = map.find(key: channel.targetNodeIndex);
473 if (result != map.cend()) {
474 jointIndex = result.value();
475 break;
476 }
477 }
478 outputChannel.jointIndex = jointIndex;
479
480 const auto &sampler = animation.samplers[channel.samplerIndex];
481 const auto interpolationType = gltfToQKeyFrameInterpolation(mode: sampler.interpolationMode);
482
483 if (sampler.inputAccessorIndex == -1 || sampler.outputAccessorIndex == -1) {
484 qWarning() << "Skipping channel due to invalid accessor indices in the sampler" << Qt::endl;
485 continue;
486 }
487
488 const auto &inputAccessor = m_accessors[sampler.inputAccessorIndex];
489 const auto &outputAccessor = m_accessors[sampler.outputAccessorIndex];
490
491 if (inputAccessor.type != Qt3DCore::QAttribute::Float) {
492 qWarning() << "Input accessor has wrong data type. Skipping channel.";
493 continue;
494 }
495
496 if (outputAccessor.type != Qt3DCore::QAttribute::Float) {
497 qWarning() << "Output accessor has wrong data type. Skipping channel.";
498 continue;
499 }
500
501 if (inputAccessor.count != outputAccessor.count) {
502 qWarning() << "Warning!!! Input accessor has" << inputAccessor.count
503 << "entries and output accessor has" << outputAccessor.count
504 << "entries. They should match. Please check your data.";
505 continue;
506 }
507
508 // TODO: Allow Qt 3D animation data to share timestamps between multiple
509 // channel components. I.e. allow key frame values of composite types.
510 // Doesn't give as much freedom but more efficient at runtime.
511
512 // Get the key frame times first as these are common to all components of the
513 // key frame values.
514 const int keyFrameCount = inputAccessor.count;
515 QVector<float> keyframeTimes(keyFrameCount);
516 for (int i = 0; i < keyFrameCount; ++i) {
517 const auto rawTimestamp = accessorData(accessorIndex: sampler.inputAccessorIndex, index: i);
518 keyframeTimes[i] = *reinterpret_cast<const float*>(rawTimestamp.data);
519 }
520
521 // Create a ChannelComponent for each component of the output sampler and
522 // populate it with data.
523 switch (outputAccessor.dataSize) {
524 // TODO: Handle other types as needed
525 case 3: {
526 // vec3
527 const int componentCount = 3;
528
529 // Construct the channel component names and add component to the channel
530 const QStringList suffixes
531 = (QStringList() << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z"));
532 outputChannel.channelComponents.resize(size: componentCount);
533 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
534 outputChannel.channelComponents[componentIndex].name
535 = QString(QLatin1String("%1 %2")).arg(args&: outputChannel.name,
536 args: suffixes[componentIndex]);
537 }
538
539 // Populate the fcurves in the channel components
540 for (int i = 0; i < keyFrameCount; ++i) {
541 const auto rawKeyframeValue = accessorData(accessorIndex: sampler.outputAccessorIndex, index: i);
542 QVector3D v;
543 memcpy(dest: &v, src: rawKeyframeValue.data, n: rawKeyframeValue.byteLength);
544
545 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
546 Keyframe keyFrame;
547 keyFrame.interpolation = interpolationType;
548 keyFrame.value = v[componentIndex];
549 outputChannel.channelComponents[componentIndex].fcurve.appendKeyframe(localTime: keyframeTimes[i], keyframe: keyFrame);
550 }
551 }
552
553 break;
554 } // case 3
555
556 case 4: {
557 // vec4 or quaternion
558 const int componentCount = 4;
559
560 // Construct the channel component names and add component to the channel
561 const QStringList rotationSuffixes = (QStringList()
562 << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z") << QLatin1String("W"));
563 const QStringList standardSuffixes = (QStringList()
564 << QLatin1String("X") << QLatin1String("Y") << QLatin1String("Z"));
565 const QStringList suffixes = (channel.targetProperty == QLatin1String("rotation"))
566 ? rotationSuffixes : standardSuffixes;
567 outputChannel.channelComponents.resize(size: componentCount);
568 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
569 outputChannel.channelComponents[componentIndex].name
570 = QString(QLatin1String("%1 %2")).arg(args&: outputChannel.name,
571 args: suffixes[componentIndex]);
572 }
573
574 // Populate the fcurves in the channel components
575 for (int i = 0; i < keyFrameCount; ++i) {
576 const auto rawKeyframeValue = accessorData(accessorIndex: sampler.outputAccessorIndex, index: i);
577 QVector4D v;
578 memcpy(dest: &v, src: rawKeyframeValue.data, n: rawKeyframeValue.byteLength);
579
580 for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
581 Keyframe keyFrame;
582 keyFrame.interpolation = interpolationType;
583 keyFrame.value = v[componentIndex];
584 outputChannel.channelComponents[componentIndex].fcurve.appendKeyframe(localTime: keyframeTimes[i], keyframe: keyFrame);
585 }
586 }
587
588 break;
589 } // case 4
590 }
591
592 nameAndChannels.channels.push_back(t: outputChannel);
593 }
594
595 return nameAndChannels;
596}
597
598GLTFImporter::RawData GLTFImporter::accessorData(int accessorIndex, int index) const
599{
600 const AccessorData &accessor = m_accessors[accessorIndex];
601 const BufferView &bufferView = m_bufferViews[accessor.bufferViewIndex];
602 const BufferData &bufferData = m_bufferDatas[bufferView.bufferIndex];
603 const QByteArray &ba = bufferData.data;
604 const char *rawData = ba.constData() + bufferView.byteOffset + accessor.byteOffset;
605
606 const uint typeSize = accessorTypeSize(componentType: accessor.type);
607 const qsizetype stride = (accessor.byteStride == 0)
608 ? accessor.dataSize * typeSize
609 : accessor.byteStride;
610
611 const char* data = rawData + index * stride;
612 if (data - rawData > ba.size()) {
613 qWarning(msg: "Attempting to access data beyond end of buffer");
614 return RawData{ .data: nullptr, .byteLength: 0 };
615 }
616
617 const quint64 byteLength = accessor.dataSize * typeSize;
618 RawData rd{ .data: data, .byteLength: byteLength };
619
620 return rd;
621}
622
623void GLTFImporter::setBasePath(const QString &path)
624{
625 m_basePath = path;
626}
627
628bool GLTFImporter::setJSON(const QJsonDocument &json)
629{
630 if (!json.isObject())
631 return false;
632 m_json = json;
633 cleanup();
634 return true;
635}
636
637bool GLTFImporter::parse()
638{
639 // Find the glTF version
640 const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject();
641 const QString versionString = asset.value(KEY_VERSION).toString();
642 const auto version = QVersionNumber::fromString(string: versionString);
643 switch (version.majorVersion()) {
644 case 2:
645 return parseGLTF2();
646
647 default:
648 qWarning() << "Unsupported version of glTF" << versionString;
649 return false;
650 }
651}
652
653bool GLTFImporter::parseGLTF2()
654{
655 bool success = true;
656 const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
657 for (const auto bufferValue : buffers)
658 success &= processJSONBuffer(json: bufferValue.toObject());
659
660 const QJsonArray bufferViews = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
661 for (const auto bufferViewValue : bufferViews)
662 success &= processJSONBufferView(json: bufferViewValue.toObject());
663
664 const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
665 for (const auto accessorValue : accessors)
666 success &= processJSONAccessor(json: accessorValue.toObject());
667
668 const QJsonArray skins = m_json.object().value(KEY_SKINS).toArray();
669 for (const auto skinValue : skins)
670 success &= processJSONSkin(json: skinValue.toObject());
671
672 const QJsonArray animations = m_json.object().value(KEY_ANIMATIONS).toArray();
673 for (const auto animationValue : animations)
674 success &= processJSONAnimation(json: animationValue.toObject());
675
676 const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
677 for (const auto nodeValue : nodes)
678 success &= processJSONNode(json: nodeValue.toObject());
679 setupNodeParentLinks();
680
681 // TODO: Make a complete GLTF 2 parser by extending to other top level elements:
682 // scenes, animations, meshes etc.
683
684 return success;
685}
686
687void GLTFImporter::cleanup()
688{
689 m_accessors.clear();
690 m_bufferViews.clear();
691 m_bufferDatas.clear();
692}
693
694bool GLTFImporter::processJSONBuffer(const QJsonObject &json)
695{
696 // Store buffer details and load data into memory
697 BufferData buffer(json);
698 buffer.data = resolveLocalData(path: buffer.path);
699 if (buffer.data.isEmpty())
700 return false;
701
702 m_bufferDatas.push_back(t: buffer);
703 return true;
704}
705
706bool GLTFImporter::processJSONBufferView(const QJsonObject &json)
707{
708 BufferView bufferView(json);
709
710 // Perform sanity checks
711 const auto bufferIndex = bufferView.bufferIndex;
712 if (Q_UNLIKELY(bufferIndex) >= m_bufferDatas.size()) {
713 qWarning(msg: "Unknown buffer %d when processing buffer view", bufferIndex);
714 return false;
715 }
716
717 const auto &bufferData = m_bufferDatas[bufferIndex];
718 if (bufferView.byteOffset > bufferData.byteLength) {
719 qWarning(msg: "Bufferview has offset greater than buffer %d length", bufferIndex);
720 return false;
721 }
722
723 if (Q_UNLIKELY(bufferView.byteOffset + bufferView.byteLength > bufferData.byteLength)) {
724 qWarning(msg: "BufferView extends beyond end of buffer %d", bufferIndex);
725 return false;
726 }
727
728 m_bufferViews.push_back(t: bufferView);
729 return true;
730}
731
732bool GLTFImporter::processJSONAccessor(const QJsonObject &json)
733{
734 AccessorData accessor(json);
735
736 // TODO: Perform sanity checks
737
738 m_accessors.push_back(t: accessor);
739 return true;
740}
741
742bool GLTFImporter::processJSONSkin(const QJsonObject &json)
743{
744 Skin skin(json);
745
746 // TODO: Perform sanity checks
747
748 m_skins.push_back(t: skin);
749 return true;
750}
751
752bool GLTFImporter::processJSONAnimation(const QJsonObject &json)
753{
754 const Animation animation(json);
755
756 for (const auto &channel : animation.channels) {
757 if (channel.samplerIndex == -1)
758 qWarning() << "Invalid sampler index in animation"
759 << animation.name << "for channel targeting node"
760 << channel.targetNodeIndex << " and property"
761 << channel.targetProperty;
762 }
763
764 for (const auto &sampler : animation.samplers) {
765 if (sampler.inputAccessorIndex == -1) {
766 qWarning() << "Sampler for animaton" << animation.name
767 << "references has an invalid input accessor index";
768 }
769
770 if (sampler.outputAccessorIndex == -1) {
771 qWarning() << "Sampler for animaton" << animation.name
772 << "references has an invalid output accessor index";
773 }
774 }
775
776 m_animations.push_back(t: animation);
777 return true;
778}
779
780bool GLTFImporter::processJSONNode(const QJsonObject &json)
781{
782 Node node(json);
783
784 // TODO: Perform sanity checks
785
786 m_nodes.push_back(t: node);
787 return true;
788}
789
790void GLTFImporter::setupNodeParentLinks()
791{
792 const qsizetype nodeCount = m_nodes.size();
793 for (qsizetype i = 0; i < nodeCount; ++i) {
794 const Node &node = m_nodes[i];
795 const QList<qsizetype> &childNodeIndices = node.childNodeIndices;
796 for (const auto childNodeIndex : childNodeIndices) {
797 Q_ASSERT(childNodeIndex < m_nodes.size());
798 Node &childNode = m_nodes[childNodeIndex];
799 Q_ASSERT(childNode.parentNodeIndex == -1);
800 childNode.parentNodeIndex = i;
801 }
802 }
803}
804
805QByteArray GLTFImporter::resolveLocalData(const QString &path) const
806{
807 QDir d(m_basePath);
808 Q_ASSERT(d.exists());
809
810 QString absPath = d.absoluteFilePath(fileName: path);
811 QFile f(absPath);
812 f.open(flags: QIODevice::ReadOnly);
813 return f.readAll();
814}
815
816} // namespace Animation
817} // namespace Qt3DAnimation
818
819QT_END_NAMESPACE
820

Provided by KDAB

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

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