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 | |
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 | |
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 | |
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 | |
106 | void 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 | |
114 | void 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 | |
163 | GLTFImporter::BufferData::BufferData() |
164 | : byteLength(0) |
165 | , data() |
166 | { |
167 | } |
168 | |
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 | |
176 | GLTFImporter::BufferView::BufferView() |
177 | : byteOffset(0) |
178 | , byteLength(0) |
179 | , bufferIndex(-1) |
180 | , target(0) |
181 | { |
182 | } |
183 | |
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 | |
195 | GLTFImporter::AccessorData::AccessorData() |
196 | : type(Qt3DRender::QAttribute::Float) |
197 | , dataSize(0) |
198 | , count(0) |
199 | , byteOffset(0) |
200 | , byteStride(0) |
201 | { |
202 | } |
203 | |
204 | GLTFImporter::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 | |
220 | GLTFImporter::Skin::Skin() |
221 | : inverseBindAccessorIndex(-1) |
222 | , jointNodeIndices() |
223 | { |
224 | } |
225 | |
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(asize: jointNodes.size()); |
232 | for (const auto jointNodeValue : jointNodes) |
233 | jointNodeIndices.push_back(t: jointNodeValue.toInt()); |
234 | } |
235 | |
236 | GLTFImporter::Channel::Channel() |
237 | : samplerIndex(-1) |
238 | , targetNodeIndex(-1) |
239 | , targetProperty() |
240 | { |
241 | } |
242 | |
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 | |
253 | GLTFImporter::Sampler::Sampler() |
254 | : inputAccessorIndex(-1) |
255 | , outputAccessorIndex(-1) |
256 | , interpolationMode(Linear) |
257 | { |
258 | } |
259 | |
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 | |
276 | 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 | |
288 | GLTFImporter::Animation::Animation() |
289 | : name() |
290 | , channels() |
291 | , samplers() |
292 | { |
293 | } |
294 | |
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(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 | |
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 | |
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(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 | |
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(msg: "unsupported accessor type %d" , componentType); |
389 | return Qt3DRender::QAttribute::Float; |
390 | } |
391 | |
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(msg: "Unhandled accessor data type %d" , componentType); |
409 | return 0; |
410 | } |
411 | } |
412 | |
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 | |
434 | GLTFImporter::GLTFImporter() |
435 | { |
436 | } |
437 | |
438 | bool 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 | |
454 | 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(asize: jointCount); |
459 | for (int i = 0; i < jointCount; ++i) |
460 | nodeIndexToJointIndexMap.insert(akey: skin.jointNodeIndices[i], avalue: i); |
461 | return nodeIndexToJointIndexMap; |
462 | } |
463 | |
464 | 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(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 | |
633 | 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(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 | |
658 | void GLTFImporter::setBasePath(const QString &path) |
659 | { |
660 | m_basePath = path; |
661 | } |
662 | |
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 | |
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(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 | |
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(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 | |
722 | void GLTFImporter::cleanup() |
723 | { |
724 | m_accessors.clear(); |
725 | m_bufferViews.clear(); |
726 | m_bufferDatas.clear(); |
727 | } |
728 | |
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(path: buffer.path); |
734 | if (buffer.data.isEmpty()) |
735 | return false; |
736 | |
737 | m_bufferDatas.push_back(t: buffer); |
738 | return true; |
739 | } |
740 | |
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(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 | |
767 | bool 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 | |
777 | bool 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 | |
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(t: animation); |
812 | return true; |
813 | } |
814 | |
815 | bool 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 | |
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 | |
840 | QByteArray 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 | |
854 | QT_END_NAMESPACE |
855 | |