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 
56 QT_BEGIN_NAMESPACE
57 
58 namespace Qt3DAnimation {
59 namespace Animation {
60 
61 namespace {
62 
gltfTargetPropertyToChannelName(const QString & propertyName)63 QString 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 
gltfToQKeyFrameInterpolation(GLTFImporter::Sampler::InterpolationMode mode)76 QKeyFrame::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 
jsonArrayToSqt(const QJsonArray & jsonArray,Qt3DCore::Sqt & sqt)94 void 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 
jsonArrayToVector3D(const QJsonArray & jsonArray,QVector3D & v)106 void jsonArrayToVector3D(const QJsonArray &jsonArray, QVector3D &v)
107 {
108     Q_ASSERT(jsonArray.size() == 3);
109     v.setX(static_cast<float>(jsonArray.at(0).toDouble()));
110     v.setY(static_cast<float>(jsonArray.at(1).toDouble()));
111     v.setZ(static_cast<float>(jsonArray.at(2).toDouble()));
112 }
113 
jsonArrayToQuaternion(const QJsonArray & jsonArray,QQuaternion & q)114 void jsonArrayToQuaternion(const QJsonArray &jsonArray, QQuaternion &q)
115 {
116     Q_ASSERT(jsonArray.size() == 4);
117     q.setX(static_cast<float>(jsonArray.at(0).toDouble()));
118     q.setY(static_cast<float>(jsonArray.at(1).toDouble()));
119     q.setZ(static_cast<float>(jsonArray.at(2).toDouble()));
120     q.setScalar(static_cast<float>(jsonArray.at(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 
BufferData()163 GLTFImporter::BufferData::BufferData()
164     : byteLength(0)
165     , data()
166 {
167 }
168 
BufferData(const QJsonObject & json)169 GLTFImporter::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 
BufferView()176 GLTFImporter::BufferView::BufferView()
177     : byteOffset(0)
178     , byteLength(0)
179     , bufferIndex(-1)
180     , target(0)
181 {
182 }
183 
BufferView(const QJsonObject & json)184 GLTFImporter::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 
AccessorData()195 GLTFImporter::AccessorData::AccessorData()
196     : type(Qt3DRender::QAttribute::Float)
197     , dataSize(0)
198     , count(0)
199     , byteOffset(0)
200     , byteStride(0)
201 {
202 }
203 
AccessorData(const QJsonObject & json)204 GLTFImporter::AccessorData::AccessorData(const QJsonObject &json)
205     : bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(-1))
206     , type(accessorTypeFromJSON(json.value(KEY_COMPONENT_TYPE).toInt()))
207     , dataSize(accessorDataSizeFromJson(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 
Skin()220 GLTFImporter::Skin::Skin()
221     : inverseBindAccessorIndex(-1)
222     , jointNodeIndices()
223 {
224 }
225 
Skin(const QJsonObject & json)226 GLTFImporter::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(jointNodes.size());
232     for (const auto jointNodeValue : jointNodes)
233         jointNodeIndices.push_back(jointNodeValue.toInt());
234 }
235 
Channel()236 GLTFImporter::Channel::Channel()
237     : samplerIndex(-1)
238     , targetNodeIndex(-1)
239     , targetProperty()
240 {
241 }
242 
Channel(const QJsonObject & json)243 GLTFImporter::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 
Sampler()253 GLTFImporter::Sampler::Sampler()
254     : inputAccessorIndex(-1)
255     , outputAccessorIndex(-1)
256     , interpolationMode(Linear)
257 {
258 }
259 
Sampler(const QJsonObject & json)260 GLTFImporter::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 
interpolationModeString() const276 QString 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 
Animation()288 GLTFImporter::Animation::Animation()
289     : name()
290     , channels()
291     , samplers()
292 {
293 }
294 
Animation(const QJsonObject & json)295 GLTFImporter::Animation::Animation(const QJsonObject &json)
296     : name(json.value(KEY_NAME).toString())
297 {
298     QJsonArray channelsArray = json.value(KEY_CHANNELS).toArray();
299     channels.reserve(channelsArray.size());
300     for (const auto channelValue : channelsArray) {
301         Channel channel(channelValue.toObject());
302         channels.push_back(channel);
303     }
304 
305     QJsonArray samplersArray = json.value(KEY_SAMPLERS).toArray();
306     samplers.reserve(samplersArray.size());
307     for (const auto samplerValue : samplersArray) {
308         Sampler sampler(samplerValue.toObject());
309         samplers.push_back(sampler);
310     }
311 }
312 
Node()313 GLTFImporter::Node::Node()
314     : localTransform()
315     , childNodeIndices()
316     , name()
317     , parentNodeIndex(-1)
318     , cameraIndex(-1)
319     , meshIndex(-1)
320     , skinIndex(-1)
321 {
322 }
323 
Node(const QJsonObject & json)324 GLTFImporter::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(childNodes.size());
336     for (const auto childNodeValue : childNodes)
337         childNodeIndices.push_back(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(matrixValue.toArray(), 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(scaleValue.toArray(), localTransform.scale);
350 
351         if (!rotationValue.isUndefined())
352             jsonArrayToQuaternion(json.value(KEY_ROTATION).toArray(), localTransform.rotation);
353 
354         if (!translationValue.isUndefined())
355             jsonArrayToVector3D(json.value(KEY_TRANSLATION).toArray(), 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 
accessorTypeFromJSON(int componentType)372 Qt3DRender::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("unsupported accessor type %d", componentType);
389     return Qt3DRender::QAttribute::Float;
390 }
391 
accessorTypeSize(Qt3DRender::QAttribute::VertexBaseType componentType)392 uint 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("Unhandled accessor data type %d", componentType);
409         return 0;
410     }
411 }
412 
accessorDataSizeFromJson(const QString & type)413 uint 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 
GLTFImporter()434 GLTFImporter::GLTFImporter()
435 {
436 }
437 
load(QIODevice * ioDev)438 bool GLTFImporter::load(QIODevice *ioDev)
439 {
440     if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) {
441         qWarning("not a JSON document");
442         return false;
443     }
444 
445     auto file = qobject_cast<QFile*>(ioDev);
446     if (file) {
447         QFileInfo finfo(file->fileName());
448         setBasePath(finfo.dir().absolutePath());
449     }
450 
451     return parse();
452 }
453 
createNodeIndexToJointIndexMap(const Skin & skin) const454 QHash<int, int> GLTFImporter::createNodeIndexToJointIndexMap(const Skin &skin) const
455 {
456     const int jointCount = skin.jointNodeIndices.size();
457     QHash<int, int> nodeIndexToJointIndexMap;
458     nodeIndexToJointIndexMap.reserve(jointCount);
459     for (int i = 0; i < jointCount; ++i)
460         nodeIndexToJointIndexMap.insert(skin.jointNodeIndices[i], i);
461     return nodeIndexToJointIndexMap;
462 }
463 
createAnimationData(int animationIndex,const QString & animationName) const464 GLTFImporter::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(m_skins.size());
493     for (const auto &skin : m_skins)
494         nodeIndexToJointIndexMaps.push_back(createNodeIndexToJointIndexMap(skin));
495 
496     int channelIndex = 0;
497     for (const auto &channel : animation.channels) {
498         Qt3DAnimation::Animation::Channel outputChannel;
499         outputChannel.name = gltfTargetPropertyToChannelName(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(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(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(sampler.inputAccessorIndex, 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(componentCount);
567             for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
568                 outputChannel.channelComponents[componentIndex].name
569                         = QString(QLatin1String("%1 %2")).arg(outputChannel.name,
570                                                               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(sampler.outputAccessorIndex, i);
576                 QVector3D v;
577                 memcpy(&v, rawKeyframeValue.data, 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(keyframeTimes[i], 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(componentCount);
602             for (int componentIndex = 0; componentIndex < componentCount; ++componentIndex) {
603                 outputChannel.channelComponents[componentIndex].name
604                         = QString(QLatin1String("%1 %2")).arg(outputChannel.name,
605                                                               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(sampler.outputAccessorIndex, i);
611                 QVector4D v;
612                 memcpy(&v, rawKeyframeValue.data, 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(keyframeTimes[i], keyFrame);
619                 }
620             }
621 
622             break;
623         } // case 4
624         }
625 
626         nameAndChannels.channels.push_back(outputChannel);
627         ++channelIndex;
628     }
629 
630     return nameAndChannels;
631 }
632 
accessorData(int accessorIndex,int index) const633 GLTFImporter::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(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("Attempting to access data beyond end of buffer");
649         return RawData{ nullptr, 0 };
650     }
651 
652     const quint64 byteLength = accessor.dataSize * typeSize;
653     RawData rd{ data, byteLength };
654 
655     return rd;
656 }
657 
setBasePath(const QString & path)658 void GLTFImporter::setBasePath(const QString &path)
659 {
660     m_basePath = path;
661 }
662 
setJSON(const QJsonDocument & json)663 bool GLTFImporter::setJSON(const QJsonDocument &json)
664 {
665     if (!json.isObject())
666         return false;
667     m_json = json;
668     cleanup();
669     return true;
670 }
671 
parse()672 bool 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(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 
parseGLTF2()688 bool 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(bufferValue.toObject());
694 
695     const QJsonArray bufferViews = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
696     for (const auto &bufferViewValue : bufferViews)
697         success &= processJSONBufferView(bufferViewValue.toObject());
698 
699     const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
700     for (const auto &accessorValue : accessors)
701         success &= processJSONAccessor(accessorValue.toObject());
702 
703     const QJsonArray skins = m_json.object().value(KEY_SKINS).toArray();
704     for (const auto &skinValue : skins)
705         success &= processJSONSkin(skinValue.toObject());
706 
707     const QJsonArray animations = m_json.object().value(KEY_ANIMATIONS).toArray();
708     for (const auto &animationValue : animations)
709         success &= processJSONAnimation(animationValue.toObject());
710 
711     const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
712     for (const auto &nodeValue : nodes)
713         success &= processJSONNode(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 
cleanup()722 void GLTFImporter::cleanup()
723 {
724     m_accessors.clear();
725     m_bufferViews.clear();
726     m_bufferDatas.clear();
727 }
728 
processJSONBuffer(const QJsonObject & json)729 bool GLTFImporter::processJSONBuffer(const QJsonObject &json)
730 {
731     // Store buffer details and load data into memory
732     BufferData buffer(json);
733     buffer.data = resolveLocalData(buffer.path);
734     if (buffer.data.isEmpty())
735         return false;
736 
737     m_bufferDatas.push_back(buffer);
738     return true;
739 }
740 
processJSONBufferView(const QJsonObject & json)741 bool 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("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("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("BufferView extends beyond end of buffer %d", bufferIndex);
760         return false;
761     }
762 
763     m_bufferViews.push_back(bufferView);
764     return true;
765 }
766 
processJSONAccessor(const QJsonObject & json)767 bool GLTFImporter::processJSONAccessor(const QJsonObject &json)
768 {
769     AccessorData accessor(json);
770 
771     // TODO: Perform sanity checks
772 
773     m_accessors.push_back(accessor);
774     return true;
775 }
776 
processJSONSkin(const QJsonObject & json)777 bool GLTFImporter::processJSONSkin(const QJsonObject &json)
778 {
779     Skin skin(json);
780 
781     // TODO: Perform sanity checks
782 
783     m_skins.push_back(skin);
784     return true;
785 }
786 
processJSONAnimation(const QJsonObject & json)787 bool 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(animation);
812     return true;
813 }
814 
processJSONNode(const QJsonObject & json)815 bool GLTFImporter::processJSONNode(const QJsonObject &json)
816 {
817     Node node(json);
818 
819     // TODO: Perform sanity checks
820 
821     m_nodes.push_back(node);
822     return true;
823 }
824 
setupNodeParentLinks()825 void 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 
resolveLocalData(const QString & path) const840 QByteArray GLTFImporter::resolveLocalData(const QString &path) const
841 {
842     QDir d(m_basePath);
843     Q_ASSERT(d.exists());
844 
845     QString absPath = d.absoluteFilePath(path);
846     QFile f(absPath);
847     f.open(QIODevice::ReadOnly);
848     return f.readAll();
849 }
850 
851 } // namespace Animation
852 } // namespace Qt3DAnimation
853 
854 QT_END_NAMESPACE
855