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

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