1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
4** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the Qt3D module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "gltfimporter.h"
42
43#include <QtCore/qdir.h>
44#include <QtCore/qfileinfo.h>
45#include <QtCore/qjsonarray.h>
46#include <QtCore/qjsonobject.h>
47#include <QtCore/qmath.h>
48
49#include <QtGui/qvector2d.h>
50
51#include <Qt3DCore/qentity.h>
52#include <Qt3DCore/qtransform.h>
53
54#include <Qt3DRender/qcameralens.h>
55#include <Qt3DRender/qcamera.h>
56#include <Qt3DRender/qalphacoverage.h>
57#include <Qt3DRender/qalphatest.h>
58#include <Qt3DRender/qblendequation.h>
59#include <Qt3DRender/qblendequationarguments.h>
60#include <Qt3DRender/qclipplane.h>
61#include <Qt3DRender/qcolormask.h>
62#include <Qt3DRender/qcullface.h>
63#include <Qt3DRender/qdithering.h>
64#include <Qt3DRender/qmultisampleantialiasing.h>
65#include <Qt3DRender/qpointsize.h>
66#include <Qt3DRender/qnodepthmask.h>
67#include <Qt3DRender/qdepthrange.h>
68#include <Qt3DRender/qdepthtest.h>
69#include <Qt3DRender/qseamlesscubemap.h>
70#include <Qt3DRender/qstencilmask.h>
71#include <Qt3DRender/qstenciloperation.h>
72#include <Qt3DRender/qstenciloperationarguments.h>
73#include <Qt3DRender/qstenciltest.h>
74#include <Qt3DRender/qstenciltestarguments.h>
75#include <Qt3DRender/qeffect.h>
76#include <Qt3DRender/qfrontface.h>
77#include <Qt3DRender/qgeometry.h>
78#include <Qt3DRender/qgeometryrenderer.h>
79#include <Qt3DRender/qmaterial.h>
80#include <Qt3DRender/qgraphicsapifilter.h>
81#include <Qt3DRender/qparameter.h>
82#include <Qt3DRender/qpolygonoffset.h>
83#include <Qt3DRender/qrenderstate.h>
84#include <Qt3DRender/qscissortest.h>
85#include <Qt3DRender/qshaderprogram.h>
86#include <Qt3DRender/qtechnique.h>
87#include <Qt3DRender/qtexture.h>
88#include <Qt3DRender/qtextureimagedatagenerator.h>
89#include <Qt3DRender/qdirectionallight.h>
90#include <Qt3DRender/qspotlight.h>
91#include <Qt3DRender/qpointlight.h>
92
93#include <Qt3DExtras/qphongmaterial.h>
94#include <Qt3DExtras/qphongalphamaterial.h>
95#include <Qt3DExtras/qdiffusemapmaterial.h>
96#include <Qt3DExtras/qdiffusespecularmapmaterial.h>
97#include <Qt3DExtras/qnormaldiffusemapmaterial.h>
98#include <Qt3DExtras/qnormaldiffusemapalphamaterial.h>
99#include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h>
100#include <Qt3DExtras/qgoochmaterial.h>
101#include <Qt3DExtras/qpervertexcolormaterial.h>
102#include <Qt3DExtras/qmetalroughmaterial.h>
103#include <Qt3DExtras/qconemesh.h>
104#include <Qt3DExtras/qcuboidmesh.h>
105#include <Qt3DExtras/qcylindermesh.h>
106#include <Qt3DExtras/qplanemesh.h>
107#include <Qt3DExtras/qspheremesh.h>
108#include <Qt3DExtras/qtorusmesh.h>
109
110#include <private/qurlhelper_p.h>
111#include <private/qloadgltf_p.h>
112
113/**
114 * glTF 2.0 conformance report
115 *
116 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
117 * Samples: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
118 *
119 * Most of the reference samples are rendered correctly, with the following exceptions:
120 *
121 * 'extensions' and 'extras' are ignored everywhere except in nodes.
122 *
123 * asset
124 * generator, copyright, minVersion: not parsed
125 * accessors
126 * min, max, normalized, sparse: not parsed
127 * animations
128 * the whole object is not parsed
129 * buffers
130 * all parsed
131 * bufferViews
132 * all parsed
133 * cameras
134 * all parsed
135 * images
136 * mimeType, bufferView, name: not parsed
137 * materials
138 * emissiveTexture, emissiveFactor: not parsed
139 * alphaMode, alphaCutoff, doubleSided: not parsed
140 * texCoord, strength: not parsed
141 * meshes
142 * weights: not parsed
143 * nodes
144 * skin, weights: not parsed
145 * samplers
146 * all parsed
147 * scenes
148 * all parsed
149 * textures
150 * all parsed
151 */
152
153#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature
154# define qUtf16PrintableImpl(string) \
155 static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
156#endif
157
158#define KEY_ASSET QLatin1String("asset")
159#define KEY_VERSION QLatin1String("version")
160#define KEY_CAMERA QLatin1String("camera")
161#define KEY_CAMERAS QLatin1String("cameras")
162#define KEY_SCENES QLatin1String("scenes")
163#define KEY_NODES QLatin1String("nodes")
164#define KEY_MESHES QLatin1String("meshes")
165#define KEY_CHILDREN QLatin1String("children")
166#define KEY_MESH QLatin1String("mesh")
167#define KEY_MATRIX QLatin1String("matrix")
168#define KEY_ROTATION QLatin1String("rotation")
169#define KEY_SCALE QLatin1String("scale")
170#define KEY_TRANSLATION QLatin1String("translation")
171#define KEY_TYPE QLatin1String("type")
172#define KEY_PERSPECTIVE QLatin1String("perspective")
173#define KEY_ORTHOGRAPHIC QLatin1String("orthographic")
174#define KEY_NAME QLatin1String("name")
175#define KEY_COUNT QLatin1String("count")
176#define KEY_YFOV QLatin1String("yfov")
177#define KEY_ZNEAR QLatin1String("znear")
178#define KEY_ZFAR QLatin1String("zfar")
179#define KEY_XMAG QLatin1String("xmag")
180#define KEY_YMAG QLatin1String("ymag")
181#define KEY_MATERIALS QLatin1String("materials")
182#define KEY_EXTENSIONS QLatin1String("extensions")
183#define KEY_COMMON_MAT QLatin1String("KHR_materials_common")
184#define KEY_TECHNIQUE QLatin1String("technique")
185#define KEY_VALUES QLatin1String("values")
186#define KEY_BUFFERS QLatin1String("buffers")
187#define KEY_SHADERS QLatin1String("shaders")
188#define KEY_PROGRAMS QLatin1String("programs")
189#define KEY_PROGRAM QLatin1String("program")
190#define KEY_TECHNIQUES QLatin1String("techniques")
191#define KEY_ACCESSORS QLatin1String("accessors")
192#define KEY_IMAGES QLatin1String("images")
193#define KEY_TEXTURES QLatin1String("textures")
194#define KEY_SCENE QLatin1String("scene")
195#define KEY_BUFFER QLatin1String("buffer")
196#define KEY_TARGET QLatin1String("target")
197#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
198#define KEY_BYTE_LENGTH QLatin1String("byteLength")
199#define KEY_BYTE_STRIDE QLatin1String("byteStride")
200#define KEY_PRIMITIVES QLatin1String("primitives")
201#define KEY_MODE QLatin1String("mode")
202#define KEY_MATERIAL QLatin1String("material")
203#define KEY_ATTRIBUTES QLatin1String("attributes")
204#define KEY_INDICES QLatin1String("indices")
205#define KEY_URI QLatin1String("uri")
206#define KEY_FORMAT QLatin1String("format")
207#define KEY_PASSES QLatin1String("passes")
208#define KEY_SOURCE QLatin1String("source")
209#define KEY_SAMPLER QLatin1String("sampler")
210#define KEY_SAMPLERS QLatin1String("samplers")
211#define KEY_SEMANTIC QLatin1String("semantic")
212#define KEY_STATES QLatin1String("states")
213#define KEY_UNIFORMS QLatin1String("uniforms")
214#define KEY_PARAMETERS QLatin1String("parameters")
215#define KEY_WRAP_S QLatin1String("wrapS")
216#define KEY_MIN_FILTER QLatin1String("minFilter")
217#define KEY_MAG_FILTER QLatin1String("magFilter")
218#define KEY_LIGHT QLatin1String("light")
219#define KEY_LIGHTS QLatin1String("lights")
220#define KEY_POINT_LIGHT QLatin1String("point")
221#define KEY_DIRECTIONAL_LIGHT QLatin1String("directional")
222#define KEY_SPOT_LIGHT QLatin1String("spot")
223#define KEY_AMBIENT_LIGHT QLatin1String("ambient")
224#define KEY_COLOR QLatin1String("color")
225#define KEY_FALLOFF_ANGLE QLatin1String("falloffAngle")
226#define KEY_DIRECTION QLatin1String("direction")
227#define KEY_CONST_ATTENUATION QLatin1String("constantAttenuation")
228#define KEY_LINEAR_ATTENUATION QLatin1String("linearAttenuation")
229#define KEY_QUAD_ATTENUATION QLatin1String("quadraticAttenuation")
230#define KEY_INTENSITY QLatin1String("intensity")
231#define KEY_PBR_METAL_ROUGH QLatin1String("pbrMetallicRoughness")
232#define KEY_BASE_COLOR QLatin1String("baseColorFactor")
233#define KEY_BASE_COLOR_TEX QLatin1String("baseColorTexture")
234#define KEY_METAL_FACTOR QLatin1String("metallicFactor")
235#define KEY_METAL_ROUGH_TEX QLatin1String("metallicRoughnessTexture")
236#define KEY_ROUGH_FACTOR QLatin1String("roughnessFactor")
237#define KEY_NORMAL_TEX QLatin1String("normalTexture")
238#define KEY_OCCLUSION_TEX QLatin1String("occlusionTexture")
239#define KEY_INDEX QLatin1String("index")
240
241#define KEY_INSTANCE_TECHNIQUE QLatin1String("instanceTechnique")
242#define KEY_INSTANCE_PROGRAM QLatin1String("instanceProgram")
243#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
244#define KEY_BUFFER_VIEW QLatin1String("bufferView")
245#define KEY_VERTEX_SHADER QLatin1String("vertexShader")
246#define KEY_FRAGMENT_SHADER QLatin1String("fragmentShader")
247#define KEY_TESS_CTRL_SHADER QLatin1String("tessCtrlShader")
248#define KEY_TESS_EVAL_SHADER QLatin1String("tessEvalShader")
249#define KEY_GEOMETRY_SHADER QLatin1String("geometryShader")
250#define KEY_COMPUTE_SHADER QLatin1String("computeShader")
251#define KEY_INTERNAL_FORMAT QLatin1String("internalFormat")
252#define KEY_COMPONENT_TYPE QLatin1String("componentType")
253#define KEY_ASPECT_RATIO QLatin1String("aspect_ratio")
254#define KEY_VALUE QLatin1String("value")
255#define KEY_ENABLE QLatin1String("enable")
256#define KEY_FUNCTIONS QLatin1String("functions")
257#define KEY_BLEND_EQUATION QLatin1String("blendEquationSeparate")
258#define KEY_BLEND_FUNCTION QLatin1String("blendFuncSeparate")
259#define KEY_TECHNIQUE_CORE QLatin1String("techniqueCore")
260#define KEY_TECHNIQUE_GL2 QLatin1String("techniqueGL2")
261#define KEY_GABIFILTER QLatin1String("gapifilter")
262#define KEY_API QLatin1String("api")
263#define KEY_MAJORVERSION QLatin1String("majorVersion")
264#define KEY_MINORVERSION QLatin1String("minorVersion")
265#define KEY_PROFILE QLatin1String("profile")
266#define KEY_VENDOR QLatin1String("vendor")
267#define KEY_FILTERKEYS QLatin1String("filterkeys")
268#define KEY_RENDERPASSES QLatin1String("renderpasses")
269#define KEY_EFFECT QLatin1String("effect")
270#define KEY_EFFECTS QLatin1String("effects")
271#define KEY_PROPERTIES QLatin1String("properties")
272#define KEY_POSITION QLatin1String("position")
273#define KEY_UPVECTOR QLatin1String("upVector")
274#define KEY_VIEW_CENTER QLatin1String("viewCenter")
275
276QT_BEGIN_NAMESPACE
277
278using namespace Qt3DCore;
279using namespace Qt3DExtras;
280
281namespace {
282
283inline QVector3D jsonArrToVec3(const QJsonArray &array)
284{
285 return QVector3D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble());
286}
287
288inline QVector4D jsonArrToVec4(const QJsonArray &array)
289{
290 return QVector4D(array[0].toDouble(), array[1].toDouble(),
291 array[2].toDouble(), array[3].toDouble());
292}
293
294inline QVariant jsonArrToColorVariant(const QJsonArray &array)
295{
296 return QVariant(QColor::fromRgbF(r: array[0].toDouble(), g: array[1].toDouble(),
297 b: array[2].toDouble(), a: array[3].toDouble()));
298}
299
300inline QColor vec4ToQColor(const QVariant &vec4Var)
301{
302 const QVector4D v = vec4Var.value<QVector4D>();
303 return QColor::fromRgbF(r: v.x(), g: v.y(), b: v.z());
304}
305
306inline QVariant vec4ToColorVariant(const QVariant &vec4Var)
307{
308 return QVariant(vec4ToQColor(vec4Var));
309}
310
311Qt3DRender::QFilterKey *buildFilterKey(const QString &key, const QJsonValue &val)
312{
313 Qt3DRender::QFilterKey *fk = new Qt3DRender::QFilterKey;
314 fk->setName(key);
315 if (val.isString())
316 fk->setValue(val.toString());
317 else
318 fk->setValue(val.toInt());
319 return fk;
320}
321
322} // namespace
323
324namespace Qt3DRender {
325
326Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport", QtWarningMsg);
327
328class GLTFRawTextureImage : public QAbstractTextureImage
329{
330 Q_OBJECT
331public:
332 explicit GLTFRawTextureImage(QNode *parent = nullptr);
333
334 QTextureImageDataGeneratorPtr dataGenerator() const final;
335
336 void setImage(const QImage &image);
337
338private:
339 QImage m_image;
340
341 class GLTFRawTextureImageFunctor : public QTextureImageDataGenerator
342 {
343 public:
344 explicit GLTFRawTextureImageFunctor(const QImage &image);
345
346 QTextureImageDataPtr operator()() final;
347 bool operator ==(const QTextureImageDataGenerator &other) const final;
348
349 QT3D_FUNCTOR(GLTFRawTextureImageFunctor)
350 private:
351 QImage m_image;
352 };
353};
354
355GLTFImporter::GLTFImporter()
356 : QSceneImporter()
357 , m_parseDone(false)
358 , m_majorVersion(1)
359 , m_minorVersion(0)
360{
361}
362
363GLTFImporter::~GLTFImporter()
364{
365
366}
367
368/*!
369 \class Qt3DRender::GLTFImporter
370 \inmodule Qt3DRender
371 \internal
372 \brief Handles importing of gltf files.
373*/
374/*!
375 Set the base \a path for importing scenes.
376*/
377void GLTFImporter::setBasePath(const QString& path)
378{
379 m_basePath = path;
380}
381
382/*!
383 Set a \a json document as the file used for importing a scene.
384 Returns true if the operation is successful.
385*/
386bool GLTFImporter::setJSON(const QJsonDocument &json)
387{
388 if (!json.isObject()) {
389 return false;
390 }
391
392 m_json = json;
393 m_parseDone = false;
394
395 return true;
396}
397
398/*!
399 * Sets the path based on parameter \a source. The path is
400 * used by the parser to load the scene file.
401 * If the file is valid, parsing is automatically triggered.
402 */
403void GLTFImporter::setSource(const QUrl &source)
404{
405 const QString path = QUrlHelper::urlToLocalFileOrQrc(url: source);
406 QFileInfo finfo(path);
407 if (Q_UNLIKELY(!finfo.exists())) {
408 qCWarning(GLTFImporterLog, "missing file: %ls", qUtf16PrintableImpl(path));
409 return;
410 }
411 QFile f(path);
412 f.open(flags: QIODevice::ReadOnly);
413
414 if (Q_UNLIKELY(!setJSON(qLoadGLTF(f.readAll())))) {
415 qCWarning(GLTFImporterLog, "not a JSON document");
416 return;
417 }
418
419 setBasePath(finfo.dir().absolutePath());
420}
421
422/*!
423 * Sets the \a basePath used by the parser to load the scene file.
424 * If the file derived from \a data is valid, parsing is automatically
425 * triggered.
426 */
427void GLTFImporter::setData(const QByteArray& data, const QString &basePath)
428{
429 if (Q_UNLIKELY(!setJSON(qLoadGLTF(data)))) {
430 qCWarning(GLTFImporterLog, "not a JSON document");
431 return;
432 }
433
434 setBasePath(basePath);
435}
436
437/*!
438 * Returns true if the \a extensions are supported by the
439 * GLTF parser.
440 */
441bool GLTFImporter::areFileTypesSupported(const QStringList &extensions) const
442{
443 return GLTFImporter::isGLTFSupported(extensions);
444}
445
446/*!
447 Imports the node specified in \a id from the GLTF file.
448*/
449Qt3DCore::QEntity* GLTFImporter::node(const QString &id)
450{
451 QJsonValue jsonVal;
452
453 if (m_majorVersion > 1) {
454 const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
455 if (Q_UNLIKELY(id.toInt() >= nodes.count())) {
456 qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls",
457 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
458 return nullptr;
459 }
460 jsonVal = nodes[id.toInt()];
461 } else {
462 const QJsonObject nodes = m_json.object().value(KEY_NODES).toObject();
463 jsonVal = nodes.value(key: id);
464 if (Q_UNLIKELY(jsonVal.isUndefined())) {
465 qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls",
466 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
467 return nullptr;
468 }
469 }
470
471 const QJsonObject jsonObj = jsonVal.toObject();
472 QEntity* result = nullptr;
473
474 // Qt3D has a limitation that a QEntity can only have 1 mesh and 1 material component
475 // So if the node has only 1 mesh, we only create 1 QEntity
476 // Otherwise if there are n meshes, there is 1 QEntity, with n children for each mesh/material combo
477 {
478 QVector<QEntity *> entities;
479 const QJsonValue meshesValue = jsonObj.value(KEY_MESHES);
480
481 if (meshesValue.isUndefined()) {
482 const QJsonValue mesh = jsonObj.value(KEY_MESH);
483 if (!mesh.isUndefined()) {
484 const QString meshName = QString::number(mesh.toInt());
485 const auto geometryRenderers = qAsConst(t&: m_meshDict).equal_range(akey: meshName);
486 for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) {
487 QGeometryRenderer *geometryRenderer = it.value();
488 QEntity *entity = new QEntity;
489 entity->addComponent(comp: geometryRenderer);
490 QMaterial *mat = material(id: m_meshMaterialDict[geometryRenderer]);
491 if (mat)
492 entity->addComponent(comp: mat);
493 entities.append(t: entity);
494 }
495 }
496 } else {
497 const auto meshes = meshesValue.toArray();
498 for (const QJsonValue &mesh : meshes) {
499 const QString meshName = mesh.toString();
500 const auto geometryRenderers = qAsConst(t&: m_meshDict).equal_range(akey: meshName);
501 if (Q_UNLIKELY(geometryRenderers.first == geometryRenderers.second)) {
502 qCWarning(GLTFImporterLog, "node %ls references unknown mesh %ls",
503 qUtf16PrintableImpl(id), qUtf16PrintableImpl(meshName));
504 continue;
505 }
506
507 for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) {
508 QGeometryRenderer *geometryRenderer = it.value();
509 QEntity *entity = new QEntity;
510 entity->addComponent(comp: geometryRenderer);
511 QMaterial *mat = material(id: m_meshMaterialDict[geometryRenderer]);
512 if (mat)
513 entity->addComponent(comp: mat);
514 entities.append(t: entity);
515 }
516 }
517 }
518
519 switch (entities.size()) {
520 case 0:
521 break;
522 case 1:
523 result = qAsConst(t&: entities).first();
524 break;
525 default:
526 result = new QEntity;
527 for (QEntity *entity : qAsConst(t&: entities))
528 entity->setParent(result);
529 }
530 }
531
532 const auto cameraVal = jsonObj.value(KEY_CAMERA);
533 const auto matrix = jsonObj.value(KEY_MATRIX);
534 const auto rotation = jsonObj.value(KEY_ROTATION);
535 const auto translation = jsonObj.value(KEY_TRANSLATION);
536 const auto scale = jsonObj.value(KEY_SCALE);
537 Qt3DCore::QTransform *trans = nullptr;
538 QCameraLens *cameraLens = nullptr;
539 QCamera *cameraEntity = nullptr;
540
541 // If the node contains no meshes, results will still be null here.
542 // If the node has camera and transform, promote it to QCamera, as that makes it more
543 // convenient to adjust the imported camera in the application.
544 if (result == nullptr) {
545 if (!cameraVal.isUndefined()
546 && (!matrix.isUndefined() || !rotation.isUndefined() || !translation.isUndefined()
547 || !scale.isUndefined())) {
548 cameraEntity = new QCamera;
549 trans = cameraEntity->transform();
550 cameraLens = cameraEntity->lens();
551 result = cameraEntity;
552 } else {
553 result = new QEntity;
554 }
555 }
556
557 // recursively retrieve children
558 const auto children = jsonObj.value(KEY_CHILDREN).toArray();
559 for (const QJsonValue &c : children) {
560 QEntity* child = node(id: (m_majorVersion > 1) ? QString::number(c.toInt()) : c.toString());
561 if (!child)
562 continue;
563 child->setParent(result);
564 }
565
566 renameFromJson(json: jsonObj, object: result);
567
568 // Node Transforms
569 if (!matrix.isUndefined()) {
570 QMatrix4x4 m(Qt::Uninitialized);
571
572 QJsonArray matrixValues = matrix.toArray();
573 for (int i = 0; i < 16; ++i) {
574 double v = matrixValues.at(i).toDouble();
575 m(i % 4, i >> 2) = v;
576 }
577
578 if (!trans)
579 trans = new Qt3DCore::QTransform;
580 trans->setMatrix(m);
581 }
582
583 // Rotation quaternion
584 if (!rotation.isUndefined()) {
585 if (!trans)
586 trans = new Qt3DCore::QTransform;
587
588 QQuaternion quaternion(jsonArrToVec4(array: rotation.toArray()));
589 trans->setRotation(quaternion);
590 }
591
592 // Translation
593 if (!translation.isUndefined()) {
594 if (!trans)
595 trans = new Qt3DCore::QTransform;
596 trans->setTranslation(jsonArrToVec3(array: translation.toArray()));
597 }
598
599 // Scale
600 if (!scale.isUndefined()) {
601 if (!trans)
602 trans = new Qt3DCore::QTransform;
603 trans->setScale3D(jsonArrToVec3(array: scale.toArray()));
604 }
605
606 // Add the Transform component
607 if (trans != nullptr)
608 result->addComponent(comp: trans);
609
610 if (!cameraVal.isUndefined()) {
611 const bool newLens = cameraLens == nullptr;
612 if (newLens)
613 cameraLens = new QCameraLens;
614 const QString cameraID = (m_majorVersion > 1) ? QString::number(cameraVal.toInt()) : cameraVal.toString();
615 if (!fillCamera(lens&: *cameraLens, cameraEntity, id: cameraID)) {
616 qCWarning(GLTFImporterLog, "failed to build camera: %ls on node %ls",
617 qUtf16PrintableImpl(cameraID), qUtf16PrintableImpl(id));
618 } else if (newLens) {
619 result->addComponent(comp: cameraLens);
620 }
621 }
622
623 const auto extensionsVal = jsonObj.value(KEY_EXTENSIONS);
624 if (!extensionsVal.isUndefined()) {
625 const auto commonMat = extensionsVal.toObject().value(KEY_COMMON_MAT);
626 if (!commonMat.isUndefined()) {
627 const QJsonValue lightVal = commonMat.toObject().value(KEY_LIGHT);
628 const QString lightId = (m_majorVersion > 1) ? QString::number(lightVal.toInt()) : lightVal.toString();
629 QAbstractLight *lightComp = m_lights.value(akey: lightId);
630 if (Q_UNLIKELY(!lightComp)) {
631 qCWarning(GLTFImporterLog, "failed to find light: %ls for node %ls",
632 qUtf16PrintableImpl(lightId), qUtf16PrintableImpl(id));
633 } else {
634 result->addComponent(comp: lightComp);
635 }
636 }
637 }
638
639 return result;
640}
641
642/*!
643 Imports the scene specified in parameter \a id.
644*/
645Qt3DCore::QEntity* GLTFImporter::scene(const QString &id)
646{
647 parse();
648
649 QEntity* sceneEntity = nullptr;
650
651 if (m_majorVersion > 1) {
652 const QJsonArray scenes = m_json.object().value(KEY_SCENES).toArray();
653 const auto sceneVal = scenes.first();
654 if (Q_UNLIKELY(sceneVal.isUndefined())) {
655 if (Q_UNLIKELY(!id.isNull()))
656 qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls",
657 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
658 return defaultScene();
659 }
660 const QJsonObject sceneObj = sceneVal.toObject();
661 sceneEntity = new QEntity;
662 const auto nodes = sceneObj.value(KEY_NODES).toArray();
663 for (const QJsonValue &n : nodes) {
664 QEntity* child = node(id: QString::number(n.toInt()));
665 if (!child)
666 continue;
667 child->setParent(sceneEntity);
668 }
669 } else {
670 const QJsonObject scenes = m_json.object().value(KEY_SCENES).toObject();
671 const auto sceneVal = scenes.value(key: id);
672 if (Q_UNLIKELY(sceneVal.isUndefined())) {
673 if (Q_UNLIKELY(!id.isNull()))
674 qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls",
675 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
676 return defaultScene();
677 }
678
679 const QJsonObject sceneObj = sceneVal.toObject();
680 sceneEntity = new QEntity;
681 const auto nodes = sceneObj.value(KEY_NODES).toArray();
682 for (const QJsonValue &nnv : nodes) {
683 QString nodeName = nnv.toString();
684 QEntity* child = node(id: nodeName);
685 if (!child)
686 continue;
687 child->setParent(sceneEntity);
688 }
689 }
690
691 cleanup();
692
693 return sceneEntity;
694}
695
696GLTFImporter::BufferData::BufferData()
697 : length(0)
698 , data(nullptr)
699{
700}
701
702GLTFImporter::BufferData::BufferData(const QJsonObject &json)
703 : length(json.value(KEY_BYTE_LENGTH).toInt()),
704 path(json.value(KEY_URI).toString()),
705 data(nullptr)
706{
707}
708
709GLTFImporter::ParameterData::ParameterData() :
710 type(0)
711{
712
713}
714
715GLTFImporter::ParameterData::ParameterData(const QJsonObject &json)
716 : semantic(json.value(KEY_SEMANTIC).toString()),
717 type(json.value(KEY_TYPE).toInt())
718{
719}
720
721GLTFImporter::AccessorData::AccessorData()
722 : type(QAttribute::Float)
723 , dataSize(0)
724 , count(0)
725 , offset(0)
726 , stride(0)
727{
728
729}
730
731GLTFImporter::AccessorData::AccessorData(const QJsonObject &json, int major, int minor)
732 : type(accessorTypeFromJSON(componentType: json.value(KEY_COMPONENT_TYPE).toInt())),
733 dataSize(accessorDataSizeFromJson(type: json.value(KEY_TYPE).toString())),
734 count(json.value(KEY_COUNT).toInt()),
735 offset(0),
736 stride(0)
737{
738 Q_UNUSED(minor)
739
740 if (major > 1) {
741 bufferViewName = QString::number(json.value(KEY_BUFFER_VIEW).toInt());
742 } else {
743 bufferViewName = json.value(KEY_BUFFER_VIEW).toString();
744 }
745
746 const auto byteOffset = json.value(KEY_BYTE_OFFSET);
747 if (!byteOffset.isUndefined())
748 offset = byteOffset.toInt();
749 const auto byteStride = json.value(KEY_BYTE_STRIDE);
750 if (!byteStride.isUndefined())
751 stride = byteStride.toInt();
752}
753
754bool GLTFImporter::isGLTFSupported(const QStringList &extensions)
755{
756 for (auto suffix: qAsConst(t: extensions)) {
757 suffix = suffix.toLower();
758 if (suffix == QLatin1String("json") || suffix == QLatin1String("gltf") || suffix == QLatin1String("qgltf"))
759 return true;
760 }
761 return false;
762}
763
764bool GLTFImporter::isEmbeddedResource(const QString &url)
765{
766 return url.startsWith(s: "data:");
767}
768
769void GLTFImporter::renameFromJson(const QJsonObject &json, QObject * const object)
770{
771 const auto name = json.value(KEY_NAME);
772 if (!name.isUndefined())
773 object->setObjectName(name.toString());
774}
775
776bool GLTFImporter::hasStandardUniformNameFromSemantic(const QString &semantic)
777{
778 //Standard Uniforms
779 if (semantic.isEmpty())
780 return false;
781 switch (semantic.at(i: 0).toLatin1()) {
782 case 'L':
783 // return semantic == QLatin1String("LOCAL");
784 return false;
785 case 'M':
786 return semantic == QLatin1String("MODEL")
787 || semantic == QLatin1String("MODELVIEW")
788 || semantic == QLatin1String("MODELVIEWPROJECTION")
789 || semantic == QLatin1String("MODELINVERSE")
790 || semantic == QLatin1String("MODELVIEWPROJECTIONINVERSE")
791 || semantic == QLatin1String("MODELINVERSETRANSPOSE")
792 || semantic == QLatin1String("MODELVIEWINVERSETRANSPOSE");
793 case 'V':
794 return semantic == QLatin1String("VIEW")
795 || semantic == QLatin1String("VIEWINVERSE")
796 || semantic == QLatin1String("VIEWPORT");
797 case 'P':
798 return semantic == QLatin1String("PROJECTION")
799 || semantic == QLatin1String("PROJECTIONINVERSE");
800 }
801 return false;
802}
803
804QString GLTFImporter::standardAttributeNameFromSemantic(const QString &semantic)
805{
806 //Standard Attributes
807 if (semantic.startsWith(s: QLatin1String("POSITION")))
808 return QAttribute::defaultPositionAttributeName();
809 if (semantic.startsWith(s: QLatin1String("NORMAL")))
810 return QAttribute::defaultNormalAttributeName();
811 if (semantic.startsWith(s: QLatin1String("TEXCOORD")))
812 return QAttribute::defaultTextureCoordinateAttributeName();
813 if (semantic.startsWith(s: QLatin1String("COLOR")))
814 return QAttribute::defaultColorAttributeName();
815 if (semantic.startsWith(s: QLatin1String("TANGENT")))
816 return QAttribute::defaultTangentAttributeName();
817
818// if (semantic.startsWith(QLatin1String("JOINT")));
819// if (semantic.startsWith(QLatin1String("JOINTMATRIX")));
820// if (semantic.startsWith(QLatin1String("WEIGHT")));
821
822 return QString();
823}
824
825QParameter *GLTFImporter::parameterFromTechnique(QTechnique *technique,
826 const QString &parameterName)
827{
828 const QList<QParameter *> parameters = m_techniqueParameters.value(akey: technique);
829 for (QParameter *parameter : parameters) {
830 if (parameter->name() == parameterName)
831 return parameter;
832 }
833
834 return nullptr;
835}
836
837Qt3DCore::QEntity* GLTFImporter::defaultScene()
838{
839 if (Q_UNLIKELY(m_defaultScene.isEmpty())) {
840 qCWarning(GLTFImporterLog, "no default scene");
841 return nullptr;
842 }
843
844 return scene(id: m_defaultScene);
845}
846
847QMaterial *GLTFImporter::materialWithCustomShader(const QString &id, const QJsonObject &jsonObj)
848{
849 QString effectName = jsonObj.value(KEY_EFFECT).toString();
850 if (effectName.isEmpty()) {
851 // GLTF custom shader material (with qgltf tool specific customizations)
852
853 // Default ES2 Technique
854 QString techniqueName = jsonObj.value(KEY_TECHNIQUE).toString();
855 const auto it = qAsConst(t&: m_techniques).find(akey: techniqueName);
856 if (Q_UNLIKELY(it == m_techniques.cend())) {
857 qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
858 qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
859 return nullptr;
860 }
861 QTechnique *technique = *it;
862 technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGLES);
863 technique->graphicsApiFilter()->setMajorVersion(2);
864 technique->graphicsApiFilter()->setMinorVersion(0);
865 technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile);
866
867 //Optional Core technique
868 QTechnique *coreTechnique = nullptr;
869 QTechnique *gl2Technique = nullptr;
870 QString coreTechniqueName = jsonObj.value(KEY_TECHNIQUE_CORE).toString();
871 if (!coreTechniqueName.isNull()) {
872 const auto it = qAsConst(t&: m_techniques).find(akey: coreTechniqueName);
873 if (Q_UNLIKELY(it == m_techniques.cend())) {
874 qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
875 qUtf16PrintableImpl(coreTechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
876 } else {
877 coreTechnique = it.value();
878 coreTechnique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL);
879 coreTechnique->graphicsApiFilter()->setMajorVersion(3);
880 coreTechnique->graphicsApiFilter()->setMinorVersion(1);
881 coreTechnique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile);
882 }
883 }
884 //Optional GL2 technique
885 QString gl2TechniqueName = jsonObj.value(KEY_TECHNIQUE_GL2).toString();
886 if (!gl2TechniqueName.isNull()) {
887 const auto it = qAsConst(t&: m_techniques).find(akey: gl2TechniqueName);
888 if (Q_UNLIKELY(it == m_techniques.cend())) {
889 qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
890 qUtf16PrintableImpl(gl2TechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
891 } else {
892 gl2Technique = it.value();
893 gl2Technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL);
894 gl2Technique->graphicsApiFilter()->setMajorVersion(2);
895 gl2Technique->graphicsApiFilter()->setMinorVersion(0);
896 gl2Technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile);
897 }
898 }
899
900 // glTF doesn't deal in effects, but we need a trivial one to wrap
901 // up our techniques
902 // However we need to create a unique effect for each material instead
903 // of caching because QMaterial does not keep up with effects
904 // its not the parent of.
905 QEffect *effect = new QEffect;
906 effect->setObjectName(techniqueName);
907 effect->addTechnique(t: technique);
908 if (coreTechnique != nullptr)
909 effect->addTechnique(t: coreTechnique);
910 if (gl2Technique != nullptr)
911 effect->addTechnique(t: gl2Technique);
912
913 QMaterial *mat = new QMaterial;
914 mat->setEffect(effect);
915
916 renameFromJson(json: jsonObj, object: mat);
917
918 const QJsonObject values = jsonObj.value(KEY_VALUES).toObject();
919 for (auto it = values.begin(), end = values.end(); it != end; ++it) {
920 const QString vName = it.key();
921 QParameter *param = parameterFromTechnique(technique, parameterName: vName);
922
923 if (param == nullptr && coreTechnique != nullptr)
924 param = parameterFromTechnique(technique: coreTechnique, parameterName: vName);
925
926 if (param == nullptr && gl2Technique != nullptr)
927 param = parameterFromTechnique(technique: gl2Technique, parameterName: vName);
928
929 if (Q_UNLIKELY(!param)) {
930 qCWarning(GLTFImporterLog, "unknown parameter: %ls in technique %ls processing material %ls",
931 qUtf16PrintableImpl(vName), qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id));
932 continue;
933 }
934
935 ParameterData paramData = m_parameterDataDict.value(akey: param);
936 QVariant var = parameterValueFromJSON(type: paramData.type, value: it.value());
937
938 mat->addParameter(parameter: new QParameter(param->name(), var));
939 } // of material technique-instance values iteration
940
941 return mat;
942 } else {
943 // Qt3D exported QGLTF custom material
944 QMaterial *mat = new QMaterial;
945 renameFromJson(json: jsonObj, object: mat);
946 QEffect *effect = m_effects.value(akey: effectName);
947 if (effect) {
948 mat->setEffect(effect);
949 } else {
950 qCWarning(GLTFImporterLog, "Effect %ls missing for material %ls",
951 qUtf16PrintableImpl(effectName), qUtf16PrintableImpl(mat->objectName()));
952 }
953 const QJsonObject params = jsonObj.value(KEY_PARAMETERS).toObject();
954 for (auto it = params.begin(), end = params.end(); it != end; ++it)
955 mat->addParameter(parameter: buildParameter(key: it.key(), paramObj: it.value().toObject()));
956
957 return mat;
958 }
959}
960
961QMaterial *GLTFImporter::commonMaterial(const QJsonObject &jsonObj)
962{
963 const auto jsonExt =
964 jsonObj.value(KEY_EXTENSIONS).toObject().value(KEY_COMMON_MAT).toObject();
965 if (m_majorVersion == 1 && jsonExt.isEmpty())
966 return nullptr;
967
968 QVariantHash params;
969 bool hasDiffuseMap = false;
970 bool hasSpecularMap = false;
971 bool hasNormalMap = false;
972 bool hasAlpha = false;
973
974 if (m_majorVersion > 1) {
975 QMaterial *mat = pbrMaterial(jsonObj);
976
977 if (mat)
978 return mat;
979 }
980
981 const QJsonObject values = jsonExt.value(KEY_VALUES).toObject();
982 for (auto it = values.begin(), end = values.end(); it != end; ++it) {
983 const QString vName = it.key();
984 const QJsonValue val = it.value();
985 QVariant var;
986 QString propertyName = vName;
987 if (vName == QLatin1String("ambient") && val.isArray()) {
988 var = vec4ToColorVariant(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: val));
989 } else if (vName == QLatin1String("diffuse")) {
990 if (val.isString()) {
991 var = parameterValueFromJSON(GL_SAMPLER_2D, value: val);
992 hasDiffuseMap = true;
993 } else if (val.isArray()) {
994 var = vec4ToColorVariant(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: val));
995 }
996 } else if (vName == QLatin1String("specular")) {
997 if (val.isString()) {
998 var = parameterValueFromJSON(GL_SAMPLER_2D, value: val);
999 hasSpecularMap = true;
1000 } else if (val.isArray()) {
1001 var = vec4ToColorVariant(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: val));
1002 }
1003 } else if (vName == QLatin1String("cool")) { // Custom Qt3D extension for gooch
1004 var = vec4ToColorVariant(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: val));
1005 } else if (vName == QLatin1String("warm")) { // Custom Qt3D extension for gooch
1006 var = vec4ToColorVariant(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: val));
1007 } else if (vName == QLatin1String("shininess") && val.isDouble()) {
1008 var = parameterValueFromJSON(GL_FLOAT, value: val);
1009 } else if (vName == QLatin1String("normalmap") && val.isString()) {
1010 var = parameterValueFromJSON(GL_SAMPLER_2D, value: val);
1011 propertyName = QStringLiteral("normal");
1012 hasNormalMap = true;
1013 } else if (vName == QLatin1String("transparency")) {
1014 var = parameterValueFromJSON(GL_FLOAT, value: val);
1015 propertyName = QStringLiteral("alpha");
1016 hasAlpha = true;
1017 } else if (vName == QLatin1String("transparent")) {
1018 hasAlpha = parameterValueFromJSON(GL_BOOL, value: val).toBool();
1019 } else if (vName == QLatin1String("textureScale")) {
1020 var = parameterValueFromJSON(GL_FLOAT, value: val);
1021 propertyName = QStringLiteral("textureScale");
1022 } else if (vName == QLatin1String("alpha")) { // Custom Qt3D extension for gooch
1023 var = parameterValueFromJSON(GL_FLOAT, value: val);
1024 } else if (vName == QLatin1String("beta")) { // Custom Qt3D extension for gooch
1025 var = parameterValueFromJSON(GL_FLOAT, value: val);
1026 }
1027 if (var.isValid())
1028 params[propertyName] = var;
1029 }
1030
1031 const QJsonObject funcValues = jsonExt.value(KEY_FUNCTIONS).toObject();
1032 if (!funcValues.isEmpty()) {
1033 const QJsonArray fArray = funcValues[KEY_BLEND_FUNCTION].toArray();
1034 const QJsonArray eArray = funcValues[KEY_BLEND_EQUATION].toArray();
1035 if (fArray.size() == 4) {
1036 params[QStringLiteral("sourceRgbArg")] = fArray[0].toInt();
1037 params[QStringLiteral("sourceAlphaArg")] = fArray[1].toInt();
1038 params[QStringLiteral("destinationRgbArg")] = fArray[2].toInt();
1039 params[QStringLiteral("destinationAlphaArg")] = fArray[3].toInt();
1040 }
1041 // We get separate values but our QPhongAlphaMaterial only supports single argument,
1042 // so we just use the first one.
1043 if (eArray.size() == 2)
1044 params[QStringLiteral("blendFunctionArg")] = eArray[0].toInt();
1045 }
1046
1047 QMaterial *mat = nullptr;
1048 const QString technique = jsonExt.value(KEY_TECHNIQUE).toString();
1049 if (technique == QStringLiteral("PHONG")) {
1050 if (hasNormalMap) {
1051 if (hasSpecularMap) {
1052 mat = new QNormalDiffuseSpecularMapMaterial;
1053 } else {
1054 if (Q_UNLIKELY(!hasDiffuseMap)) {
1055 qCWarning(GLTFImporterLog, "Common material with normal and specular maps needs a diffuse map as well");
1056 } else {
1057 if (hasAlpha)
1058 mat = new QNormalDiffuseMapAlphaMaterial;
1059 else
1060 mat = new QNormalDiffuseMapMaterial;
1061 }
1062 }
1063 } else {
1064 if (hasSpecularMap) {
1065 if (Q_UNLIKELY(!hasDiffuseMap))
1066 qCWarning(GLTFImporterLog, "Common material with specular map needs a diffuse map as well");
1067 else
1068 mat = new QDiffuseSpecularMapMaterial;
1069 } else if (hasDiffuseMap) {
1070 mat = new QDiffuseMapMaterial;
1071 } else {
1072 if (hasAlpha)
1073 mat = new QPhongAlphaMaterial;
1074 else
1075 mat = new QPhongMaterial;
1076 }
1077 }
1078 } else if (technique == QStringLiteral("GOOCH")) { // Qt3D specific extension
1079 mat = new QGoochMaterial;
1080 } else if (technique == QStringLiteral("PERVERTEX")) { // Qt3D specific extension
1081 mat = new QPerVertexColorMaterial;
1082 }
1083
1084 if (Q_UNLIKELY(!mat)) {
1085 qCWarning(GLTFImporterLog, "Could not find a suitable built-in material for KHR_materials_common");
1086 } else {
1087 for (QVariantHash::const_iterator it = params.constBegin(), itEnd = params.constEnd(); it != itEnd; ++it)
1088 mat->setProperty(name: it.key().toUtf8(), value: it.value());
1089 }
1090
1091 renameFromJson(json: jsonObj, object: mat);
1092
1093 return mat;
1094}
1095
1096QMaterial *GLTFImporter::pbrMaterial(const QJsonObject &jsonObj)
1097{
1098 // check for pbrMetallicRoughness material
1099 QMetalRoughMaterial *mrMaterial = nullptr;
1100 QJsonValue jsonValue = jsonObj.value(KEY_PBR_METAL_ROUGH);
1101
1102 if (!jsonValue.isUndefined()) {
1103 const QJsonObject pbrObj = jsonValue.toObject();
1104 mrMaterial = new QMetalRoughMaterial;
1105 jsonValue = pbrObj.value(KEY_BASE_COLOR);
1106 if (!jsonValue.isUndefined())
1107 mrMaterial->setBaseColor(jsonArrToColorVariant(array: jsonValue.toArray()));
1108
1109 jsonValue = pbrObj.value(KEY_BASE_COLOR_TEX);
1110 if (!jsonValue.isUndefined()) {
1111 const QJsonObject texObj = jsonValue.toObject();
1112 const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
1113 const auto it = m_textures.find(akey: textureId);
1114 if (Q_UNLIKELY(it == m_textures.end())) {
1115 qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
1116 } else {
1117 mrMaterial->setBaseColor(QVariant::fromValue(value: it.value()));
1118 }
1119 }
1120
1121 jsonValue = pbrObj.value(KEY_METAL_FACTOR);
1122 if (!jsonValue.isUndefined())
1123 mrMaterial->setMetalness(jsonValue.toVariant());
1124
1125 jsonValue = pbrObj.value(KEY_METAL_ROUGH_TEX);
1126 if (!jsonValue.isUndefined()) {
1127 const QJsonObject texObj = jsonValue.toObject();
1128 const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
1129 const auto it = m_textures.find(akey: textureId);
1130 if (Q_UNLIKELY(it == m_textures.end())) {
1131 qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
1132 } else {
1133 // find the texture again
1134 const QJsonArray texArray = m_json.object().value(KEY_TEXTURES).toArray();
1135 const QJsonObject tObj = texArray.at(i: texObj.value(KEY_INDEX).toInt()).toObject();
1136 const QString sourceId = QString::number(tObj.value(KEY_SOURCE).toInt());
1137 QImage image;
1138 if (m_imagePaths.contains(akey: sourceId)) {
1139 image.load(fileName: m_imagePaths.value(akey: sourceId));
1140 } else if (m_imageData.contains(akey: sourceId)) {
1141 image = m_imageData.value(akey: sourceId);
1142 } else {
1143 return mrMaterial;
1144 }
1145
1146 // at this point, in image there is data for metalness (on B) and
1147 // roughness (on G) bytes. 2 new textures are created
1148 // to make Qt3D happy, since it samples only on R.
1149
1150 QTexture2D* metalTex = new QTexture2D;
1151 QTexture2D* roughTex = new QTexture2D;
1152 GLTFRawTextureImage* metalImgTex = new GLTFRawTextureImage();
1153 GLTFRawTextureImage* roughImgTex = new GLTFRawTextureImage();
1154 QImage metalness(image.size(), image.format());
1155 QImage roughness(image.size(), image.format());
1156
1157 const uchar *imgData = image.constBits();
1158 const int pixelBytes = image.depth() / 8;
1159 Q_ASSERT_X(pixelBytes < 3, "GLTFImporter::pbrMaterial", "Unsupported texture format");
1160
1161 for (int y = 0; y < image.height(); y++) {
1162 for (int x = 0; x < image.width(); x++) {
1163 metalness.setPixel(x, y, index_or_rgb: qRgb(r: imgData[0], g: imgData[0], b: imgData[0]));
1164 roughness.setPixel(x, y, index_or_rgb: qRgb(r: imgData[1], g: imgData[1], b: imgData[1]));
1165 imgData += pixelBytes;
1166 }
1167 }
1168
1169 metalImgTex->setImage(metalness);
1170 metalTex->addTextureImage(textureImage: metalImgTex);
1171 roughImgTex->setImage(roughness);
1172 roughTex->addTextureImage(textureImage: roughImgTex);
1173
1174 setTextureSamplerInfo(id: "", jsonObj: tObj, tex: metalTex);
1175 setTextureSamplerInfo(id: "", jsonObj: tObj, tex: roughTex);
1176
1177 mrMaterial->setMetalness(QVariant::fromValue(value: metalTex));
1178 mrMaterial->setRoughness(QVariant::fromValue(value: roughTex));
1179 }
1180 }
1181
1182 jsonValue = pbrObj.value(KEY_ROUGH_FACTOR);
1183 if (!jsonValue.isUndefined())
1184 mrMaterial->setRoughness(jsonValue.toVariant());
1185 }
1186
1187 jsonValue = jsonObj.value(KEY_NORMAL_TEX);
1188 if (!jsonValue.isUndefined()) {
1189 const QJsonObject texObj = jsonValue.toObject();
1190 const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
1191 const auto it = m_textures.find(akey: textureId);
1192 if (Q_UNLIKELY(it == m_textures.end())) {
1193 qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
1194 } else {
1195 if (mrMaterial)
1196 mrMaterial->setNormal(QVariant::fromValue(value: it.value()));
1197 }
1198 }
1199
1200 jsonValue = jsonObj.value(KEY_OCCLUSION_TEX);
1201 if (!jsonValue.isUndefined()) {
1202 const QJsonObject texObj = jsonValue.toObject();
1203 const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
1204 const auto it = m_textures.find(akey: textureId);
1205 if (Q_UNLIKELY(it == m_textures.end())) {
1206 qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
1207 } else {
1208 if (mrMaterial)
1209 mrMaterial->setAmbientOcclusion(QVariant::fromValue(value: it.value()));
1210 }
1211 }
1212
1213 return mrMaterial;
1214}
1215
1216QMaterial* GLTFImporter::material(const QString &id)
1217{
1218 const auto it = qAsConst(t&: m_materialCache).find(akey: id);
1219 if (it != m_materialCache.cend())
1220 return it.value();
1221
1222 QJsonValue jsonVal;
1223
1224 if (m_majorVersion > 1) {
1225 const QJsonArray mats = m_json.object().value(KEY_MATERIALS).toArray();
1226 jsonVal = mats.at(i: id.toInt());
1227 } else {
1228 const QJsonObject mats = m_json.object().value(KEY_MATERIALS).toObject();
1229 jsonVal = mats.value(key: id);
1230 }
1231
1232 if (Q_UNLIKELY(jsonVal.isUndefined())) {
1233 qCWarning(GLTFImporterLog, "unknown material %ls in GLTF file %ls",
1234 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
1235 return nullptr;
1236 }
1237
1238 const QJsonObject jsonObj = jsonVal.toObject();
1239
1240 QMaterial *mat = nullptr;
1241
1242 // Prefer common materials over custom shaders.
1243 mat = commonMaterial(jsonObj);
1244 if (!mat)
1245 mat = materialWithCustomShader(id, jsonObj);
1246
1247 m_materialCache[id] = mat;
1248 return mat;
1249}
1250
1251bool GLTFImporter::fillCamera(QCameraLens &lens, QCamera *cameraEntity, const QString &id) const
1252{
1253 QJsonObject jsonObj;
1254
1255 if (m_majorVersion > 1) {
1256 const QJsonArray camArray = m_json.object().value(KEY_CAMERAS).toArray();
1257 if (id.toInt() >= camArray.count()) {
1258 qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls",
1259 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
1260 return false;
1261 }
1262 jsonObj = camArray[id.toInt()].toObject();
1263 } else {
1264 const auto jsonVal = m_json.object().value(KEY_CAMERAS).toObject().value(key: id);
1265 if (Q_UNLIKELY(jsonVal.isUndefined())) {
1266 qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls",
1267 qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
1268 return false;
1269 }
1270 jsonObj = jsonVal.toObject();
1271 }
1272
1273 QString camTy = jsonObj.value(KEY_TYPE).toString();
1274
1275 if (camTy == QLatin1String("perspective")) {
1276 const auto pVal = jsonObj.value(KEY_PERSPECTIVE);
1277 if (Q_UNLIKELY(pVal.isUndefined())) {
1278 qCWarning(GLTFImporterLog, "camera: %ls missing 'perspective' object",
1279 qUtf16PrintableImpl(id));
1280 return false;
1281 }
1282
1283 const QJsonObject pObj = pVal.toObject();
1284 double aspectRatio = pObj.value(KEY_ASPECT_RATIO).toDouble();
1285 double yfov = pObj.value(KEY_YFOV).toDouble();
1286 double frustumNear = pObj.value(KEY_ZNEAR).toDouble();
1287 double frustumFar = pObj.value(KEY_ZFAR).toDouble();
1288
1289 lens.setPerspectiveProjection(fieldOfView: qRadiansToDegrees(radians: yfov), aspect: aspectRatio, nearPlane: frustumNear,
1290 farPlane: frustumFar);
1291 } else if (camTy == QLatin1String("orthographic")) {
1292 const auto pVal = jsonObj.value(KEY_ORTHOGRAPHIC);
1293 if (Q_UNLIKELY(pVal.isUndefined())) {
1294 qCWarning(GLTFImporterLog, "camera: %ls missing 'orthographic' object",
1295 qUtf16PrintableImpl(id));
1296 return false;
1297 }
1298
1299 const QJsonObject pObj = pVal.toObject();
1300 double xmag = pObj.value(KEY_XMAG).toDouble() / 2.0f;
1301 double ymag = pObj.value(KEY_YMAG).toDouble() / 2.0f;
1302 double frustumNear = pObj.value(KEY_ZNEAR).toDouble();
1303 double frustumFar = pObj.value(KEY_ZFAR).toDouble();
1304
1305 lens.setOrthographicProjection(left: -xmag, right: xmag, bottom: -ymag, top: ymag, nearPlane: frustumNear, farPlane: frustumFar);
1306 } else {
1307 qCWarning(GLTFImporterLog, "camera: %ls has unsupported type: %ls",
1308 qUtf16PrintableImpl(id), qUtf16PrintableImpl(camTy));
1309 return false;
1310 }
1311 if (cameraEntity) {
1312 if (jsonObj.contains(KEY_POSITION))
1313 cameraEntity->setPosition(jsonArrToVec3(array: jsonObj.value(KEY_POSITION).toArray()));
1314 if (jsonObj.contains(KEY_UPVECTOR))
1315 cameraEntity->setUpVector(jsonArrToVec3(array: jsonObj.value(KEY_UPVECTOR).toArray()));
1316 if (jsonObj.contains(KEY_VIEW_CENTER))
1317 cameraEntity->setViewCenter(jsonArrToVec3(array: jsonObj.value(KEY_VIEW_CENTER).toArray()));
1318 }
1319 renameFromJson(json: jsonObj, object: &lens);
1320 return true;
1321}
1322
1323void GLTFImporter::parse()
1324{
1325 if (m_parseDone)
1326 return;
1327
1328 const QJsonValue asset = m_json.object().value(KEY_ASSET);
1329 if (!asset.isUndefined())
1330 processJSONAsset(json: asset.toObject());
1331
1332 if (m_majorVersion > 1) {
1333 parseV2();
1334 } else {
1335 parseV1();
1336 }
1337
1338 m_parseDone = true;
1339}
1340
1341void GLTFImporter::parseV1()
1342{
1343 const QJsonObject buffers = m_json.object().value(KEY_BUFFERS).toObject();
1344 for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it)
1345 processJSONBuffer(id: it.key(), json: it.value().toObject());
1346
1347 const QJsonObject views = m_json.object().value(KEY_BUFFER_VIEWS).toObject();
1348 loadBufferData();
1349 for (auto it = views.begin(), end = views.end(); it != end; ++it)
1350 processJSONBufferView(id: it.key(), json: it.value().toObject());
1351 unloadBufferData();
1352
1353 const QJsonObject shaders = m_json.object().value(KEY_SHADERS).toObject();
1354 for (auto it = shaders.begin(), end = shaders.end(); it != end; ++it)
1355 processJSONShader(id: it.key(), jsonObject: it.value().toObject());
1356
1357 const QJsonObject programs = m_json.object().value(KEY_PROGRAMS).toObject();
1358 for (auto it = programs.begin(), end = programs.end(); it != end; ++it)
1359 processJSONProgram(id: it.key(), jsonObject: it.value().toObject());
1360
1361 const QJsonObject attrs = m_json.object().value(KEY_ACCESSORS).toObject();
1362 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
1363 processJSONAccessor(id: it.key(), json: it.value().toObject());
1364
1365 const QJsonObject meshes = m_json.object().value(KEY_MESHES).toObject();
1366 for (auto it = meshes.begin(), end = meshes.end(); it != end; ++it)
1367 processJSONMesh(id: it.key(), json: it.value().toObject());
1368
1369 const QJsonObject images = m_json.object().value(KEY_IMAGES).toObject();
1370 for (auto it = images.begin(), end = images.end(); it != end; ++it)
1371 processJSONImage(id: it.key(), jsonObject: it.value().toObject());
1372
1373 const QJsonObject textures = m_json.object().value(KEY_TEXTURES).toObject();
1374 for (auto it = textures.begin(), end = textures.end(); it != end; ++it)
1375 processJSONTexture(id: it.key(), jsonObject: it.value().toObject());
1376
1377 const QJsonObject extensions = m_json.object().value(KEY_EXTENSIONS).toObject();
1378 for (auto it = extensions.begin(), end = extensions.end(); it != end; ++it)
1379 processJSONExtensions(id: it.key(), jsonObject: it.value().toObject());
1380
1381 const QJsonObject passes = m_json.object().value(KEY_RENDERPASSES).toObject();
1382 for (auto it = passes.begin(), end = passes.end(); it != end; ++it)
1383 processJSONRenderPass(id: it.key(), jsonObject: it.value().toObject());
1384
1385 const QJsonObject techniques = m_json.object().value(KEY_TECHNIQUES).toObject();
1386 for (auto it = techniques.begin(), end = techniques.end(); it != end; ++it)
1387 processJSONTechnique(id: it.key(), jsonObject: it.value().toObject());
1388
1389 const QJsonObject effects = m_json.object().value(KEY_EFFECTS).toObject();
1390 for (auto it = effects.begin(), end = effects.end(); it != end; ++it)
1391 processJSONEffect(id: it.key(), jsonObject: it.value().toObject());
1392
1393 m_defaultScene = m_json.object().value(KEY_SCENE).toString();
1394}
1395
1396void GLTFImporter::parseV2()
1397{
1398 int i;
1399 const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
1400 for (i = 0; i < buffers.count(); i++)
1401 processJSONBuffer(id: QString::number(i), json: buffers[i].toObject());
1402
1403 const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
1404 loadBufferData();
1405 for (i = 0; i < views.count(); i++)
1406 processJSONBufferView(id: QString::number(i), json: views[i].toObject());
1407 unloadBufferData();
1408
1409 const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
1410 for (i = 0; i < accessors.count(); i++)
1411 processJSONAccessor(id: QString::number(i), json: accessors[i].toObject());
1412
1413 const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray();
1414 for (i = 0; i < meshes.count(); i++)
1415 processJSONMesh(id: QString::number(i), json: meshes[i].toObject());
1416
1417 const QJsonArray images = m_json.object().value(KEY_IMAGES).toArray();
1418 for (i = 0; i < images.count(); i++)
1419 processJSONImage(id: QString::number(i), jsonObject: images[i].toObject());
1420
1421 const QJsonArray textures = m_json.object().value(KEY_TEXTURES).toArray();
1422 for (i = 0; i < textures.count(); i++)
1423 processJSONTexture(id: QString::number(i), jsonObject: textures[i].toObject());
1424
1425 m_defaultScene = QString::number(m_json.object().value(KEY_SCENE).toInt());
1426}
1427
1428namespace {
1429template <typename C>
1430void delete_if_without_parent(const C &c)
1431{
1432 for (const auto *e : c) {
1433 if (!e->parent())
1434 delete e;
1435 }
1436}
1437} // unnamed namespace
1438
1439void GLTFImporter::cleanup()
1440{
1441 m_meshDict.clear();
1442 m_meshMaterialDict.clear();
1443 m_accessorDict.clear();
1444 delete_if_without_parent(c: m_materialCache);
1445 m_materialCache.clear();
1446 m_bufferDatas.clear();
1447 m_buffers.clear();
1448 m_shaderPaths.clear();
1449 delete_if_without_parent(c: m_programs);
1450 m_programs.clear();
1451 for (const auto &params : qAsConst(t&: m_techniqueParameters))
1452 delete_if_without_parent(c: params);
1453 m_techniqueParameters.clear();
1454 delete_if_without_parent(c: m_techniques);
1455 m_techniques.clear();
1456 delete_if_without_parent(c: m_textures);
1457 m_textures.clear();
1458 m_imagePaths.clear();
1459 m_imageData.clear();
1460 m_defaultScene.clear();
1461 m_parameterDataDict.clear();
1462 delete_if_without_parent(c: m_renderPasses);
1463 m_renderPasses.clear();
1464 delete_if_without_parent(c: m_effects);
1465 m_effects.clear();
1466}
1467
1468void GLTFImporter::processJSONAsset(const QJsonObject &json)
1469{
1470 const QString version = json.value(KEY_VERSION).toString();
1471 if (!version.isEmpty()) {
1472 const QStringList verTokens = version.split(sep: '.');
1473 if (verTokens.length() >= 2) {
1474 m_majorVersion = verTokens[0].toInt();
1475 m_minorVersion = verTokens[1].toInt();
1476 }
1477 }
1478}
1479
1480void GLTFImporter::processJSONBuffer(const QString &id, const QJsonObject& json)
1481{
1482 // simply cache buffers for lookup by buffer-views
1483 m_bufferDatas[id] = BufferData(json);
1484}
1485
1486void GLTFImporter::processJSONBufferView(const QString &id, const QJsonObject& json)
1487{
1488 QString bufName;
1489 if (m_majorVersion > 1) {
1490 bufName = QString::number(json.value(KEY_BUFFER).toInt());
1491 } else {
1492 bufName = json.value(KEY_BUFFER).toString();
1493 }
1494 const auto it = qAsConst(t&: m_bufferDatas).find(akey: bufName);
1495 if (Q_UNLIKELY(it == m_bufferDatas.cend())) {
1496 qCWarning(GLTFImporterLog, "unknown buffer: %ls processing view: %ls",
1497 qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id));
1498 return;
1499 }
1500 const auto &bufferData = *it;
1501
1502 const QJsonValue targetValue = json.value(KEY_TARGET);
1503 int target;
1504 if (targetValue.isUndefined()) {
1505 target = GL_ARRAY_BUFFER;
1506 } else {
1507 target = targetValue.toInt();
1508 }
1509
1510 quint64 offset = 0;
1511 const auto byteOffset = json.value(KEY_BYTE_OFFSET);
1512 if (!byteOffset.isUndefined()) {
1513 offset = byteOffset.toInt();
1514 qCDebug(GLTFImporterLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset);
1515 }
1516
1517 quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
1518
1519 QByteArray bytes = bufferData.data->mid(index: offset, len);
1520 if (Q_UNLIKELY(bytes.count() != int(len))) {
1521 qCWarning(GLTFImporterLog, "failed to read sufficient bytes from: %ls for view %ls",
1522 qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id));
1523 }
1524
1525 Qt3DRender::QBuffer *b = new Qt3DRender::QBuffer();
1526 b->setData(bytes);
1527 m_buffers[id] = b;
1528}
1529
1530void GLTFImporter::processJSONShader(const QString &id, const QJsonObject &jsonObject)
1531{
1532 // shaders are trivial for the moment, defer the real work
1533 // to the program section
1534 QString path = jsonObject.value(KEY_URI).toString();
1535
1536 if (!isEmbeddedResource(url: path)) {
1537 QFileInfo info(m_basePath, path);
1538 if (Q_UNLIKELY(!info.exists())) {
1539 qCWarning(GLTFImporterLog, "can't find shader %ls from path %ls",
1540 qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
1541 return;
1542 }
1543
1544 m_shaderPaths[id] = info.absoluteFilePath();
1545 } else {
1546 const QByteArray base64Data = path.toLatin1().remove(index: 0, len: path.indexOf(s: ",") + 1);
1547 m_shaderPaths[id] = QString(QByteArray::fromBase64(base64: base64Data));
1548 }
1549
1550
1551}
1552
1553void GLTFImporter::processJSONProgram(const QString &id, const QJsonObject &jsonObject)
1554{
1555 const QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString();
1556 const QString vertName = jsonObject.value(KEY_VERTEX_SHADER).toString();
1557
1558 const auto fragIt = qAsConst(t&: m_shaderPaths).find(akey: fragName);
1559 const auto vertIt = qAsConst(t&: m_shaderPaths).find(akey: vertName);
1560
1561 if (Q_UNLIKELY(fragIt == m_shaderPaths.cend() || vertIt == m_shaderPaths.cend())) {
1562 qCWarning(GLTFImporterLog, "program: %ls missing shader: %ls %ls",
1563 qUtf16PrintableImpl(id), qUtf16PrintableImpl(fragName), qUtf16PrintableImpl(vertName));
1564 return;
1565 }
1566
1567 QShaderProgram* prog = new QShaderProgram;
1568 prog->setObjectName(id);
1569 prog->setFragmentShaderCode(QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: fragIt.value())));
1570 prog->setVertexShaderCode(QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: vertIt.value())));
1571
1572 const QString tessCtrlName = jsonObject.value(KEY_TESS_CTRL_SHADER).toString();
1573 if (!tessCtrlName.isEmpty()) {
1574 const auto it = qAsConst(t&: m_shaderPaths).find(akey: tessCtrlName);
1575 prog->setTessellationControlShaderCode(
1576 QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: it.value())));
1577 }
1578
1579 const QString tessEvalName = jsonObject.value(KEY_TESS_EVAL_SHADER).toString();
1580 if (!tessEvalName.isEmpty()) {
1581 const auto it = qAsConst(t&: m_shaderPaths).find(akey: tessEvalName);
1582 prog->setTessellationEvaluationShaderCode(
1583 QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: it.value())));
1584 }
1585
1586 const QString geomName = jsonObject.value(KEY_GEOMETRY_SHADER).toString();
1587 if (!geomName.isEmpty()) {
1588 const auto it = qAsConst(t&: m_shaderPaths).find(akey: geomName);
1589 prog->setGeometryShaderCode(QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: it.value())));
1590 }
1591
1592 const QString computeName = jsonObject.value(KEY_COMPUTE_SHADER).toString();
1593 if (!computeName.isEmpty()) {
1594 const auto it = qAsConst(t&: m_shaderPaths).find(akey: computeName);
1595 prog->setComputeShaderCode(QShaderProgram::loadSource(sourceUrl: QUrl::fromLocalFile(localfile: it.value())));
1596 }
1597
1598 m_programs[id] = prog;
1599}
1600
1601void GLTFImporter::processJSONTechnique(const QString &id, const QJsonObject &jsonObject )
1602{
1603 QTechnique *t = new QTechnique;
1604 t->setObjectName(id);
1605
1606 const QJsonObject gabifilter = jsonObject.value(KEY_GABIFILTER).toObject();
1607 if (gabifilter.isEmpty()) {
1608 // Regular GLTF technique
1609
1610 // Parameters
1611 QHash<QString, QParameter *> paramDict;
1612 const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
1613 for (auto it = params.begin(), end = params.end(); it != end; ++it) {
1614 const QString pname = it.key();
1615 const QJsonObject po = it.value().toObject();
1616 QParameter *p = buildParameter(key: pname, paramObj: po);
1617 m_parameterDataDict.insert(akey: p, avalue: ParameterData(po));
1618 // We don't want to insert invalid parameters to techniques themselves, but we
1619 // need to maintain link between all parameters and techniques so we can resolve
1620 // parameter type later when creating materials.
1621 if (p->value().isValid())
1622 t->addParameter(p);
1623 paramDict[pname] = p;
1624 }
1625
1626 // Program
1627 QRenderPass *pass = new QRenderPass;
1628 addProgramToPass(pass, progName: jsonObject.value(KEY_PROGRAM).toString());
1629
1630 // Attributes
1631 const QJsonObject attrs = jsonObject.value(KEY_ATTRIBUTES).toObject();
1632 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
1633 QString pname = it.value().toString();
1634 QParameter *parameter = paramDict.value(akey: pname, adefaultValue: nullptr);
1635 QString attributeName = pname;
1636 if (Q_UNLIKELY(!parameter)) {
1637 qCWarning(GLTFImporterLog, "attribute %ls defined in instanceProgram but not as parameter",
1638 qUtf16PrintableImpl(pname));
1639 continue;
1640 }
1641 //Check if the parameter has a standard attribute semantic
1642 const auto paramDataIt = m_parameterDataDict.find(akey: parameter);
1643 QString standardAttributeName = standardAttributeNameFromSemantic(semantic: paramDataIt->semantic);
1644 if (!standardAttributeName.isNull()) {
1645 attributeName = standardAttributeName;
1646 t->removeParameter(p: parameter);
1647 m_parameterDataDict.erase(it: paramDataIt);
1648 paramDict.remove(akey: pname);
1649 delete parameter;
1650 }
1651
1652 } // of program-instance attributes
1653
1654 // Uniforms
1655 const QJsonObject uniforms = jsonObject.value(KEY_UNIFORMS).toObject();
1656 for (auto it = uniforms.begin(), end = uniforms.end(); it != end; ++it) {
1657 const QString pname = it.value().toString();
1658 QParameter *parameter = paramDict.value(akey: pname, adefaultValue: nullptr);
1659 if (Q_UNLIKELY(!parameter)) {
1660 qCWarning(GLTFImporterLog, "uniform %ls defined in instanceProgram but not as parameter",
1661 qUtf16PrintableImpl(pname));
1662 continue;
1663 }
1664 //Check if the parameter has a standard uniform semantic
1665 const auto paramDataIt = m_parameterDataDict.find(akey: parameter);
1666 if (hasStandardUniformNameFromSemantic(semantic: paramDataIt->semantic)) {
1667 t->removeParameter(p: parameter);
1668 m_parameterDataDict.erase(it: paramDataIt);
1669 paramDict.remove(akey: pname);
1670 delete parameter;
1671 }
1672 } // of program-instance uniforms
1673
1674 m_techniqueParameters.insert(akey: t, avalue: paramDict.values());
1675
1676 populateRenderStates(pass, states: jsonObject.value(KEY_STATES).toObject());
1677
1678 t->addRenderPass(pass);
1679 } else {
1680 // Qt3D exported custom technique
1681
1682 // Graphics API filter
1683 t->graphicsApiFilter()->setApi(QGraphicsApiFilter::Api(gabifilter.value(KEY_API).toInt()));
1684 t->graphicsApiFilter()->setMajorVersion(gabifilter.value(KEY_MAJORVERSION).toInt());
1685 t->graphicsApiFilter()->setMinorVersion(gabifilter.value(KEY_MINORVERSION).toInt());
1686 t->graphicsApiFilter()->setProfile(QGraphicsApiFilter::OpenGLProfile(
1687 gabifilter.value(KEY_PROFILE).toInt()));
1688 t->graphicsApiFilter()->setVendor(gabifilter.value(KEY_VENDOR).toString());
1689 QStringList extensionList;
1690 QJsonArray extArray = gabifilter.value(KEY_EXTENSIONS).toArray();
1691 for (const QJsonValue &extValue : extArray)
1692 extensionList << extValue.toString();
1693 t->graphicsApiFilter()->setExtensions(extensionList);
1694
1695 // Filter keys (we will assume filter keys are always strings or integers)
1696 const QJsonObject fkObj = jsonObject.value(KEY_FILTERKEYS).toObject();
1697 for (auto it = fkObj.begin(), end = fkObj.end(); it != end; ++it)
1698 t->addFilterKey(filterKey: buildFilterKey(key: it.key(), val: it.value()));
1699
1700 t->setObjectName(jsonObject.value(KEY_NAME).toString());
1701
1702 // Parameters
1703 const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
1704 for (auto it = params.begin(), end = params.end(); it != end; ++it)
1705 t->addParameter(p: buildParameter(key: it.key(), paramObj: it.value().toObject()));
1706
1707 // Render passes
1708 const QJsonArray passArray = jsonObject.value(KEY_RENDERPASSES).toArray();
1709 for (const QJsonValue &passValue : passArray) {
1710 const QString passName = passValue.toString();
1711 QRenderPass *pass = m_renderPasses.value(akey: passName);
1712 if (pass) {
1713 t->addRenderPass(pass);
1714 } else {
1715 qCWarning(GLTFImporterLog, "Render pass %ls missing for technique %ls",
1716 qUtf16PrintableImpl(passName), qUtf16PrintableImpl(id));
1717 }
1718 }
1719 }
1720
1721 m_techniques[id] = t;
1722}
1723
1724void GLTFImporter::processJSONAccessor(const QString &id, const QJsonObject& json)
1725{
1726 m_accessorDict[id] = AccessorData(json, m_majorVersion, m_minorVersion);
1727}
1728
1729void GLTFImporter::processJSONMesh(const QString &id, const QJsonObject &json)
1730{
1731 const QString meshName = json.value(KEY_NAME).toString();
1732 const QString meshType = json.value(KEY_TYPE).toString();
1733 if (meshType.isEmpty()) {
1734 // Custom mesh
1735 const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
1736 for (const QJsonValue &primitiveValue : primitivesArray) {
1737 const QJsonObject primitiveObject = primitiveValue.toObject();
1738 const QJsonValue type = primitiveObject.value(KEY_MODE);
1739 const QJsonValue matValue = primitiveObject.value(KEY_MATERIAL);
1740 const QString material = (m_majorVersion > 1) ? QString::number(matValue.toInt()) : matValue.toString();
1741
1742 QGeometryRenderer *geometryRenderer = new QGeometryRenderer;
1743 QGeometry *meshGeometry = new QGeometry(geometryRenderer);
1744
1745 //Set Primitive Type
1746 if (type.isUndefined()) {
1747 geometryRenderer->setPrimitiveType(QGeometryRenderer::Triangles);
1748 } else {
1749 geometryRenderer->setPrimitiveType(static_cast<QGeometryRenderer::PrimitiveType>(type.toInt()));
1750 }
1751
1752 //Save Material for mesh
1753 m_meshMaterialDict[geometryRenderer] = material;
1754
1755 const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
1756 for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
1757 const QString k = (m_majorVersion > 1) ? QString::number(it.value().toInt()) : it.value().toString();
1758 const auto accessorIt = qAsConst(t&: m_accessorDict).find(akey: k);
1759 if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
1760 qCWarning(GLTFImporterLog, "unknown attribute accessor: %ls on mesh %ls",
1761 qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
1762 continue;
1763 }
1764
1765 const QString attrName = it.key();
1766 QString attributeName = standardAttributeNameFromSemantic(semantic: attrName);
1767 if (attributeName.isEmpty())
1768 attributeName = attrName;
1769
1770 //Get buffer handle for accessor
1771 Qt3DRender::QBuffer *buffer = m_buffers.value(akey: accessorIt->bufferViewName, adefaultValue: nullptr);
1772 if (Q_UNLIKELY(!buffer)) {
1773 qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls",
1774 qUtf16PrintableImpl(accessorIt->bufferViewName),
1775 qUtf16PrintableImpl(id));
1776 continue;
1777 }
1778
1779 QAttribute *attribute = new QAttribute(buffer,
1780 attributeName,
1781 accessorIt->type,
1782 accessorIt->dataSize,
1783 accessorIt->count,
1784 accessorIt->offset,
1785 accessorIt->stride);
1786 attribute->setAttributeType(QAttribute::VertexAttribute);
1787 meshGeometry->addAttribute(attribute);
1788 }
1789
1790 const auto indices = primitiveObject.value(KEY_INDICES);
1791 if (!indices.isUndefined()) {
1792 const QString accIndex = (m_majorVersion > 1) ? QString::number(indices.toInt()) : indices.toString();
1793 const auto accessorIt = qAsConst(t&: m_accessorDict).find(akey: accIndex);
1794 if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
1795 qCWarning(GLTFImporterLog, "unknown index accessor: %ls on mesh %ls",
1796 qUtf16PrintableImpl(accIndex), qUtf16PrintableImpl(id));
1797 } else {
1798 //Get buffer handle for accessor
1799 Qt3DRender::QBuffer *buffer = m_buffers.value(akey: accessorIt->bufferViewName, adefaultValue: nullptr);
1800 if (Q_UNLIKELY(!buffer)) {
1801 qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls",
1802 qUtf16PrintableImpl(accessorIt->bufferViewName),
1803 qUtf16PrintableImpl(id));
1804 continue;
1805 }
1806
1807 QAttribute *attribute = new QAttribute(buffer,
1808 accessorIt->type,
1809 accessorIt->dataSize,
1810 accessorIt->count,
1811 accessorIt->offset,
1812 accessorIt->stride);
1813 attribute->setAttributeType(QAttribute::IndexAttribute);
1814 meshGeometry->addAttribute(attribute);
1815 }
1816 } // of has indices
1817
1818 geometryRenderer->setGeometry(meshGeometry);
1819 geometryRenderer->setObjectName(meshName);
1820
1821 m_meshDict.insert(akey: id, avalue: geometryRenderer);
1822 } // of primitives iteration
1823 } else {
1824 QGeometryRenderer *mesh = nullptr;
1825 if (meshType == QStringLiteral("cone")) {
1826 mesh = new QConeMesh;
1827 } else if (meshType == QStringLiteral("cuboid")) {
1828 mesh = new QCuboidMesh;
1829 } else if (meshType == QStringLiteral("cylinder")) {
1830 mesh = new QCylinderMesh;
1831 } else if (meshType == QStringLiteral("plane")) {
1832 mesh = new QPlaneMesh;
1833 } else if (meshType == QStringLiteral("sphere")) {
1834 mesh = new QSphereMesh;
1835 } else if (meshType == QStringLiteral("torus")) {
1836 mesh = new QTorusMesh;
1837 } else {
1838 qCWarning(GLTFImporterLog,
1839 "Invalid mesh type: %ls for mesh: %ls",
1840 qUtf16PrintableImpl(meshType),
1841 qUtf16PrintableImpl(id));
1842 }
1843
1844 if (mesh) {
1845 // Read and set properties
1846 const QJsonObject propObj = json.value(KEY_PROPERTIES).toObject();
1847 for (auto it = propObj.begin(), end = propObj.end(); it != end; ++it) {
1848 const QByteArray propName = it.key().toLatin1();
1849 // Basic mesh types only have bool, int, float, and QSize type properties
1850 if (it.value().isBool()) {
1851 mesh->setProperty(name: propName.constData(), value: QVariant(it.value().toBool()));
1852 } else if (it.value().isArray()) {
1853 const QJsonArray valueArray = it.value().toArray();
1854 if (valueArray.size() == 2) {
1855 QSize size;
1856 size.setWidth(valueArray.at(i: 0).toInt());
1857 size.setHeight(valueArray.at(i: 1).toInt());
1858 mesh->setProperty(name: propName.constData(), value: QVariant(size));
1859 }
1860 } else {
1861 const QVariant::Type propType = mesh->property(name: propName.constData()).type();
1862 if (propType == QVariant::Int) {
1863 mesh->setProperty(name: propName.constData(), value: QVariant(it.value().toInt()));
1864 } else {
1865 mesh->setProperty(name: propName.constData(),
1866 value: QVariant(float(it.value().toDouble())));
1867 }
1868 }
1869 }
1870 mesh->setObjectName(meshName);
1871 m_meshMaterialDict[mesh] = (m_majorVersion > 1) ?
1872 QString::number(json.value(KEY_MATERIAL).toInt()) :
1873 json.value(KEY_MATERIAL).toString();
1874 m_meshDict.insert(akey: id, avalue: mesh);
1875 }
1876 }
1877}
1878
1879void GLTFImporter::processJSONImage(const QString &id, const QJsonObject &jsonObject)
1880{
1881 QString path = jsonObject.value(KEY_URI).toString();
1882
1883 if (!isEmbeddedResource(url: path)) {
1884 QFileInfo info(m_basePath, path);
1885 if (Q_UNLIKELY(!info.exists())) {
1886 qCWarning(GLTFImporterLog, "can't find image %ls from path %ls",
1887 qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
1888 return;
1889 }
1890
1891 m_imagePaths[id] = info.absoluteFilePath();
1892 } else {
1893 const QByteArray base64Data = path.toLatin1().remove(index: 0, len: path.indexOf(s: ",") + 1);
1894 QImage image;
1895 image.loadFromData(data: QByteArray::fromBase64(base64: base64Data));
1896 m_imageData[id] = image;
1897 }
1898}
1899
1900void GLTFImporter::processJSONTexture(const QString &id, const QJsonObject &jsonObject)
1901{
1902 QJsonValue jsonVal = jsonObject.value(KEY_TARGET);
1903 if (!jsonVal.isUndefined()) {
1904 int target = jsonVal.toInt(GL_TEXTURE_2D);
1905 //TODO: support other targets that GL_TEXTURE_2D (though the spec doesn't support anything else)
1906 if (Q_UNLIKELY(target != GL_TEXTURE_2D)) {
1907 qCWarning(GLTFImporterLog, "unsupported texture target: %d", target);
1908 return;
1909 }
1910 }
1911
1912 QTexture2D* tex = new QTexture2D;
1913
1914 // TODO: Choose suitable internal format - may vary on OpenGL context type
1915 //int pixelFormat = jsonObj.value(KEY_FORMAT).toInt(GL_RGBA);
1916 int internalFormat = GL_RGBA;
1917 jsonVal = jsonObject.value(KEY_INTERNAL_FORMAT);
1918 if (!jsonVal.isUndefined())
1919 internalFormat = jsonObject.value(KEY_INTERNAL_FORMAT).toInt(GL_RGBA);
1920
1921 tex->setFormat(static_cast<QAbstractTexture::TextureFormat>(internalFormat));
1922
1923 QJsonValue srcValue = jsonObject.value(KEY_SOURCE);
1924 QString source = (m_majorVersion > 1) ? QString::number(srcValue.toInt()) : srcValue.toString();
1925
1926 const auto imagIt = qAsConst(t&: m_imagePaths).find(akey: source);
1927 if (Q_UNLIKELY(imagIt == m_imagePaths.cend())) {
1928 // if an image is not found in paths, it probably means
1929 // it was an embedded resource, referenced in m_imageData
1930 const auto embImgIt = qAsConst(t&: m_imageData).find(akey: source);
1931 if (Q_UNLIKELY(embImgIt == m_imageData.cend())) {
1932 qCWarning(GLTFImporterLog, "texture %ls references missing image %ls",
1933 qUtf16PrintableImpl(id), qUtf16PrintableImpl(source));
1934 return;
1935 }
1936
1937 QImage img = embImgIt.value();
1938 GLTFRawTextureImage *imageData = new GLTFRawTextureImage();
1939 imageData->setImage(img);
1940 tex->addTextureImage(textureImage: imageData);
1941 } else {
1942 QTextureImage *texImage = new QTextureImage(tex);
1943 texImage->setMirrored(false);
1944 texImage->setSource(QUrl::fromLocalFile(localfile: imagIt.value()));
1945 tex->addTextureImage(textureImage: texImage);
1946 }
1947
1948 setTextureSamplerInfo(id, jsonObj: jsonObject, tex);
1949
1950 m_textures[id] = tex;
1951}
1952
1953void GLTFImporter::processJSONExtensions(const QString &id, const QJsonObject &jsonObject)
1954{
1955 // Lights are defined in "KHR_materials_common" property of "extensions" property of the top
1956 // level GLTF item.
1957 if (id == KEY_COMMON_MAT) {
1958 const auto lights = jsonObject.value(KEY_LIGHTS).toObject();
1959 const auto keys = lights.keys();
1960 for (const auto &lightKey : keys) {
1961 const auto light = lights.value(key: lightKey).toObject();
1962 auto lightType = light.value(KEY_TYPE).toString();
1963 const auto lightValues = light.value(key: lightType).toObject();
1964 QAbstractLight *lightComp = nullptr;
1965 if (lightType == KEY_DIRECTIONAL_LIGHT) {
1966 auto dirLight = new QDirectionalLight;
1967 dirLight->setWorldDirection(
1968 jsonArrToVec3(array: lightValues.value(KEY_DIRECTION).toArray()));
1969 lightComp = dirLight;
1970 } else if (lightType == KEY_SPOT_LIGHT) {
1971 auto spotLight = new QSpotLight;
1972 spotLight->setLocalDirection(
1973 jsonArrToVec3(array: lightValues.value(KEY_DIRECTION).toArray()));
1974 spotLight->setConstantAttenuation(
1975 lightValues.value(KEY_CONST_ATTENUATION).toDouble());
1976 spotLight->setLinearAttenuation(
1977 lightValues.value(KEY_LINEAR_ATTENUATION).toDouble());
1978 spotLight->setQuadraticAttenuation(
1979 lightValues.value(KEY_QUAD_ATTENUATION).toDouble());
1980 spotLight->setCutOffAngle(
1981 lightValues.value(KEY_FALLOFF_ANGLE).toDouble());
1982 lightComp = spotLight;
1983 } else if (lightType == KEY_POINT_LIGHT) {
1984 auto pointLight = new QPointLight;
1985 pointLight->setConstantAttenuation(
1986 lightValues.value(KEY_CONST_ATTENUATION).toDouble());
1987 pointLight->setLinearAttenuation(
1988 lightValues.value(KEY_LINEAR_ATTENUATION).toDouble());
1989 pointLight->setQuadraticAttenuation(
1990 lightValues.value(KEY_QUAD_ATTENUATION).toDouble());
1991 lightComp = pointLight;
1992 } else if (lightType == KEY_AMBIENT_LIGHT) {
1993 qCWarning(GLTFImporterLog, "Ambient lights are not supported.");
1994 } else {
1995 qCWarning(GLTFImporterLog, "Unknown light type: %ls",
1996 qUtf16PrintableImpl(lightType));
1997 }
1998
1999 if (lightComp) {
2000 auto colorVal = lightValues.value(KEY_COLOR);
2001 lightComp->setColor(vec4ToQColor(vec4Var: parameterValueFromJSON(GL_FLOAT_VEC4, value: colorVal)));
2002 lightComp->setIntensity(lightValues.value(KEY_INTENSITY).toDouble());
2003 lightComp->setObjectName(light.value(KEY_NAME).toString());
2004
2005 m_lights.insert(akey: lightKey, avalue: lightComp);
2006 }
2007 }
2008 }
2009
2010}
2011
2012void GLTFImporter::processJSONEffect(const QString &id, const QJsonObject &jsonObject)
2013{
2014 QEffect *effect = new QEffect;
2015 renameFromJson(json: jsonObject, object: effect);
2016
2017 const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
2018 for (auto it = params.begin(), end = params.end(); it != end; ++it)
2019 effect->addParameter(parameter: buildParameter(key: it.key(), paramObj: it.value().toObject()));
2020
2021 const QJsonArray techArray = jsonObject.value(KEY_TECHNIQUES).toArray();
2022 for (const QJsonValue &techValue : techArray) {
2023 const QString techName = techValue.toString();
2024 QTechnique *tech = m_techniques.value(akey: techName);
2025 if (tech) {
2026 effect->addTechnique(t: tech);
2027 } else {
2028 qCWarning(GLTFImporterLog, "Technique pass %ls missing for effect %ls",
2029 qUtf16PrintableImpl(techName), qUtf16PrintableImpl(id));
2030 }
2031 }
2032
2033 m_effects[id] = effect;
2034}
2035
2036void GLTFImporter::processJSONRenderPass(const QString &id, const QJsonObject &jsonObject)
2037{
2038 QRenderPass *pass = new QRenderPass;
2039 const QJsonObject passFkObj = jsonObject.value(KEY_FILTERKEYS).toObject();
2040 for (auto it = passFkObj.begin(), end = passFkObj.end(); it != end; ++it)
2041 pass->addFilterKey(filterKey: buildFilterKey(key: it.key(), val: it.value()));
2042
2043 const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
2044 for (auto it = params.begin(), end = params.end(); it != end; ++it)
2045 pass->addParameter(p: buildParameter(key: it.key(), paramObj: it.value().toObject()));
2046
2047 populateRenderStates(pass, states: jsonObject.value(KEY_STATES).toObject());
2048 addProgramToPass(pass, progName: jsonObject.value(KEY_PROGRAM).toString());
2049
2050 renameFromJson(json: jsonObject, object: pass);
2051
2052 m_renderPasses[id] = pass;
2053}
2054
2055/*!
2056 Loads raw data from the GLTF file into the buffer.
2057*/
2058void GLTFImporter::loadBufferData()
2059{
2060 for (auto &bufferData : m_bufferDatas) {
2061 if (!bufferData.data) {
2062 bufferData.data = new QByteArray(resolveLocalData(path: bufferData.path));
2063 }
2064 }
2065}
2066
2067/*!
2068 Removes all data from the buffer.
2069*/
2070void GLTFImporter::unloadBufferData()
2071{
2072 for (const auto &bufferData : qAsConst(t&: m_bufferDatas)) {
2073 QByteArray *data = bufferData.data;
2074 delete data;
2075 }
2076}
2077
2078QByteArray GLTFImporter::resolveLocalData(const QString &path) const
2079{
2080 QDir d(m_basePath);
2081 Q_ASSERT(d.exists());
2082
2083 // check for embedded data
2084 if (isEmbeddedResource(url: path)) {
2085 const QByteArray base64Data = path.toLatin1().remove(index: 0, len: path.indexOf(s: ",") + 1);
2086 return QByteArray::fromBase64(base64: base64Data);
2087 } else {
2088 const QString absPath = d.absoluteFilePath(fileName: path);
2089 QFile f(absPath);
2090 f.open(flags: QIODevice::ReadOnly);
2091 return f.readAll();
2092 }
2093}
2094
2095QVariant GLTFImporter::parameterValueFromJSON(int type, const QJsonValue &value) const
2096{
2097 if (value.isBool()) {
2098 if (type == GL_BOOL)
2099 return QVariant(static_cast<GLboolean>(value.toBool()));
2100 } else if (value.isString()) {
2101 if (type == GL_SAMPLER_2D) {
2102 //Textures are special because we need to do a lookup to return the
2103 //QAbstractTexture
2104 QString textureId = value.toString();
2105 const auto it = m_textures.find(akey: textureId);
2106 if (Q_UNLIKELY(it == m_textures.end())) {
2107 qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
2108 return QVariant();
2109 } else {
2110 return QVariant::fromValue(value: it.value());
2111 }
2112 }
2113 } else if (value.isDouble()) {
2114 switch (type) {
2115 case GL_BYTE:
2116 return QVariant(static_cast<GLbyte>(value.toInt()));
2117 case GL_UNSIGNED_BYTE:
2118 return QVariant(static_cast<GLubyte>(value.toInt()));
2119 case GL_SHORT:
2120 return QVariant(static_cast<GLshort>(value.toInt()));
2121 case GL_UNSIGNED_SHORT:
2122 return QVariant(static_cast<GLushort>(value.toInt()));
2123 case GL_INT:
2124 return QVariant(static_cast<GLint>(value.toInt()));
2125 case GL_UNSIGNED_INT:
2126 return QVariant(static_cast<GLuint>(value.toInt()));
2127 case GL_FLOAT:
2128 return QVariant(static_cast<GLfloat>(value.toDouble()));
2129 default:
2130 break;
2131 }
2132 } else if (value.isArray()) {
2133
2134 const QJsonArray valueArray = value.toArray();
2135
2136 QVector2D vector2D;
2137 QVector3D vector3D;
2138 QVector4D vector4D;
2139 QVector<float> dataMat2(4, 0.0f);
2140 QVector<float> dataMat3(9, 0.0f);
2141
2142 switch (type) {
2143 case GL_BYTE:
2144 return QVariant(static_cast<GLbyte>(valueArray.first().toInt()));
2145 case GL_UNSIGNED_BYTE:
2146 return QVariant(static_cast<GLubyte>(valueArray.first().toInt()));
2147 case GL_SHORT:
2148 return QVariant(static_cast<GLshort>(valueArray.first().toInt()));
2149 case GL_UNSIGNED_SHORT:
2150 return QVariant(static_cast<GLushort>(valueArray.first().toInt()));
2151 case GL_INT:
2152 return QVariant(static_cast<GLint>(valueArray.first().toInt()));
2153 case GL_UNSIGNED_INT:
2154 return QVariant(static_cast<GLuint>(valueArray.first().toInt()));
2155 case GL_FLOAT:
2156 return QVariant(static_cast<GLfloat>(valueArray.first().toDouble()));
2157 case GL_FLOAT_VEC2:
2158 vector2D.setX(static_cast<GLfloat>(valueArray.at(i: 0).toDouble()));
2159 vector2D.setY(static_cast<GLfloat>(valueArray.at(i: 1).toDouble()));
2160 return QVariant(vector2D);
2161 case GL_FLOAT_VEC3:
2162 vector3D.setX(static_cast<GLfloat>(valueArray.at(i: 0).toDouble()));
2163 vector3D.setY(static_cast<GLfloat>(valueArray.at(i: 1).toDouble()));
2164 vector3D.setZ(static_cast<GLfloat>(valueArray.at(i: 2).toDouble()));
2165 return QVariant(vector3D);
2166 case GL_FLOAT_VEC4:
2167 vector4D.setX(static_cast<GLfloat>(valueArray.at(i: 0).toDouble()));
2168 vector4D.setY(static_cast<GLfloat>(valueArray.at(i: 1).toDouble()));
2169 vector4D.setZ(static_cast<GLfloat>(valueArray.at(i: 2).toDouble()));
2170 vector4D.setW(static_cast<GLfloat>(valueArray.at(i: 3).toDouble()));
2171 return QVariant(vector4D);
2172 case GL_INT_VEC2:
2173 vector2D.setX(static_cast<GLint>(valueArray.at(i: 0).toInt()));
2174 vector2D.setY(static_cast<GLint>(valueArray.at(i: 1).toInt()));
2175 return QVariant(vector2D);
2176 case GL_INT_VEC3:
2177 vector3D.setX(static_cast<GLint>(valueArray.at(i: 0).toInt()));
2178 vector3D.setY(static_cast<GLint>(valueArray.at(i: 1).toInt()));
2179 vector3D.setZ(static_cast<GLint>(valueArray.at(i: 2).toInt()));
2180 return QVariant(vector3D);
2181 case GL_INT_VEC4:
2182 vector4D.setX(static_cast<GLint>(valueArray.at(i: 0).toInt()));
2183 vector4D.setY(static_cast<GLint>(valueArray.at(i: 1).toInt()));
2184 vector4D.setZ(static_cast<GLint>(valueArray.at(i: 2).toInt()));
2185 vector4D.setW(static_cast<GLint>(valueArray.at(i: 3).toInt()));
2186 return QVariant(vector4D);
2187 case GL_BOOL:
2188 return QVariant(static_cast<GLboolean>(valueArray.first().toBool()));
2189 case GL_BOOL_VEC2:
2190 vector2D.setX(static_cast<GLboolean>(valueArray.at(i: 0).toBool()));
2191 vector2D.setY(static_cast<GLboolean>(valueArray.at(i: 1).toBool()));
2192 return QVariant(vector2D);
2193 case GL_BOOL_VEC3:
2194 vector3D.setX(static_cast<GLboolean>(valueArray.at(i: 0).toBool()));
2195 vector3D.setY(static_cast<GLboolean>(valueArray.at(i: 1).toBool()));
2196 vector3D.setZ(static_cast<GLboolean>(valueArray.at(i: 2).toBool()));
2197 return QVariant(vector3D);
2198 case GL_BOOL_VEC4:
2199 vector4D.setX(static_cast<GLboolean>(valueArray.at(i: 0).toBool()));
2200 vector4D.setY(static_cast<GLboolean>(valueArray.at(i: 1).toBool()));
2201 vector4D.setZ(static_cast<GLboolean>(valueArray.at(i: 2).toBool()));
2202 vector4D.setW(static_cast<GLboolean>(valueArray.at(i: 3).toBool()));
2203 return QVariant(vector4D);
2204 case GL_FLOAT_MAT2:
2205 //Matrix2x2 is in Row Major ordering (so we need to convert)
2206 dataMat2[0] = static_cast<GLfloat>(valueArray.at(i: 0).toDouble());
2207 dataMat2[1] = static_cast<GLfloat>(valueArray.at(i: 2).toDouble());
2208 dataMat2[2] = static_cast<GLfloat>(valueArray.at(i: 1).toDouble());
2209 dataMat2[3] = static_cast<GLfloat>(valueArray.at(i: 3).toDouble());
2210 return QVariant::fromValue(value: QMatrix2x2(dataMat2.constData()));
2211 case GL_FLOAT_MAT3:
2212 //Matrix3x3 is in Row Major ordering (so we need to convert)
2213 dataMat3[0] = static_cast<GLfloat>(valueArray.at(i: 0).toDouble());
2214 dataMat3[1] = static_cast<GLfloat>(valueArray.at(i: 3).toDouble());
2215 dataMat3[2] = static_cast<GLfloat>(valueArray.at(i: 6).toDouble());
2216 dataMat3[3] = static_cast<GLfloat>(valueArray.at(i: 1).toDouble());
2217 dataMat3[4] = static_cast<GLfloat>(valueArray.at(i: 4).toDouble());
2218 dataMat3[5] = static_cast<GLfloat>(valueArray.at(i: 7).toDouble());
2219 dataMat3[6] = static_cast<GLfloat>(valueArray.at(i: 2).toDouble());
2220 dataMat3[7] = static_cast<GLfloat>(valueArray.at(i: 5).toDouble());
2221 dataMat3[8] = static_cast<GLfloat>(valueArray.at(i: 8).toDouble());
2222 return QVariant::fromValue(value: QMatrix3x3(dataMat3.constData()));
2223 case GL_FLOAT_MAT4:
2224 //Matrix4x4 is Column Major ordering
2225 return QVariant(QMatrix4x4(static_cast<GLfloat>(valueArray.at(i: 0).toDouble()),
2226 static_cast<GLfloat>(valueArray.at(i: 1).toDouble()),
2227 static_cast<GLfloat>(valueArray.at(i: 2).toDouble()),
2228 static_cast<GLfloat>(valueArray.at(i: 3).toDouble()),
2229 static_cast<GLfloat>(valueArray.at(i: 4).toDouble()),
2230 static_cast<GLfloat>(valueArray.at(i: 5).toDouble()),
2231 static_cast<GLfloat>(valueArray.at(i: 6).toDouble()),
2232 static_cast<GLfloat>(valueArray.at(i: 7).toDouble()),
2233 static_cast<GLfloat>(valueArray.at(i: 8).toDouble()),
2234 static_cast<GLfloat>(valueArray.at(i: 9).toDouble()),
2235 static_cast<GLfloat>(valueArray.at(i: 10).toDouble()),
2236 static_cast<GLfloat>(valueArray.at(i: 11).toDouble()),
2237 static_cast<GLfloat>(valueArray.at(i: 12).toDouble()),
2238 static_cast<GLfloat>(valueArray.at(i: 13).toDouble()),
2239 static_cast<GLfloat>(valueArray.at(i: 14).toDouble()),
2240 static_cast<GLfloat>(valueArray.at(i: 15).toDouble())));
2241 case GL_SAMPLER_2D:
2242 return QVariant(valueArray.at(i: 0).toString());
2243 }
2244 }
2245 return QVariant();
2246}
2247
2248QAttribute::VertexBaseType GLTFImporter::accessorTypeFromJSON(int componentType)
2249{
2250 if (componentType == GL_BYTE) {
2251 return QAttribute::Byte;
2252 } else if (componentType == GL_UNSIGNED_BYTE) {
2253 return QAttribute::UnsignedByte;
2254 } else if (componentType == GL_SHORT) {
2255 return QAttribute::Short;
2256 } else if (componentType == GL_UNSIGNED_SHORT) {
2257 return QAttribute::UnsignedShort;
2258 } else if (componentType == GL_UNSIGNED_INT) {
2259 return QAttribute::UnsignedInt;
2260 } else if (componentType == GL_FLOAT) {
2261 return QAttribute::Float;
2262 }
2263
2264 //There shouldn't be an invalid case here
2265 qCWarning(GLTFImporterLog, "unsupported accessor type %d", componentType);
2266 return QAttribute::Float;
2267}
2268
2269uint GLTFImporter::accessorDataSizeFromJson(const QString &type)
2270{
2271 QString typeName = type.toUpper();
2272 if (typeName == QLatin1String("SCALAR"))
2273 return 1;
2274 if (typeName == QLatin1String("VEC2"))
2275 return 2;
2276 if (typeName == QLatin1String("VEC3"))
2277 return 3;
2278 if (typeName == QLatin1String("VEC4"))
2279 return 4;
2280 if (typeName == QLatin1String("MAT2"))
2281 return 4;
2282 if (typeName == QLatin1String("MAT3"))
2283 return 9;
2284 if (typeName == QLatin1String("MAT4"))
2285 return 16;
2286
2287 return 0;
2288}
2289
2290QRenderState *GLTFImporter::buildStateEnable(int state)
2291{
2292 int type = 0;
2293 //By calling buildState with QJsonValue(), a Render State with
2294 //default values is created.
2295
2296 switch (state) {
2297 case GL_BLEND:
2298 //It doesn't make sense to handle this state alone
2299 return nullptr;
2300 case GL_CULL_FACE:
2301 return buildState(QStringLiteral("cullFace"), value: QJsonValue(), type);
2302 case GL_DEPTH_TEST:
2303 return buildState(QStringLiteral("depthFunc"), value: QJsonValue(), type);
2304 case GL_POLYGON_OFFSET_FILL:
2305 return buildState(QStringLiteral("polygonOffset"), value: QJsonValue(), type);
2306 case GL_SAMPLE_ALPHA_TO_COVERAGE:
2307 return new QAlphaCoverage();
2308 case GL_SCISSOR_TEST:
2309 return buildState(QStringLiteral("scissor"), value: QJsonValue(), type);
2310 case GL_DITHER: // Qt3D Custom
2311 return new QDithering();
2312 case 0x809D: // GL_MULTISAMPLE - Qt3D Custom
2313 return new QMultiSampleAntiAliasing();
2314 case 0x884F: // GL_TEXTURE_CUBE_MAP_SEAMLESS - Qt3D Custom
2315 return new QSeamlessCubemap();
2316 default:
2317 break;
2318 }
2319
2320 qCWarning(GLTFImporterLog, "unsupported render state: %d", state);
2321
2322 return nullptr;
2323}
2324
2325QRenderState* GLTFImporter::buildState(const QString& functionName, const QJsonValue &value, int &type)
2326{
2327 type = -1;
2328 QJsonArray values = value.toArray();
2329
2330 if (functionName == QLatin1String("blendColor")) {
2331 type = GL_BLEND;
2332 //TODO: support render state blendColor
2333 qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
2334 return nullptr;
2335 }
2336
2337 if (functionName == QLatin1String("blendEquationSeparate")) {
2338 type = GL_BLEND;
2339 //TODO: support settings blendEquation alpha
2340 QBlendEquation *blendEquation = new QBlendEquation;
2341 blendEquation->setBlendFunction((QBlendEquation::BlendFunction)values.at(i: 0).toInt(GL_FUNC_ADD));
2342 return blendEquation;
2343 }
2344
2345 if (functionName == QLatin1String("blendFuncSeparate")) {
2346 type = GL_BLEND;
2347 QBlendEquationArguments *blendArgs = new QBlendEquationArguments;
2348 blendArgs->setSourceRgb((QBlendEquationArguments::Blending)values.at(i: 0).toInt(GL_ONE));
2349 blendArgs->setSourceAlpha((QBlendEquationArguments::Blending)values.at(i: 1).toInt(GL_ONE));
2350 blendArgs->setDestinationRgb((QBlendEquationArguments::Blending)values.at(i: 2).toInt(GL_ZERO));
2351 blendArgs->setDestinationAlpha((QBlendEquationArguments::Blending)values.at(i: 3).toInt(GL_ZERO));
2352 blendArgs->setBufferIndex(values.at(i: 4).toInt(defaultValue: -1));
2353 return blendArgs;
2354 }
2355
2356 if (functionName == QLatin1String("colorMask")) {
2357 QColorMask *colorMask = new QColorMask;
2358 colorMask->setRedMasked(values.at(i: 0).toBool(defaultValue: true));
2359 colorMask->setGreenMasked(values.at(i: 1).toBool(defaultValue: true));
2360 colorMask->setBlueMasked(values.at(i: 2).toBool(defaultValue: true));
2361 colorMask->setAlphaMasked(values.at(i: 3).toBool(defaultValue: true));
2362 return colorMask;
2363 }
2364
2365 if (functionName == QLatin1String("cullFace")) {
2366 type = GL_CULL_FACE;
2367 QCullFace *cullFace = new QCullFace;
2368 cullFace->setMode((QCullFace::CullingMode)values.at(i: 0).toInt(GL_BACK));
2369 return cullFace;
2370 }
2371
2372 if (functionName == QLatin1String("depthFunc")) {
2373 type = GL_DEPTH_TEST;
2374 QDepthTest *depthTest = new QDepthTest;
2375 depthTest->setDepthFunction((QDepthTest::DepthFunction)values.at(i: 0).toInt(GL_LESS));
2376 return depthTest;
2377 }
2378
2379 if (functionName == QLatin1String("depthMask")) {
2380 if (!values.at(i: 0).toBool(defaultValue: true)) {
2381 QNoDepthMask *depthMask = new QNoDepthMask;
2382 return depthMask;
2383 }
2384 return nullptr;
2385 }
2386
2387 if (functionName == QLatin1String("depthRange")) {
2388 type = GL_DEPTH_RANGE;
2389 QDepthRange *depthRange = new QDepthRange;
2390 depthRange->setNearValue(values.at(i: 0).toDouble(defaultValue: 0.0));
2391 depthRange->setFarValue(values.at(i: 1).toDouble(defaultValue: 1.0));
2392 return depthRange;
2393 }
2394
2395 if (functionName == QLatin1String("frontFace")) {
2396 QFrontFace *frontFace = new QFrontFace;
2397 frontFace->setDirection((QFrontFace::WindingDirection)values.at(i: 0).toInt(GL_CCW));
2398 return frontFace;
2399 }
2400
2401 if (functionName == QLatin1String("lineWidth")) {
2402 //TODO: support render state lineWidth
2403 qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
2404 return nullptr;
2405 }
2406
2407 if (functionName == QLatin1String("polygonOffset")) {
2408 type = GL_POLYGON_OFFSET_FILL;
2409 QPolygonOffset *polygonOffset = new QPolygonOffset;
2410 polygonOffset->setScaleFactor((float)values.at(i: 0).toDouble(defaultValue: 0.0f));
2411 polygonOffset->setDepthSteps((float)values.at(i: 1).toDouble(defaultValue: 0.0f));
2412 return polygonOffset;
2413 }
2414
2415 if (functionName == QLatin1String("scissor")) {
2416 type = GL_SCISSOR_TEST;
2417 QScissorTest *scissorTest = new QScissorTest;
2418 scissorTest->setLeft(values.at(i: 0).toDouble(defaultValue: 0.0f));
2419 scissorTest->setBottom(values.at(i: 1).toDouble(defaultValue: 0.0f));
2420 scissorTest->setWidth(values.at(i: 2).toDouble(defaultValue: 0.0f));
2421 scissorTest->setHeight(values.at(i: 3).toDouble(defaultValue: 0.0f));
2422 return scissorTest;
2423 }
2424
2425 // Qt3D custom functions
2426 if (functionName == QLatin1String("alphaTest")) {
2427 QAlphaTest *args = new QAlphaTest;
2428 args->setAlphaFunction(QAlphaTest::AlphaFunction(values.at(i: 0).toInt()));
2429 args->setReferenceValue(float(values.at(i: 1).toDouble()));
2430 return args;
2431 }
2432
2433 if (functionName == QLatin1String("clipPlane")) {
2434 QClipPlane *args = new QClipPlane;
2435 args->setPlaneIndex(values.at(i: 0).toInt());
2436 args->setNormal(QVector3D(float(values.at(i: 1).toDouble()),
2437 float(values.at(i: 2).toDouble()),
2438 float(values.at(i: 3).toDouble())));
2439 args->setDistance(float(values.at(i: 4).toDouble()));
2440 return args;
2441 }
2442
2443 if (functionName == QLatin1String("pointSize")) {
2444 QPointSize *pointSize = new QPointSize;
2445 pointSize->setSizeMode(QPointSize::SizeMode(values.at(i: 0).toInt(defaultValue: QPointSize::Programmable)));
2446 pointSize->setValue(float(values.at(i: 1).toDouble()));
2447 return pointSize;
2448 }
2449
2450 if (functionName == QLatin1String("stencilMask")) {
2451 QStencilMask *stencilMask = new QStencilMask;
2452 stencilMask->setFrontOutputMask(uint(values.at(i: 0).toInt()));
2453 stencilMask->setBackOutputMask(uint(values.at(i: 1).toInt()));
2454 return stencilMask;
2455 }
2456
2457 if (functionName == QLatin1String("stencilOperation")) {
2458 QStencilOperation *stencilOperation = new QStencilOperation;
2459 stencilOperation->front()->setStencilTestFailureOperation(
2460 QStencilOperationArguments::Operation(values.at(i: 0).toInt(
2461 defaultValue: QStencilOperationArguments::Keep)));
2462 stencilOperation->front()->setDepthTestFailureOperation(
2463 QStencilOperationArguments::Operation(values.at(i: 1).toInt(
2464 defaultValue: QStencilOperationArguments::Keep)));
2465 stencilOperation->front()->setAllTestsPassOperation(
2466 QStencilOperationArguments::Operation(values.at(i: 2).toInt(
2467 defaultValue: QStencilOperationArguments::Keep)));
2468 stencilOperation->back()->setStencilTestFailureOperation(
2469 QStencilOperationArguments::Operation(values.at(i: 3).toInt(
2470 defaultValue: QStencilOperationArguments::Keep)));
2471 stencilOperation->back()->setDepthTestFailureOperation(
2472 QStencilOperationArguments::Operation(values.at(i: 4).toInt(
2473 defaultValue: QStencilOperationArguments::Keep)));
2474 stencilOperation->back()->setAllTestsPassOperation(
2475 QStencilOperationArguments::Operation(values.at(i: 5).toInt(
2476 defaultValue: QStencilOperationArguments::Keep)));
2477 return stencilOperation;
2478 }
2479
2480 if (functionName == QLatin1String("stencilTest")) {
2481 QStencilTest *stencilTest = new QStencilTest;
2482 stencilTest->front()->setComparisonMask(uint(values.at(i: 0).toInt()));
2483 stencilTest->front()->setReferenceValue(values.at(i: 1).toInt());
2484 stencilTest->front()->setStencilFunction(
2485 QStencilTestArguments::StencilFunction(values.at(i: 2).toInt(
2486 defaultValue: QStencilTestArguments::Never)));
2487 stencilTest->back()->setComparisonMask(uint(values.at(i: 3).toInt()));
2488 stencilTest->back()->setReferenceValue(values.at(i: 4).toInt());
2489 stencilTest->back()->setStencilFunction(
2490 QStencilTestArguments::StencilFunction(values.at(i: 5).toInt(
2491 defaultValue: QStencilTestArguments::Never)));
2492 return stencilTest;
2493 }
2494
2495 qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
2496 return nullptr;
2497}
2498
2499QParameter *GLTFImporter::buildParameter(const QString &key, const QJsonObject &paramObj)
2500{
2501 QParameter *p = new QParameter;
2502 p->setName(key);
2503 QJsonValue value = paramObj.value(KEY_VALUE);
2504
2505 if (!value.isUndefined()) {
2506 int dataType = paramObj.value(KEY_TYPE).toInt();
2507 p->setValue(parameterValueFromJSON(type: dataType, value));
2508 }
2509
2510 return p;
2511}
2512
2513void GLTFImporter::populateRenderStates(QRenderPass *pass, const QJsonObject &states)
2514{
2515 // Process states to enable
2516 const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray();
2517 QVector<int> enableStates;
2518 for (const QJsonValue &enableValue : enableStatesArray)
2519 enableStates.append(t: enableValue.toInt());
2520
2521 // Process the list of state functions
2522 const QJsonObject functions = states.value(KEY_FUNCTIONS).toObject();
2523 for (auto it = functions.begin(), end = functions.end(); it != end; ++it) {
2524 int enableStateType = 0;
2525 QRenderState *renderState = buildState(functionName: it.key(), value: it.value(), type&: enableStateType);
2526 if (renderState != nullptr) {
2527 //Remove the need to set a default state values for enableStateType
2528 enableStates.removeOne(t: enableStateType);
2529 pass->addRenderState(state: renderState);
2530 }
2531 }
2532
2533 // Create render states with default values for any remaining enable states
2534 for (int enableState : qAsConst(t&: enableStates)) {
2535 QRenderState *renderState = buildStateEnable(state: enableState);
2536 if (renderState != nullptr)
2537 pass->addRenderState(state: renderState);
2538 }
2539}
2540
2541void GLTFImporter::addProgramToPass(QRenderPass *pass, const QString &progName)
2542{
2543 const auto progIt = qAsConst(t&: m_programs).find(akey: progName);
2544 if (Q_UNLIKELY(progIt == m_programs.cend()))
2545 qCWarning(GLTFImporterLog, "missing program %ls", qUtf16PrintableImpl(progName));
2546 else
2547 pass->setShaderProgram(progIt.value());
2548}
2549
2550void GLTFImporter::setTextureSamplerInfo(const QString &id, const QJsonObject &jsonObj, QTexture2D *tex)
2551{
2552 QJsonObject sampler;
2553 const QJsonValue jsonValue = jsonObj.value(KEY_SAMPLER);
2554 if (jsonValue.isUndefined())
2555 return;
2556
2557 if (m_majorVersion > 1) {
2558 const int samplerId = jsonValue.toInt();
2559 const QJsonArray sArray = m_json.object().value(KEY_SAMPLERS).toArray();
2560 if (Q_UNLIKELY(samplerId >= sArray.count())) {
2561 qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %d",
2562 qUtf16PrintableImpl(id), samplerId);
2563 return;
2564 }
2565 sampler = sArray[samplerId].toObject();
2566 } else {
2567 const QString samplerId = jsonValue.toString();
2568 const QJsonValue samplersDictValue = m_json.object().value(KEY_SAMPLERS).toObject().value(key: samplerId);
2569 if (Q_UNLIKELY(samplersDictValue.isUndefined())) {
2570 qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %ls",
2571 qUtf16PrintableImpl(id), qUtf16PrintableImpl(samplerId));
2572 return;
2573 }
2574 sampler = samplersDictValue.toObject();
2575 }
2576
2577 tex->setWrapMode(QTextureWrapMode(static_cast<QTextureWrapMode::WrapMode>(sampler.value(KEY_WRAP_S).toInt())));
2578 tex->setMinificationFilter(static_cast<QAbstractTexture::Filter>(sampler.value(KEY_MIN_FILTER).toInt()));
2579 if (tex->minificationFilter() == QAbstractTexture::NearestMipMapLinear ||
2580 tex->minificationFilter() == QAbstractTexture::LinearMipMapNearest ||
2581 tex->minificationFilter() == QAbstractTexture::NearestMipMapNearest ||
2582 tex->minificationFilter() == QAbstractTexture::LinearMipMapLinear) {
2583
2584 tex->setGenerateMipMaps(true);
2585 }
2586 tex->setMagnificationFilter(static_cast<QAbstractTexture::Filter>(sampler.value(KEY_MAG_FILTER).toInt()));
2587}
2588
2589GLTFRawTextureImage::GLTFRawTextureImage(QNode *parent)
2590 : QAbstractTextureImage(parent)
2591{
2592}
2593
2594QTextureImageDataGeneratorPtr GLTFRawTextureImage::dataGenerator() const
2595{
2596 return QTextureImageDataGeneratorPtr(new GLTFRawTextureImageFunctor(m_image));
2597}
2598
2599void GLTFRawTextureImage::setImage(const QImage &image)
2600{
2601 if (image != m_image) {
2602 m_image = image;
2603 notifyDataGeneratorChanged();
2604 }
2605}
2606
2607GLTFRawTextureImage::GLTFRawTextureImageFunctor::GLTFRawTextureImageFunctor(const QImage &image)
2608 : QTextureImageDataGenerator()
2609 , m_image(image)
2610{
2611}
2612
2613QTextureImageDataPtr GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator()()
2614{
2615 QTextureImageDataPtr dataPtr = QTextureImageDataPtr::create();
2616 // Note: we assume 4 components per pixel and not compressed for now
2617 dataPtr->setImage(m_image);
2618 return dataPtr;
2619}
2620
2621bool GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator ==(const QTextureImageDataGenerator &other) const
2622{
2623 const GLTFRawTextureImageFunctor *otherFunctor = functor_cast<GLTFRawTextureImageFunctor>(other: &other);
2624 return (otherFunctor != nullptr && otherFunctor->m_image == m_image);
2625}
2626
2627} // namespace Qt3DRender
2628
2629QT_END_NAMESPACE
2630
2631#include "gltfimporter.moc"
2632

source code of qt3d/src/plugins/sceneparsers/gltf/gltfimporter.cpp