| 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 "gltfskeletonloader_p.h" | 
| 38 |  | 
| 39 | #include <qopengl.h> | 
| 40 | #include <QtCore/qdir.h> | 
| 41 | #include <QtCore/qfile.h> | 
| 42 | #include <QtCore/qfileinfo.h> | 
| 43 | #include <QtCore/qiodevice.h> | 
| 44 | #include <QtCore/qjsonarray.h> | 
| 45 | #include <QtCore/qjsonobject.h> | 
| 46 | #include <QtCore/qjsonvalue.h> | 
| 47 | #include <QtCore/qversionnumber.h> | 
| 48 |  | 
| 49 | #include <Qt3DRender/private/renderlogging_p.h> | 
| 50 | #include <Qt3DCore/private/qmath3d_p.h> | 
| 51 | #include <Qt3DCore/private/qloadgltf_p.h> | 
| 52 |  | 
| 53 | QT_BEGIN_NAMESPACE | 
| 54 |  | 
| 55 | namespace { | 
| 56 |  | 
| 57 | void jsonArrayToSqt(const QJsonArray &jsonArray, Qt3DCore::Sqt &sqt) | 
| 58 | { | 
| 59 |     Q_ASSERT(jsonArray.size() == 16); | 
| 60 |     QMatrix4x4 m; | 
| 61 |     float *data = m.data(); | 
| 62 |     int i = 0; | 
| 63 |     for (const auto &element : jsonArray) | 
| 64 |         *(data + i++) = static_cast<float>(element.toDouble()); | 
| 65 |  | 
| 66 |     decomposeQMatrix4x4(m, sqt); | 
| 67 | } | 
| 68 |  | 
| 69 | void jsonArrayToVector3D(const QJsonArray &jsonArray, QVector3D &v) | 
| 70 | { | 
| 71 |     Q_ASSERT(jsonArray.size() == 3); | 
| 72 |     v.setX(static_cast<float>(jsonArray.at(i: 0).toDouble())); | 
| 73 |     v.setY(static_cast<float>(jsonArray.at(i: 1).toDouble())); | 
| 74 |     v.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble())); | 
| 75 | } | 
| 76 |  | 
| 77 | void jsonArrayToQuaternion(const QJsonArray &jsonArray, QQuaternion &q) | 
| 78 | { | 
| 79 |     Q_ASSERT(jsonArray.size() == 4); | 
| 80 |     q.setX(static_cast<float>(jsonArray.at(i: 0).toDouble())); | 
| 81 |     q.setY(static_cast<float>(jsonArray.at(i: 1).toDouble())); | 
| 82 |     q.setZ(static_cast<float>(jsonArray.at(i: 2).toDouble())); | 
| 83 |     q.setScalar(static_cast<float>(jsonArray.at(i: 3).toDouble())); | 
| 84 | } | 
| 85 |  | 
| 86 | } | 
| 87 |  | 
| 88 | namespace Qt3DRender { | 
| 89 | namespace Render { | 
| 90 |  | 
| 91 | #define KEY_ACCESSORS               QLatin1String("accessors") | 
| 92 | #define KEY_ASSET                   QLatin1String("asset") | 
| 93 | #define KEY_BUFFER                  QLatin1String("buffer") | 
| 94 | #define KEY_BUFFERS                 QLatin1String("buffers") | 
| 95 | #define KEY_BUFFER_VIEW             QLatin1String("bufferView") | 
| 96 | #define KEY_BUFFER_VIEWS            QLatin1String("bufferViews") | 
| 97 | #define KEY_BYTE_LENGTH             QLatin1String("byteLength") | 
| 98 | #define KEY_BYTE_OFFSET             QLatin1String("byteOffset") | 
| 99 | #define KEY_BYTE_STRIDE             QLatin1String("byteStride") | 
| 100 | #define KEY_CAMERA                  QLatin1String("camera") | 
| 101 | #define KEY_CHILDREN                QLatin1String("children") | 
| 102 | #define KEY_COMPONENT_TYPE          QLatin1String("componentType") | 
| 103 | #define KEY_COUNT                   QLatin1String("count") | 
| 104 | #define KEY_JOINTS                  QLatin1String("joints") | 
| 105 | #define KEY_INVERSE_BIND_MATRICES   QLatin1String("inverseBindMatrices") | 
| 106 | #define KEY_MATRIX                  QLatin1String("matrix") | 
| 107 | #define KEY_MESH                    QLatin1String("mesh") | 
| 108 | #define KEY_NAME                    QLatin1String("name") | 
| 109 | #define KEY_NODES                   QLatin1String("nodes") | 
| 110 | #define KEY_ROTATION                QLatin1String("rotation") | 
| 111 | #define KEY_SCALE                   QLatin1String("scale") | 
| 112 | #define KEY_SKIN                    QLatin1String("skin") | 
| 113 | #define KEY_SKINS                   QLatin1String("skins") | 
| 114 | #define KEY_TARGET                  QLatin1String("target") | 
| 115 | #define KEY_TRANSLATION             QLatin1String("translation") | 
| 116 | #define KEY_TYPE                    QLatin1String("type") | 
| 117 | #define KEY_URI                     QLatin1String("uri") | 
| 118 | #define KEY_VERSION                 QLatin1String("version") | 
| 119 |  | 
| 120 | GLTFSkeletonLoader::BufferData::BufferData() | 
| 121 |     : byteLength(0) | 
| 122 |     , data() | 
| 123 | { | 
| 124 | } | 
| 125 |  | 
| 126 | GLTFSkeletonLoader::BufferData::BufferData(const QJsonObject &json) | 
| 127 |     : byteLength(json.value(KEY_BYTE_LENGTH).toInt()) | 
| 128 |     , path(json.value(KEY_URI).toString()) | 
| 129 |     , data() | 
| 130 | { | 
| 131 | } | 
| 132 |  | 
| 133 | GLTFSkeletonLoader::BufferView::BufferView() | 
| 134 |     : bufferIndex(-1) | 
| 135 |     , byteOffset(0) | 
| 136 |     , byteLength(0) | 
| 137 |     , target(0) | 
| 138 | { | 
| 139 | } | 
| 140 |  | 
| 141 | GLTFSkeletonLoader::BufferView::BufferView(const QJsonObject &json) | 
| 142 |     : bufferIndex(json.value(KEY_BUFFER).toInt()) | 
| 143 |     , byteOffset(json.value(KEY_BYTE_OFFSET).toInt()) | 
| 144 |     , byteLength(json.value(KEY_BYTE_LENGTH).toInt()) | 
| 145 |     , target(0) | 
| 146 | { | 
| 147 |     const auto targetValue = json.value(KEY_TARGET); | 
| 148 |     if (!targetValue.isUndefined()) | 
| 149 |         target = targetValue.toInt(); | 
| 150 | } | 
| 151 |  | 
| 152 | GLTFSkeletonLoader::AccessorData::AccessorData() | 
| 153 |     : type(QAttribute::Float) | 
| 154 |     , dataSize(0) | 
| 155 |     , count(0) | 
| 156 |     , byteOffset(0) | 
| 157 |     , byteStride(0) | 
| 158 | { | 
| 159 | } | 
| 160 |  | 
| 161 | GLTFSkeletonLoader::AccessorData::AccessorData(const QJsonObject &json) | 
| 162 |     : bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(defaultValue: -1)) | 
| 163 |     , type(accessorTypeFromJSON(componentType: json.value(KEY_COMPONENT_TYPE).toInt())) | 
| 164 |     , dataSize(accessorDataSizeFromJson(type: json.value(KEY_TYPE).toString())) | 
| 165 |     , count(json.value(KEY_COUNT).toInt()) | 
| 166 |     , byteOffset(0) | 
| 167 |     , byteStride(0) | 
| 168 | { | 
| 169 |     const auto byteOffsetValue = json.value(KEY_BYTE_OFFSET); | 
| 170 |     if (!byteOffsetValue.isUndefined()) | 
| 171 |         byteOffset = byteOffsetValue.toInt(); | 
| 172 |     const auto byteStrideValue = json.value(KEY_BYTE_STRIDE); | 
| 173 |     if (!byteStrideValue.isUndefined()) | 
| 174 |         byteStride = byteStrideValue.toInt(); | 
| 175 | } | 
| 176 |  | 
| 177 | GLTFSkeletonLoader::Skin::Skin() | 
| 178 |     : inverseBindAccessorIndex(-1) | 
| 179 |     , jointNodeIndices() | 
| 180 | { | 
| 181 | } | 
| 182 |  | 
| 183 | GLTFSkeletonLoader::Skin::Skin(const QJsonObject &json) | 
| 184 |     : name(json.value(KEY_NAME).toString()) | 
| 185 |     , inverseBindAccessorIndex(json.value(KEY_INVERSE_BIND_MATRICES).toInt()) | 
| 186 | { | 
| 187 |     QJsonArray jointNodes = json.value(KEY_JOINTS).toArray(); | 
| 188 |     jointNodeIndices.reserve(asize: jointNodes.size()); | 
| 189 |     for (const auto jointNodeValue : jointNodes) | 
| 190 |         jointNodeIndices.push_back(t: jointNodeValue.toInt()); | 
| 191 | } | 
| 192 |  | 
| 193 | GLTFSkeletonLoader::Node::Node() | 
| 194 |     : localTransform() | 
| 195 |     , childNodeIndices() | 
| 196 |     , name() | 
| 197 |     , parentNodeIndex(-1) | 
| 198 |     , cameraIndex(-1) | 
| 199 |     , meshIndex(-1) | 
| 200 |     , skinIndex(-1) | 
| 201 | { | 
| 202 | } | 
| 203 |  | 
| 204 | GLTFSkeletonLoader::Node::Node(const QJsonObject &json) | 
| 205 |     : localTransform() | 
| 206 |     , childNodeIndices() | 
| 207 |     , name(json.value(KEY_NAME).toString()) | 
| 208 |     , parentNodeIndex(-1) | 
| 209 |     , cameraIndex(-1) | 
| 210 |     , meshIndex(-1) | 
| 211 |     , skinIndex(-1) | 
| 212 | { | 
| 213 |     // Child nodes - we setup the parent links in a later pass | 
| 214 |     QJsonArray childNodes = json.value(KEY_CHILDREN).toArray(); | 
| 215 |     childNodeIndices.reserve(asize: childNodes.size()); | 
| 216 |     for (const auto childNodeValue : childNodes) | 
| 217 |         childNodeIndices.push_back(t: childNodeValue.toInt()); | 
| 218 |  | 
| 219 |     // Local transform - matrix or scale, rotation, translation | 
| 220 |     const auto matrixValue = json.value(KEY_MATRIX); | 
| 221 |     if (!matrixValue.isUndefined()) { | 
| 222 |         jsonArrayToSqt(jsonArray: matrixValue.toArray(), sqt&: localTransform); | 
| 223 |     } else { | 
| 224 |         const auto scaleValue = json.value(KEY_SCALE); | 
| 225 |         const auto rotationValue = json.value(KEY_ROTATION); | 
| 226 |         const auto translationValue = json.value(KEY_TRANSLATION); | 
| 227 |  | 
| 228 |         if (!scaleValue.isUndefined()) | 
| 229 |             jsonArrayToVector3D(jsonArray: scaleValue.toArray(), v&: localTransform.scale); | 
| 230 |  | 
| 231 |         if (!rotationValue.isUndefined()) | 
| 232 |             jsonArrayToQuaternion(jsonArray: json.value(KEY_ROTATION).toArray(), q&: localTransform.rotation); | 
| 233 |  | 
| 234 |         if (!translationValue.isUndefined()) | 
| 235 |             jsonArrayToVector3D(jsonArray: json.value(KEY_TRANSLATION).toArray(), v&: localTransform.translation); | 
| 236 |     } | 
| 237 |  | 
| 238 |     // Referenced objects | 
| 239 |     const auto cameraValue = json.value(KEY_CAMERA); | 
| 240 |     if (!cameraValue.isUndefined()) | 
| 241 |         cameraIndex = cameraValue.toInt(); | 
| 242 |  | 
| 243 |     const auto meshValue = json.value(KEY_MESH); | 
| 244 |     if (!meshValue.isUndefined()) | 
| 245 |         meshIndex = meshValue.toInt(); | 
| 246 |  | 
| 247 |     const auto skinValue = json.value(KEY_SKIN); | 
| 248 |     if (!skinValue.isUndefined()) | 
| 249 |         skinIndex = skinValue.toInt(); | 
| 250 | } | 
| 251 |  | 
| 252 | QAttribute::VertexBaseType GLTFSkeletonLoader::accessorTypeFromJSON(int componentType) | 
| 253 | { | 
| 254 |     if (componentType == GL_BYTE) | 
| 255 |         return QAttribute::Byte; | 
| 256 |     else if (componentType == GL_UNSIGNED_BYTE) | 
| 257 |         return QAttribute::UnsignedByte; | 
| 258 |     else if (componentType == GL_SHORT) | 
| 259 |         return QAttribute::Short; | 
| 260 |     else if (componentType == GL_UNSIGNED_SHORT) | 
| 261 |         return QAttribute::UnsignedShort; | 
| 262 |     else if (componentType == GL_UNSIGNED_INT) | 
| 263 |         return QAttribute::UnsignedInt; | 
| 264 |     else if (componentType == GL_FLOAT) | 
| 265 |         return QAttribute::Float; | 
| 266 |  | 
| 267 |     // There shouldn't be an invalid case here | 
| 268 |     qCWarning(Jobs, "unsupported accessor type %d" , componentType); | 
| 269 |     return QAttribute::Float; | 
| 270 | } | 
| 271 |  | 
| 272 | uint GLTFSkeletonLoader::accessorTypeSize(QAttribute::VertexBaseType componentType) | 
| 273 | { | 
| 274 |     switch (componentType) { | 
| 275 |     case QAttribute::Byte: | 
| 276 |     case QAttribute::UnsignedByte: | 
| 277 |          return 1; | 
| 278 |  | 
| 279 |     case QAttribute::Short: | 
| 280 |     case QAttribute::UnsignedShort: | 
| 281 |         return 2; | 
| 282 |  | 
| 283 |     case QAttribute::Int: | 
| 284 |     case QAttribute::Float: | 
| 285 |         return 4; | 
| 286 |  | 
| 287 |     default: | 
| 288 |         qCWarning(Jobs, "Unhandled accessor data type %d" , componentType); | 
| 289 |         return 0; | 
| 290 |     } | 
| 291 | } | 
| 292 |  | 
| 293 | uint GLTFSkeletonLoader::accessorDataSizeFromJson(const QString &type) | 
| 294 | { | 
| 295 |     QString typeName = type.toUpper(); | 
| 296 |     if (typeName == QLatin1String("SCALAR" )) | 
| 297 |         return 1; | 
| 298 |     if (typeName == QLatin1String("VEC2" )) | 
| 299 |         return 2; | 
| 300 |     if (typeName == QLatin1String("VEC3" )) | 
| 301 |         return 3; | 
| 302 |     if (typeName == QLatin1String("VEC4" )) | 
| 303 |         return 4; | 
| 304 |     if (typeName == QLatin1String("MAT2" )) | 
| 305 |         return 4; | 
| 306 |     if (typeName == QLatin1String("MAT3" )) | 
| 307 |         return 9; | 
| 308 |     if (typeName == QLatin1String("MAT4" )) | 
| 309 |         return 16; | 
| 310 |  | 
| 311 |     return 0; | 
| 312 | } | 
| 313 |  | 
| 314 | GLTFSkeletonLoader::GLTFSkeletonLoader() | 
| 315 | { | 
| 316 | } | 
| 317 |  | 
| 318 | bool GLTFSkeletonLoader::load(QIODevice *ioDev) | 
| 319 | { | 
| 320 |     if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) { | 
| 321 |         qCWarning(Jobs, "not a JSON document" ); | 
| 322 |         return false; | 
| 323 |     } | 
| 324 |  | 
| 325 |     auto file = qobject_cast<QFile*>(object: ioDev); | 
| 326 |     if (file) { | 
| 327 |         QFileInfo finfo(file->fileName()); | 
| 328 |         setBasePath(finfo.dir().absolutePath()); | 
| 329 |     } | 
| 330 |  | 
| 331 |     return parse(); | 
| 332 | } | 
| 333 |  | 
| 334 | SkeletonData GLTFSkeletonLoader::createSkeleton(const QString &skeletonName) | 
| 335 | { | 
| 336 |     if (m_skins.isEmpty()) { | 
| 337 |         qCWarning(Jobs, "glTF file does not contain any skins" ); | 
| 338 |         return SkeletonData(); | 
| 339 |     } | 
| 340 |  | 
| 341 |     Skin *skin = m_skins.begin(); | 
| 342 |     if (!skeletonName.isNull()) { | 
| 343 |         const auto result = std::find_if(first: m_skins.begin(), last: m_skins.end(), | 
| 344 |             pred: [skeletonName](const Skin &skin) { return skin.name == skeletonName; }); | 
| 345 |         if (result != m_skins.end()) | 
| 346 |             skin = result; | 
| 347 |     } | 
| 348 |  | 
| 349 |     Q_ASSERT(skin != nullptr); | 
| 350 |     return createSkeletonFromSkin(skin); | 
| 351 | } | 
| 352 |  | 
| 353 | SkeletonData GLTFSkeletonLoader::createSkeletonFromSkin(Skin *skin) const | 
| 354 | { | 
| 355 |     SkeletonData skel; | 
| 356 |  | 
| 357 |     const int jointCount = skin->jointNodeIndices.size(); | 
| 358 |     skel.reserve(size: jointCount); | 
| 359 |  | 
| 360 |     QHash<const Node *, int> jointIndexMap; | 
| 361 |     for (int i = 0; i < jointCount; ++i) { | 
| 362 |         // Get a pointer to the node for this joint and store it in | 
| 363 |         // a map to the JointInfo index. We can later use this to set | 
| 364 |         // the parent indices of the joints | 
| 365 |         const Node *node = &m_nodes[skin->jointNodeIndices[i]]; | 
| 366 |         jointIndexMap.insert(akey: node, avalue: i); | 
| 367 |  | 
| 368 |         JointInfo joint; | 
| 369 |         joint.inverseBindPose = inverseBindMatrix(skin, jointIndex: i); | 
| 370 |         joint.parentIndex = jointIndexMap.value(akey: &m_nodes[node->parentNodeIndex], adefaultValue: -1); | 
| 371 |         if (joint.parentIndex == -1 && i != 0) | 
| 372 |             qCDebug(Jobs) << "Cannot find parent joint for joint"  << i; | 
| 373 |  | 
| 374 |         skel.joints.push_back(t: joint); | 
| 375 |         skel.localPoses.push_back(t: node->localTransform); | 
| 376 |         skel.jointNames.push_back(t: node->name); | 
| 377 |     } | 
| 378 |  | 
| 379 |     return skel; | 
| 380 | } | 
| 381 |  | 
| 382 | QMatrix4x4 GLTFSkeletonLoader::inverseBindMatrix(Skin *skin, int jointIndex) const | 
| 383 | { | 
| 384 |     // Create a matrix and copy the data into it | 
| 385 |     RawData rawData = accessorData(accessorIndex: skin->inverseBindAccessorIndex, index: jointIndex); | 
| 386 |     QMatrix4x4 m; | 
| 387 |     memcpy(dest: m.data(), src: rawData.data, n: rawData.byteLength); | 
| 388 |     return m; | 
| 389 | } | 
| 390 |  | 
| 391 | GLTFSkeletonLoader::RawData GLTFSkeletonLoader::accessorData(int accessorIndex, int index) const | 
| 392 | { | 
| 393 |     const AccessorData &accessor = m_accessors[accessorIndex]; | 
| 394 |     const BufferView &bufferView = m_bufferViews[accessor.bufferViewIndex]; | 
| 395 |     const BufferData &bufferData = m_bufferDatas[bufferView.bufferIndex]; | 
| 396 |     const QByteArray &ba = bufferData.data; | 
| 397 |     const char *rawData = ba.constData() + bufferView.byteOffset + accessor.byteOffset; | 
| 398 |  | 
| 399 |     const uint typeSize = accessorTypeSize(componentType: accessor.type); | 
| 400 |     const int stride = (accessor.byteStride == 0) | 
| 401 |             ? accessor.dataSize * typeSize | 
| 402 |             : accessor.byteStride; | 
| 403 |  | 
| 404 |     const char* data = rawData + index * stride; | 
| 405 |     if (data - rawData > ba.size()) { | 
| 406 |         qCWarning(Jobs, "Attempting to access data beyond end of buffer" ); | 
| 407 |         return RawData{ .data: nullptr, .byteLength: 0 }; | 
| 408 |     } | 
| 409 |  | 
| 410 |     const quint64 byteLength = accessor.dataSize * typeSize; | 
| 411 |     RawData rd{ .data: data, .byteLength: byteLength }; | 
| 412 |  | 
| 413 |     return rd; | 
| 414 | } | 
| 415 |  | 
| 416 | void GLTFSkeletonLoader::setBasePath(const QString &path) | 
| 417 | { | 
| 418 |     m_basePath = path; | 
| 419 | } | 
| 420 |  | 
| 421 | bool GLTFSkeletonLoader::setJSON(const QJsonDocument &json) | 
| 422 | { | 
| 423 |     if (!json.isObject()) | 
| 424 |         return false; | 
| 425 |     m_json = json; | 
| 426 |     cleanup(); | 
| 427 |     return true; | 
| 428 | } | 
| 429 |  | 
| 430 | bool GLTFSkeletonLoader::parse() | 
| 431 | { | 
| 432 |     // Find the glTF version | 
| 433 |     const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject(); | 
| 434 |     const QString versionString = asset.value(KEY_VERSION).toString(); | 
| 435 |     const auto version = QVersionNumber::fromString(string: versionString); | 
| 436 |     switch (version.majorVersion()) { | 
| 437 |     case 2: | 
| 438 |         return parseGLTF2(); | 
| 439 |  | 
| 440 |     default: | 
| 441 |         qWarning() << "Unsupported version of glTF"  << versionString; | 
| 442 |         return false; | 
| 443 |     } | 
| 444 | } | 
| 445 |  | 
| 446 | bool GLTFSkeletonLoader::parseGLTF2() | 
| 447 | { | 
| 448 |     bool success = true; | 
| 449 |     const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray(); | 
| 450 |     for (const auto &bufferValue : buffers) | 
| 451 |         success &= processJSONBuffer(json: bufferValue.toObject()); | 
| 452 |  | 
| 453 |     const QJsonArray bufferViews = m_json.object().value(KEY_BUFFER_VIEWS).toArray(); | 
| 454 |     for (const auto &bufferViewValue : bufferViews) | 
| 455 |         success &= processJSONBufferView(json: bufferViewValue.toObject()); | 
| 456 |  | 
| 457 |     const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray(); | 
| 458 |     for (const auto &accessorValue : accessors) | 
| 459 |         success &= processJSONAccessor(json: accessorValue.toObject()); | 
| 460 |  | 
| 461 |     const QJsonArray skins = m_json.object().value(KEY_SKINS).toArray(); | 
| 462 |     for (const auto &skinValue : skins) | 
| 463 |         success &= processJSONSkin(json: skinValue.toObject()); | 
| 464 |  | 
| 465 |     const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray(); | 
| 466 |     for (const auto &nodeValue : nodes) | 
| 467 |         success &= processJSONNode(json: nodeValue.toObject()); | 
| 468 |     setupNodeParentLinks(); | 
| 469 |  | 
| 470 |     // TODO: Make a complete GLTF 2 parser by extending to other top level elements: | 
| 471 |     // scenes, animations, meshes etc. | 
| 472 |  | 
| 473 |     return success; | 
| 474 | } | 
| 475 |  | 
| 476 | void GLTFSkeletonLoader::cleanup() | 
| 477 | { | 
| 478 |     m_accessors.clear(); | 
| 479 |     m_bufferViews.clear(); | 
| 480 |     m_bufferDatas.clear(); | 
| 481 | } | 
| 482 |  | 
| 483 | bool GLTFSkeletonLoader::processJSONBuffer(const QJsonObject &json) | 
| 484 | { | 
| 485 |     // Store buffer details and load data into memory | 
| 486 |     BufferData buffer(json); | 
| 487 |     buffer.data = resolveLocalData(path: buffer.path); | 
| 488 |     if (buffer.data.isEmpty()) | 
| 489 |         return false; | 
| 490 |  | 
| 491 |     m_bufferDatas.push_back(t: buffer); | 
| 492 |     return true; | 
| 493 | } | 
| 494 |  | 
| 495 | bool GLTFSkeletonLoader::processJSONBufferView(const QJsonObject &json) | 
| 496 | { | 
| 497 |     BufferView bufferView(json); | 
| 498 |  | 
| 499 |     // Perform sanity checks | 
| 500 |     const auto bufferIndex = bufferView.bufferIndex; | 
| 501 |     if (Q_UNLIKELY(bufferIndex) >= m_bufferDatas.size()) { | 
| 502 |         qCWarning(Jobs, "Unknown buffer %d when processing buffer view" , bufferIndex); | 
| 503 |         return false; | 
| 504 |     } | 
| 505 |  | 
| 506 |     const auto &bufferData = m_bufferDatas[bufferIndex]; | 
| 507 |     if (bufferView.byteOffset > bufferData.byteLength) { | 
| 508 |         qCWarning(Jobs, "Bufferview has offset greater than buffer %d length" , bufferIndex); | 
| 509 |         return false; | 
| 510 |     } | 
| 511 |  | 
| 512 |     if (Q_UNLIKELY(bufferView.byteOffset + bufferView.byteLength > bufferData.byteLength)) { | 
| 513 |         qCWarning(Jobs, "BufferView extends beyond end of buffer %d" , bufferIndex); | 
| 514 |         return false; | 
| 515 |     } | 
| 516 |  | 
| 517 |     m_bufferViews.push_back(t: bufferView); | 
| 518 |     return true; | 
| 519 | } | 
| 520 |  | 
| 521 | bool GLTFSkeletonLoader::processJSONAccessor(const QJsonObject &json) | 
| 522 | { | 
| 523 |     AccessorData accessor(json); | 
| 524 |  | 
| 525 |     // TODO: Perform sanity checks | 
| 526 |  | 
| 527 |     m_accessors.push_back(t: accessor); | 
| 528 |     return true; | 
| 529 | } | 
| 530 |  | 
| 531 | bool GLTFSkeletonLoader::processJSONSkin(const QJsonObject &json) | 
| 532 | { | 
| 533 |     Skin skin(json); | 
| 534 |  | 
| 535 |     // TODO: Perform sanity checks | 
| 536 |  | 
| 537 |     m_skins.push_back(t: skin); | 
| 538 |     return true; | 
| 539 | } | 
| 540 |  | 
| 541 | bool GLTFSkeletonLoader::processJSONNode(const QJsonObject &json) | 
| 542 | { | 
| 543 |     Node node(json); | 
| 544 |  | 
| 545 |     // TODO: Perform sanity checks | 
| 546 |  | 
| 547 |     m_nodes.push_back(t: node); | 
| 548 |     return true; | 
| 549 | } | 
| 550 |  | 
| 551 | void GLTFSkeletonLoader::setupNodeParentLinks() | 
| 552 | { | 
| 553 |     const int nodeCount = m_nodes.size(); | 
| 554 |     for (int i = 0; i < nodeCount; ++i) { | 
| 555 |         const Node &node = m_nodes[i]; | 
| 556 |         const QVector<int> &childNodeIndices = node.childNodeIndices; | 
| 557 |         for (const auto childNodeIndex : childNodeIndices) { | 
| 558 |             Q_ASSERT(childNodeIndex < m_nodes.size()); | 
| 559 |             Node &childNode = m_nodes[childNodeIndex]; | 
| 560 |             Q_ASSERT(childNode.parentNodeIndex == -1); | 
| 561 |             childNode.parentNodeIndex = i; | 
| 562 |         } | 
| 563 |     } | 
| 564 | } | 
| 565 |  | 
| 566 | QByteArray GLTFSkeletonLoader::resolveLocalData(const QString &path) const | 
| 567 | { | 
| 568 |     QDir d(m_basePath); | 
| 569 |     Q_ASSERT(d.exists()); | 
| 570 |  | 
| 571 |     QString absPath = d.absoluteFilePath(fileName: path); | 
| 572 |     QFile f(absPath); | 
| 573 |     f.open(flags: QIODevice::ReadOnly); | 
| 574 |     return f.readAll(); | 
| 575 | } | 
| 576 |  | 
| 577 | } // namespace Render | 
| 578 | } // namespace Qt3DRender | 
| 579 |  | 
| 580 | QT_END_NAMESPACE | 
| 581 |  |