1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt3D module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "gltfexporter.h"
41 
42 #include <QtCore/qiodevice.h>
43 #include <QtCore/qfile.h>
44 #include <QtCore/qfileinfo.h>
45 #include <QtCore/qdir.h>
46 #include <QtCore/qhash.h>
47 #include <QtCore/qdebug.h>
48 #include <QtCore/qcoreapplication.h>
49 #include <QtCore/qjsondocument.h>
50 #include <QtCore/qjsonobject.h>
51 #include <QtCore/qjsonarray.h>
52 #include <QtCore/qmath.h>
53 #include <QtCore/qtemporarydir.h>
54 #include <QtCore/qregularexpression.h>
55 #include <QtCore/qmetaobject.h>
56 #include <QtGui/qvector2d.h>
57 #include <QtGui/qvector4d.h>
58 #include <QtGui/qmatrix4x4.h>
59 
60 #include <Qt3DCore/qentity.h>
61 #include <Qt3DCore/qtransform.h>
62 #include <Qt3DRender/qcameralens.h>
63 #include <Qt3DRender/qcamera.h>
64 #include <Qt3DRender/qblendequation.h>
65 #include <Qt3DRender/qblendequationarguments.h>
66 #include <Qt3DRender/qeffect.h>
67 #include <Qt3DRender/qattribute.h>
68 #include <Qt3DRender/qbuffer.h>
69 #include <Qt3DRender/qbufferdatagenerator.h>
70 #include <Qt3DRender/qmaterial.h>
71 #include <Qt3DRender/qgraphicsapifilter.h>
72 #include <Qt3DRender/qparameter.h>
73 #include <Qt3DRender/qtexture.h>
74 #include <Qt3DRender/qabstractlight.h>
75 #include <Qt3DRender/qpointlight.h>
76 #include <Qt3DRender/qspotlight.h>
77 #include <Qt3DRender/qdirectionallight.h>
78 #include <Qt3DRender/qgeometry.h>
79 #include <Qt3DRender/qgeometryrenderer.h>
80 #include <Qt3DRender/qgeometryfactory.h>
81 #include <Qt3DRender/qtechnique.h>
82 #include <Qt3DRender/qalphacoverage.h>
83 #include <Qt3DRender/qalphatest.h>
84 #include <Qt3DRender/qclipplane.h>
85 #include <Qt3DRender/qcolormask.h>
86 #include <Qt3DRender/qcullface.h>
87 #include <Qt3DRender/qdepthrange.h>
88 #include <Qt3DRender/qdepthtest.h>
89 #include <Qt3DRender/qdithering.h>
90 #include <Qt3DRender/qfrontface.h>
91 #include <Qt3DRender/qmultisampleantialiasing.h>
92 #include <Qt3DRender/qnodepthmask.h>
93 #include <Qt3DRender/qpointsize.h>
94 #include <Qt3DRender/qpolygonoffset.h>
95 #include <Qt3DRender/qscissortest.h>
96 #include <Qt3DRender/qseamlesscubemap.h>
97 #include <Qt3DRender/qstencilmask.h>
98 #include <Qt3DRender/qstenciloperation.h>
99 #include <Qt3DRender/qstenciloperationarguments.h>
100 #include <Qt3DRender/qstenciltest.h>
101 #include <Qt3DRender/qstenciltestarguments.h>
102 #include <Qt3DExtras/qconemesh.h>
103 #include <Qt3DExtras/qcuboidmesh.h>
104 #include <Qt3DExtras/qcylindermesh.h>
105 #include <Qt3DExtras/qplanemesh.h>
106 #include <Qt3DExtras/qspheremesh.h>
107 #include <Qt3DExtras/qtorusmesh.h>
108 #include <Qt3DExtras/qphongmaterial.h>
109 #include <Qt3DExtras/qphongalphamaterial.h>
110 #include <Qt3DExtras/qdiffusemapmaterial.h>
111 #include <Qt3DExtras/qdiffusespecularmapmaterial.h>
112 #include <Qt3DExtras/qnormaldiffusemapmaterial.h>
113 #include <Qt3DExtras/qnormaldiffusemapalphamaterial.h>
114 #include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h>
115 #include <Qt3DExtras/qgoochmaterial.h>
116 #include <Qt3DExtras/qpervertexcolormaterial.h>
117 
118 #include <private/qurlhelper_p.h>
119 
120 #ifndef qUtf16PrintableImpl
121 #  define qUtf16PrintableImpl(string) \
122     static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
123 #endif
124 
125 namespace {
126 
col2jsvec(const QColor & color,bool alpha=false)127 inline QJsonArray col2jsvec(const QColor &color, bool alpha = false)
128 {
129     QJsonArray arr;
130     arr << color.redF() << color.greenF() << color.blueF();
131     if (alpha)
132         arr << color.alphaF();
133     return arr;
134 }
135 
136 template <typename T>
vec2jsvec(const QVector<T> & v)137 inline QJsonArray vec2jsvec(const QVector<T> &v)
138 {
139     QJsonArray arr;
140     for (int i = 0; i < v.count(); ++i)
141         arr << v.at(i);
142     return arr;
143 }
144 
size2jsvec(const QSize & size)145 inline QJsonArray size2jsvec(const QSize &size) {
146     QJsonArray arr;
147     arr << size.width() << size.height();
148     return arr;
149 }
150 
vec2jsvec(const QVector2D & v)151 inline QJsonArray vec2jsvec(const QVector2D &v)
152 {
153     QJsonArray arr;
154     arr << v.x() << v.y();
155     return arr;
156 }
157 
vec2jsvec(const QVector3D & v)158 inline QJsonArray vec2jsvec(const QVector3D &v)
159 {
160     QJsonArray arr;
161     arr << v.x() << v.y() << v.z();
162     return arr;
163 }
164 
vec2jsvec(const QVector4D & v)165 inline QJsonArray vec2jsvec(const QVector4D &v)
166 {
167     QJsonArray arr;
168     arr << v.x() << v.y() << v.z() << v.w();
169     return arr;
170 }
171 
172 #if 0   // unused for now
173 inline QJsonArray matrix2jsvec(const QMatrix2x2 &matrix)
174 {
175     QJsonArray jm;
176     const float *mtxp = matrix.constData();
177     for (int j = 0; j < 4; ++j)
178         jm.append(*mtxp++);
179     return jm;
180 }
181 
182 inline QJsonArray matrix2jsvec(const QMatrix3x3 &matrix)
183 {
184     QJsonArray jm;
185     const float *mtxp = matrix.constData();
186     for (int j = 0; j < 9; ++j)
187         jm.append(*mtxp++);
188     return jm;
189 }
190 #endif
191 
matrix2jsvec(const QMatrix4x4 & matrix)192 inline QJsonArray matrix2jsvec(const QMatrix4x4 &matrix)
193 {
194     QJsonArray jm;
195     const float *mtxp = matrix.constData();
196     for (int j = 0; j < 16; ++j)
197         jm.append(*mtxp++);
198     return jm;
199 }
200 
promoteColorsToRGBA(QJsonObject * obj)201 inline void promoteColorsToRGBA(QJsonObject *obj)
202 {
203     auto it = obj->begin();
204     auto itEnd = obj->end();
205     while (it != itEnd) {
206         QJsonArray arr = it.value().toArray();
207         if (arr.count() == 3) {
208             const QString key = it.key();
209             if (key == QStringLiteral("ambient")
210                     || key == QStringLiteral("diffuse")
211                     || key == QStringLiteral("specular")
212                     || key == QStringLiteral("warm")
213                     || key == QStringLiteral("cool")) {
214                 arr.append(1);
215                 *it = arr;
216             }
217         }
218         ++it;
219     }
220 }
221 
222 } // namespace
223 
224 QT_BEGIN_NAMESPACE
225 
226 using namespace Qt3DCore;
227 using namespace Qt3DExtras;
228 
229 namespace Qt3DRender {
230 
231 Q_LOGGING_CATEGORY(GLTFExporterLog, "Qt3D.GLTFExport", QtWarningMsg)
232 
233 const QString MATERIAL_DIFFUSE_COLOR = QStringLiteral("kd");
234 const QString MATERIAL_SPECULAR_COLOR = QStringLiteral("ks");
235 const QString MATERIAL_AMBIENT_COLOR = QStringLiteral("ka");
236 
237 const QString MATERIAL_DIFFUSE_TEXTURE = QStringLiteral("diffuseTexture");
238 const QString MATERIAL_SPECULAR_TEXTURE = QStringLiteral("specularTexture");
239 const QString MATERIAL_NORMALS_TEXTURE = QStringLiteral("normalTexture");
240 
241 const QString MATERIAL_SHININESS = QStringLiteral("shininess");
242 const QString MATERIAL_ALPHA = QStringLiteral("alpha");
243 
244 // Custom extension for Qt3D
245 const QString MATERIAL_TEXTURE_SCALE = QStringLiteral("texCoordScale");
246 
247 // Custom gooch material values
248 const QString MATERIAL_BETA = QStringLiteral("beta");
249 const QString MATERIAL_COOL_COLOR = QStringLiteral("kblue");
250 const QString MATERIAL_WARM_COLOR = QStringLiteral("kyellow");
251 
252 const QString VERTICES_ATTRIBUTE_NAME = QAttribute::defaultPositionAttributeName();
253 const QString NORMAL_ATTRIBUTE_NAME =  QAttribute::defaultNormalAttributeName();
254 const QString TANGENT_ATTRIBUTE_NAME = QAttribute::defaultTangentAttributeName();
255 const QString TEXTCOORD_ATTRIBUTE_NAME = QAttribute::defaultTextureCoordinateAttributeName();
256 const QString COLOR_ATTRIBUTE_NAME = QAttribute::defaultColorAttributeName();
257 
GLTFExporter()258 GLTFExporter::GLTFExporter() : QSceneExporter()
259   , m_sceneRoot(nullptr)
260   , m_rootNode(nullptr)
261   , m_rootNodeEmpty(false)
262 
263 {
264 }
265 
~GLTFExporter()266 GLTFExporter::~GLTFExporter()
267 {
268 }
269 
270 /*!
271     \class Qt3DRender::GLTFExporter
272     \inmodule Qt3DRender
273     \internal
274     \brief Manages the export of a 3D scene to the GLTF format.
275 
276     Handles the export of a 3D scene to the GLTF format.
277 */
278 // sceneRoot  : The root entity that contains the exported scene. If the sceneRoot doesn't have
279 //              any exportable components, it is not exported itself. This is because importing a
280 //              scene creates an empty top level entity to hold the scene.
281 // outDir     : The directory where the scene export directory is created in.
282 // exportName : Name of the directory created in outDir to hold the exported scene. Also used as
283 //              the file name base for generated files.
284 // options    : Export options.
285 //
286 // Supported options are:
287 // "binaryJson"  (bool): Generates a binary JSON file, which is more efficient to parse.
288 // "compactJson" (bool): Removes unnecessary whitespace from the generated JSON file.
289 //                       Ignored if "binaryJson" option is true.
290 
291 /*!
292     Exports the scene to the GLTF format
293 
294     \a sceneRoot is the root entity that will be exported.
295     If the sceneRoot does not have any exportable components, it is not exported itself.
296 
297     \a outDir is the directory in which the scene export is created.
298 
299     \a exportName is the name of the directory created in \c outDir that will hold
300        the exported scene.
301 
302     \a options contain the export options.
303 
304     Returns true if the export was carried out successfully.
305 */
306 
exportScene(QEntity * sceneRoot,const QString & outDir,const QString & exportName,const QVariantHash & options)307 bool GLTFExporter::exportScene(QEntity *sceneRoot, const QString &outDir,
308                                const QString &exportName, const QVariantHash &options)
309 {
310     m_bufferViewCount = 0;
311     m_accessorCount = 0;
312     m_meshCount = 0;
313     m_materialCount = 0;
314     m_techniqueCount = 0;
315     m_textureCount = 0;
316     m_imageCount = 0;
317     m_shaderCount = 0;
318     m_programCount = 0;
319     m_nodeCount = 0;
320     m_cameraCount = 0;
321     m_lightCount = 0;
322     m_renderPassCount = 0;
323     m_effectCount = 0;
324 
325     m_gltfOpts.binaryJson = options.value(QStringLiteral("binaryJson"),
326                                           QVariant(false)).toBool();
327     m_gltfOpts.compactJson = options.value(QStringLiteral("compactJson"),
328                                            QVariant(false)).toBool();
329 
330     QFileInfo outDirFileInfo(outDir);
331     QString absoluteOutDir = outDirFileInfo.absoluteFilePath();
332     if (!absoluteOutDir.endsWith(QLatin1Char('/')))
333         absoluteOutDir.append(QLatin1Char('/'));
334     m_exportName = exportName;
335     m_sceneRoot = sceneRoot;
336     QString finalExportDir = absoluteOutDir + m_exportName;
337     if (!finalExportDir.endsWith(QLatin1Char('/')))
338         finalExportDir.append(QLatin1Char('/'));
339 
340     QDir outDirDir(absoluteOutDir);
341 
342     // Make sure outDir exists
343     if (outDirFileInfo.exists()) {
344         if (!outDirFileInfo.isDir()) {
345             qCWarning(GLTFExporterLog, "outDir is not a directory: '%ls'",
346                       qUtf16PrintableImpl(absoluteOutDir));
347             return false;
348         }
349     } else {
350         if (!outDirDir.mkpath(outDirFileInfo.absoluteFilePath())) {
351             qCWarning(GLTFExporterLog, "outDir could not be created: '%ls'",
352                       qUtf16PrintableImpl(absoluteOutDir));
353             return false;
354         }
355     }
356 
357     // Create temporary directory for exporting
358     QTemporaryDir exportDir;
359 
360     if (!exportDir.isValid()) {
361         qCWarning(GLTFExporterLog, "Temporary export directory could not be created");
362         return false;
363     }
364     m_exportDir = exportDir.path();
365     m_exportDir.append(QStringLiteral("/"));
366 
367     qCDebug(GLTFExporterLog, "Output directory: %ls", qUtf16PrintableImpl(absoluteOutDir));
368     qCDebug(GLTFExporterLog, "Export name: %ls", qUtf16PrintableImpl(m_exportName));
369     qCDebug(GLTFExporterLog, "Temp export dir: %ls", qUtf16PrintableImpl(m_exportDir));
370     qCDebug(GLTFExporterLog, "Final export dir: %ls", qUtf16PrintableImpl(finalExportDir));
371 
372     parseScene();
373 
374     // Export scene to temporary directory
375     if (!saveScene()) {
376         qCWarning(GLTFExporterLog, "Exporting GLTF scene failed");
377         return false;
378     }
379 
380     // Create final export directory
381     if (!outDirDir.mkpath(m_exportName)) {
382         qCWarning(GLTFExporterLog, "Final export directory could not be created: '%ls'",
383                   qUtf16PrintableImpl(finalExportDir));
384         return false;
385     }
386 
387     // As a safety feature, we don't indiscriminately delete existing directory or it's contents,
388     // but instead look for an old export and delete only related files.
389     clearOldExport(finalExportDir);
390 
391     // Files copied from resources will have read-only permissions, which isn't ideal in cases
392     // where export is done on top of an existing export.
393     // Since different file systems handle permissions differently, we grab the target permissions
394     // from the qgltf file, which we created ourselves.
395     QFile gltfFile(m_exportDir + m_exportName + QStringLiteral(".qgltf"));
396     QFile::Permissions targetPermissions = gltfFile.permissions();
397 
398     // Copy exported scene to actual export directory
399     for (const auto &sourceFileStr : qAsConst(m_exportedFiles)) {
400         QFileInfo fiSource(m_exportDir + sourceFileStr);
401         QFileInfo fiDestination(finalExportDir + sourceFileStr);
402         if (fiDestination.exists()) {
403             QFile(fiDestination.absoluteFilePath()).remove();
404             qCDebug(GLTFExporterLog, "Removed old file: '%ls'",
405                     qUtf16PrintableImpl(fiDestination.absoluteFilePath()));
406         }
407         QString srcPath = fiSource.absoluteFilePath();
408         QString destPath = fiDestination.absoluteFilePath();
409         if (!QFile(srcPath).copy(destPath)) {
410             qCWarning(GLTFExporterLog, "  Failed to copy file: '%ls' -> '%ls'",
411                       qUtf16PrintableImpl(srcPath), qUtf16PrintableImpl(destPath));
412             // Don't fail entire export because file copy failed - if there is somehow a read-only
413             // file with same name already in the export dir after cleanup we did, let's just assume
414             // it's the same file we want rather than risk deleting unrelated protected file.
415         } else {
416             qCDebug(GLTFExporterLog, "  Copied file: '%ls' -> '%ls'",
417                     qUtf16PrintableImpl(srcPath), qUtf16PrintableImpl(destPath));
418             QFile(destPath).setPermissions(targetPermissions);
419         }
420     }
421 
422     // Clean up after export
423 
424     m_buffer.clear();
425     m_meshMap.clear();
426     m_materialMap.clear();
427     m_cameraMap.clear();
428     m_lightMap.clear();
429     m_transformMap.clear();
430     m_imageMap.clear();
431     m_textureIdMap.clear();
432     m_meshInfo.clear();
433     m_materialInfo.clear();
434     m_cameraInfo.clear();
435     m_lightInfo.clear();
436     m_exportedFiles.clear();
437     m_renderPassIdMap.clear();
438     m_shaderInfo.clear();
439     m_programInfo.clear();
440     m_techniqueIdMap.clear();
441     m_effectIdMap.clear();
442     qDeleteAll(m_defaultObjectCache);
443     m_defaultObjectCache.clear();
444     m_propertyCache.clear();
445 
446     delNode(m_rootNode);
447 
448     return true;
449 }
450 
cacheDefaultProperties(GLTFExporter::PropertyCacheType type)451 void GLTFExporter::cacheDefaultProperties(GLTFExporter::PropertyCacheType type)
452 {
453     if (m_defaultObjectCache.contains(type))
454         return;
455 
456     QObject *defaultObject = nullptr;
457 
458     switch (type) {
459     case TypeConeMesh:
460         defaultObject = new QConeMesh;
461         break;
462     case TypeCuboidMesh:
463         defaultObject = new QCuboidMesh;
464         break;
465     case TypeCylinderMesh:
466         defaultObject = new QCylinderMesh;
467         break;
468     case TypePlaneMesh:
469         defaultObject = new QPlaneMesh;
470         break;
471     case TypeSphereMesh:
472         defaultObject = new QSphereMesh;
473         break;
474     case TypeTorusMesh:
475         defaultObject = new QTorusMesh;
476         break;
477     default:
478         return; // Unsupported type
479     }
480 
481     // Store the default object for property comparisons
482     m_defaultObjectCache.insert(type, defaultObject);
483 
484     // Cache metaproperties of supported types (but not their parent class types)
485     const QMetaObject *meta = defaultObject->metaObject();
486     QVector<QMetaProperty> properties;
487     properties.reserve(meta->propertyCount() - meta->propertyOffset());
488     for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
489         if (meta->property(i).isWritable())
490             properties.append(meta->property(i));
491     }
492 
493     m_propertyCache.insert(type, properties);
494 }
495 
496 // Copies textures from original locations to the temporary export directory.
497 // If texture names conflict, they are renamed.
copyTextures()498 void GLTFExporter::copyTextures()
499 {
500     qCDebug(GLTFExporterLog, "Copying textures...");
501     QHash<QString, QString> copiedMap;
502     for (auto texIt = m_textureIdMap.constBegin(); texIt != m_textureIdMap.constEnd(); ++texIt) {
503         QFileInfo fi(texIt.key());
504         QString absoluteFilePath;
505         if (texIt.key().startsWith(QStringLiteral(":")))
506             absoluteFilePath = texIt.key();
507         else
508             absoluteFilePath = fi.absoluteFilePath();
509         if (copiedMap.contains(absoluteFilePath)) {
510             // Texture has already been copied
511             qCDebug(GLTFExporterLog, "  Skipped copying duplicate texture: '%ls'",
512                     qUtf16PrintableImpl(absoluteFilePath));
513             if (!m_imageMap.contains(texIt.key()))
514                 m_imageMap.insert(texIt.key(), copiedMap.value(absoluteFilePath));
515         } else {
516             QString fileName = fi.fileName();
517             QString outFile = m_exportDir;
518             outFile.append(fileName);
519             QFileInfo fiTry(outFile);
520             if (fiTry.exists()) {
521                 static const QString outFileTemplate = QStringLiteral("%2_%3.%4");
522                 int counter = 0;
523                 QString tryFile = outFile;
524                 QString suffix = fiTry.suffix();
525                 QString base = fiTry.baseName();
526                 while (fiTry.exists()) {
527                     fileName = outFileTemplate.arg(base).arg(counter++).arg(suffix);
528                     tryFile = m_exportDir;
529                     tryFile.append(fileName);
530                     fiTry.setFile(tryFile);
531                 }
532                 outFile = tryFile;
533             }
534             if (!QFile(absoluteFilePath).copy(outFile)) {
535                 qCWarning(GLTFExporterLog, "  Failed to copy texture: '%ls' -> '%ls'",
536                           qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile));
537             } else {
538                 qCDebug(GLTFExporterLog, "  Copied texture: '%ls' -> '%ls'",
539                         qUtf16PrintableImpl(absoluteFilePath), qUtf16PrintableImpl(outFile));
540             }
541             // Generate actual target file (as current exportDir is temp dir)
542             copiedMap.insert(absoluteFilePath, fileName);
543             m_exportedFiles.insert(fileName);
544             m_imageMap.insert(texIt.key(), fileName);
545         }
546     }
547 }
548 
549 // Creates shaders to the temporary export directory.
createShaders()550 void GLTFExporter::createShaders()
551 {
552     qCDebug(GLTFExporterLog, "Creating shaders...");
553     for (const auto &si : qAsConst(m_shaderInfo)) {
554         const QString fileName = m_exportDir + si.uri;
555         QFile f(fileName);
556         if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
557             m_exportedFiles.insert(QFileInfo(f.fileName()).fileName());
558             f.write(si.code);
559             f.close();
560         } else {
561             qCWarning(GLTFExporterLog, "  Writing shaderfile '%ls' failed!",
562                       qUtf16PrintableImpl(fileName));
563         }
564     }
565 }
566 
567 
parseEntities(const QEntity * entity,Node * parentNode)568 void GLTFExporter::parseEntities(const QEntity *entity, Node *parentNode)
569 {
570     if (entity) {
571         Node *node =  new Node;
572         node->name = entity->objectName();
573         node->uniqueName = newNodeName();
574 
575         int irrelevantComponents = 0;
576         const auto components = entity->components();
577         for (auto component : components) {
578             if (auto mesh = qobject_cast<QGeometryRenderer *>(component))
579                 m_meshMap.insert(node, mesh);
580             else if (auto material = qobject_cast<QMaterial *>(component))
581                 m_materialMap.insert(node, material);
582             else if (auto transform = qobject_cast<Qt3DCore::QTransform *>(component))
583                 m_transformMap.insert(node, transform);
584             else if (auto camera = qobject_cast<QCameraLens *>(component))
585                 m_cameraMap.insert(node, camera);
586             else if (auto light = qobject_cast<QAbstractLight *>(component))
587                 m_lightMap.insert(node, light);
588             else
589                 irrelevantComponents++;
590         }
591         if (!parentNode) {
592             m_rootNode = node;
593             if (irrelevantComponents == entity->components().size())
594                 m_rootNodeEmpty = true;
595         } else {
596             parentNode->children.append(node);
597         }
598         qCDebug(GLTFExporterLog, "Parsed entity '%ls' -> '%ls'",
599                 qUtf16PrintableImpl(entity->objectName()), qUtf16PrintableImpl(node->uniqueName));
600 
601         for (auto child : entity->children())
602             parseEntities(qobject_cast<QEntity *>(child), node);
603     }
604 }
605 
parseScene()606 void GLTFExporter::parseScene()
607 {
608     parseEntities(m_sceneRoot, nullptr);
609     parseMaterials();
610     parseMeshes();
611     parseCameras();
612     parseLights();
613 }
614 
parseMaterials()615 void GLTFExporter::parseMaterials()
616 {
617     qCDebug(GLTFExporterLog, "Parsing materials...");
618 
619     int materialCount = 0;
620     for (auto it = m_materialMap.constBegin(); it != m_materialMap.constEnd(); ++it) {
621         QMaterial *material = it.value();
622 
623         MaterialInfo matInfo;
624         matInfo.name = newMaterialName();
625         matInfo.originalName = material->objectName();
626 
627         // Is material common or custom?
628         if (qobject_cast<QPhongMaterial *>(material)) {
629             matInfo.type = MaterialInfo::TypePhong;
630         } else if (auto phongAlpha = qobject_cast<QPhongAlphaMaterial *>(material)) {
631             matInfo.type = MaterialInfo::TypePhongAlpha;
632             matInfo.blendArguments.resize(4);
633             matInfo.blendEquations.resize(2);
634             matInfo.blendArguments[0] = int(phongAlpha->sourceRgbArg());
635             matInfo.blendArguments[1] = int(phongAlpha->sourceAlphaArg());
636             matInfo.blendArguments[2] = int(phongAlpha->destinationRgbArg());
637             matInfo.blendArguments[3] = int(phongAlpha->destinationAlphaArg());
638             matInfo.blendEquations[0] = int(phongAlpha->blendFunctionArg());
639             matInfo.blendEquations[1] = int(phongAlpha->blendFunctionArg());
640         } else if (qobject_cast<QDiffuseMapMaterial *>(material)) {
641             matInfo.type = MaterialInfo::TypeDiffuseMap;
642         } else if (qobject_cast<QDiffuseSpecularMapMaterial *>(material)) {
643             matInfo.type = MaterialInfo::TypeDiffuseSpecularMap;
644         } else if (qobject_cast<QNormalDiffuseMapAlphaMaterial *>(material)) {
645             matInfo.values.insert(QStringLiteral("transparent"), QVariant(true));
646             matInfo.type = MaterialInfo::TypeNormalDiffuseMapAlpha;
647         } else if (qobject_cast<QNormalDiffuseMapMaterial *>(material)) {
648             matInfo.type = MaterialInfo::TypeNormalDiffuseMap;
649         } else if (qobject_cast<QNormalDiffuseSpecularMapMaterial *>(material)) {
650             matInfo.type = MaterialInfo::TypeNormalDiffuseSpecularMap;
651         } else if (qobject_cast<QGoochMaterial *>(material)) {
652             matInfo.type = MaterialInfo::TypeGooch;
653         } else if (qobject_cast<QPerVertexColorMaterial *>(material)) {
654             matInfo.type = MaterialInfo::TypePerVertex;
655         } else {
656             matInfo.type = MaterialInfo::TypeCustom;
657         }
658 
659         if (matInfo.type == MaterialInfo::TypeCustom) {
660             if (material->effect()) {
661                 if (!m_effectIdMap.contains(material->effect()))
662                     m_effectIdMap.insert(material->effect(), newEffectName());
663                 parseTechniques(material);
664             }
665         } else {
666             // Default materials do not have separate effect, all effect parameters are stored as
667             // material values.
668             if (material->effect()) {
669                 QVector<QParameter *> parameters = material->effect()->parameters();
670                 for (auto param : parameters) {
671                     if (param->value().type() == QVariant::Color) {
672                         QColor color = param->value().value<QColor>();
673                         if (param->name() == MATERIAL_AMBIENT_COLOR) {
674                             matInfo.colors.insert(QStringLiteral("ambient"), color);
675                         } else if (param->name() == MATERIAL_DIFFUSE_COLOR) {
676                             if (matInfo.type == MaterialInfo::TypePhongAlpha) {
677                                 matInfo.values.insert(QStringLiteral("transparency"), float(color.alphaF()));
678                                 color.setAlphaF(1.0f);
679                             }
680                             matInfo.colors.insert(QStringLiteral("diffuse"), color);
681                         } else if (param->name() == MATERIAL_SPECULAR_COLOR) {
682                             matInfo.colors.insert(QStringLiteral("specular"), color);
683                         } else if (param->name() == MATERIAL_COOL_COLOR) { // Custom Qt3D gooch
684                             matInfo.colors.insert(QStringLiteral("cool"), color);
685                         } else if (param->name() == MATERIAL_WARM_COLOR) { // Custom Qt3D gooch
686                             matInfo.colors.insert(QStringLiteral("warm"), color);
687                         } else {
688                             matInfo.colors.insert(param->name(), color);
689                         }
690                     } else if (param->value().canConvert<QAbstractTexture *>()) {
691                         const QString urlString = textureVariantToUrl(param->value());
692                         if (param->name() == MATERIAL_DIFFUSE_TEXTURE)
693                             matInfo.textures.insert(QStringLiteral("diffuse"), urlString);
694                         else if (param->name() == MATERIAL_SPECULAR_TEXTURE)
695                             matInfo.textures.insert(QStringLiteral("specular"), urlString);
696                         else if (param->name() == MATERIAL_NORMALS_TEXTURE)
697                             matInfo.textures.insert(QStringLiteral("normal"), urlString);
698                         else
699                             matInfo.textures.insert(param->name(), urlString);
700                     } else if (param->name() == MATERIAL_SHININESS) {
701                         matInfo.values.insert(QStringLiteral("shininess"), param->value());
702                     } else if (param->name() == MATERIAL_BETA) { // Custom Qt3D param for gooch
703                         matInfo.values.insert(QStringLiteral("beta"), param->value());
704                     } else if (param->name() == MATERIAL_ALPHA) {
705                         if (matInfo.type == MaterialInfo::TypeGooch)
706                             matInfo.values.insert(QStringLiteral("alpha"), param->value());
707                         else
708                             matInfo.values.insert(QStringLiteral("transparency"), param->value());
709                     } else if (param->name() == MATERIAL_TEXTURE_SCALE) { // Custom Qt3D param
710                         matInfo.values.insert(QStringLiteral("textureScale"), param->value());
711                     } else {
712                         qCDebug(GLTFExporterLog,
713                                 "Common material had unknown parameter: '%ls'",
714                                 qUtf16PrintableImpl(param->name()));
715                     }
716                 }
717             }
718         }
719 
720         if (GLTFExporterLog().isDebugEnabled()) {
721             qCDebug(GLTFExporterLog, "  Material #%i", materialCount);
722             qCDebug(GLTFExporterLog, "    name: '%ls'", qUtf16PrintableImpl(matInfo.name));
723             qCDebug(GLTFExporterLog, "    originalName: '%ls'",
724                     qUtf16PrintableImpl(matInfo.originalName));
725             qCDebug(GLTFExporterLog, "    type: %i", matInfo.type);
726             qCDebug(GLTFExporterLog) << "    colors:" << matInfo.colors;
727             qCDebug(GLTFExporterLog) << "    values:" << matInfo.values;
728             qCDebug(GLTFExporterLog) << "    textures:" << matInfo.textures;
729         }
730 
731         m_materialInfo.insert(material, matInfo);
732         materialCount++;
733     }
734 }
735 
parseMeshes()736 void GLTFExporter::parseMeshes()
737 {
738     qCDebug(GLTFExporterLog, "Parsing meshes...");
739 
740     int meshCount = 0;
741     for (auto it = m_meshMap.constBegin(); it != m_meshMap.constEnd(); ++it) {
742         Node *node = it.key();
743         QGeometryRenderer *mesh = it.value();
744 
745         MeshInfo meshInfo;
746         meshInfo.originalName = mesh->objectName();
747         meshInfo.name = newMeshName();
748         meshInfo.materialName = m_materialInfo.value(m_materialMap.value(node)).name;
749 
750         if (qobject_cast<QConeMesh *>(mesh)) {
751             meshInfo.meshType = TypeConeMesh;
752             meshInfo.meshTypeStr = QStringLiteral("cone");
753         } else if (qobject_cast<QCuboidMesh *>(mesh)) {
754             meshInfo.meshType = TypeCuboidMesh;
755             meshInfo.meshTypeStr = QStringLiteral("cuboid");
756         } else if (qobject_cast<QCylinderMesh *>(mesh)) {
757             meshInfo.meshType = TypeCylinderMesh;
758             meshInfo.meshTypeStr = QStringLiteral("cylinder");
759         } else if (qobject_cast<QPlaneMesh *>(mesh)) {
760             meshInfo.meshType = TypePlaneMesh;
761             meshInfo.meshTypeStr = QStringLiteral("plane");
762         } else if (qobject_cast<QSphereMesh *>(mesh)) {
763             meshInfo.meshType = TypeSphereMesh;
764             meshInfo.meshTypeStr = QStringLiteral("sphere");
765         } else if (qobject_cast<QTorusMesh *>(mesh)) {
766             meshInfo.meshType = TypeTorusMesh;
767             meshInfo.meshTypeStr = QStringLiteral("torus");
768         } else {
769             meshInfo.meshType = TypeNone;
770         }
771 
772         if (meshInfo.meshType != TypeNone) {
773             meshInfo.meshComponent = mesh;
774             cacheDefaultProperties(meshInfo.meshType);
775 
776             if (GLTFExporterLog().isDebugEnabled()) {
777                 qCDebug(GLTFExporterLog, "  Mesh #%i: (%ls/%ls)", meshCount,
778                         qUtf16PrintableImpl(meshInfo.name), qUtf16PrintableImpl(meshInfo.originalName));
779                 qCDebug(GLTFExporterLog, "    material: '%ls'",
780                         qUtf16PrintableImpl(meshInfo.materialName));
781                 qCDebug(GLTFExporterLog, "    basic mesh type: '%s'",
782                         mesh->metaObject()->className());
783             }
784         } else {
785             meshInfo.meshComponent = nullptr;
786             QGeometry *meshGeometry = nullptr;
787             QGeometryFactoryPtr geometryFunctorPtr = mesh->geometryFactory();
788             if (!geometryFunctorPtr.data()) {
789                 meshGeometry = mesh->geometry();
790             } else {
791                 // Execute the geometry functor to get the geometry, if it is available.
792                 // Functor gives us the latest data if geometry has changed.
793                 meshGeometry = geometryFunctorPtr.data()->operator()();
794             }
795 
796             if (!meshGeometry) {
797                 qCWarning(GLTFExporterLog, "Ignoring mesh without geometry!");
798                 continue;
799             }
800 
801             QAttribute *indexAttrib = nullptr;
802             const quint16 *indexPtr = nullptr;
803 
804             struct VertexAttrib {
805                 QAttribute *att;
806                 const float *ptr;
807                 QString usage;
808                 uint offset;
809                 uint stride;
810                 int index;
811             };
812 
813             QVector<VertexAttrib> vAttribs;
814             vAttribs.reserve(meshGeometry->attributes().size());
815 
816             uint stride(0);
817 
818             const auto attributes = meshGeometry->attributes();
819             for (QAttribute *att : attributes) {
820                 if (att->attributeType() == QAttribute::IndexAttribute) {
821                     indexAttrib = att;
822                     indexPtr = reinterpret_cast<const quint16 *>(att->buffer()->data().constData());
823                 } else {
824                     VertexAttrib vAtt;
825                     vAtt.att = att;
826                     vAtt.ptr = reinterpret_cast<const float *>(att->buffer()->data().constData());
827                     if (att->name() == VERTICES_ATTRIBUTE_NAME)
828                         vAtt.usage = QStringLiteral("POSITION");
829                     else if (att->name() == NORMAL_ATTRIBUTE_NAME)
830                         vAtt.usage = QStringLiteral("NORMAL");
831                     else if (att->name() == TEXTCOORD_ATTRIBUTE_NAME)
832                         vAtt.usage = QStringLiteral("TEXCOORD_0");
833                     else if (att->name() == COLOR_ATTRIBUTE_NAME)
834                         vAtt.usage = QStringLiteral("COLOR");
835                     else if (att->name() == TANGENT_ATTRIBUTE_NAME)
836                         vAtt.usage = QStringLiteral("TANGENT");
837                     else
838                         vAtt.usage = att->name();
839 
840                     vAtt.offset = att->byteOffset() / sizeof(float);
841                     vAtt.index = vAtt.offset;
842                     vAtt.stride = att->byteStride() > 0
843                             ? att->byteStride() / sizeof(float) - att->vertexSize() : 0;
844                     stride += att->vertexSize();
845 
846                     vAttribs << vAtt;
847                 }
848             }
849 
850             int attribCount(vAttribs.size());
851             if (!attribCount) {
852                 qCWarning(GLTFExporterLog, "Ignoring mesh without any attributes!");
853                 continue;
854             }
855 
856             QByteArray vertexBuf;
857             const int vertexCount = vAttribs.at(0).att->count();
858             vertexBuf.resize(stride * vertexCount * sizeof(float));
859             float *p = reinterpret_cast<float *>(vertexBuf.data());
860 
861             // Create interleaved buffer
862             for (int i = 0; i < vertexCount; ++i) {
863                 for (int j = 0; j < attribCount; ++j) {
864                     VertexAttrib &vAtt = vAttribs[j];
865                     for (uint k = 0; k < vAtt.att->vertexSize(); ++k)
866                         *p++ = vAtt.ptr[vAtt.index++];
867                     vAtt.index += vAtt.stride;
868                 }
869             }
870 
871             MeshInfo::BufferView vertexBufView;
872             vertexBufView.name = newBufferViewName();
873             vertexBufView.length = vertexBuf.size();
874             vertexBufView.offset = m_buffer.size();
875             vertexBufView.componentType = GL_FLOAT;
876             vertexBufView.target = GL_ARRAY_BUFFER;
877             meshInfo.views.append(vertexBufView);
878 
879             QByteArray indexBuf;
880             MeshInfo::BufferView indexBufView;
881             uint indexCount = 0;
882             if (indexAttrib) {
883                 const uint indexSize = indexAttrib->vertexBaseType() == QAttribute::UnsignedShort
884                         ? sizeof(quint16) : sizeof(quint32);
885                 indexCount = indexAttrib->count();
886                 uint srcIndex = indexAttrib->byteOffset() / indexSize;
887                 const uint indexStride = indexAttrib->byteStride()
888                         ? indexAttrib->byteStride() / indexSize - 1: 0;
889                 indexBuf.resize(indexCount * indexSize);
890                 if (indexSize == sizeof(quint32)) {
891                     quint32 *dst = reinterpret_cast<quint32 *>(indexBuf.data());
892                     const quint32 *src = reinterpret_cast<const quint32 *>(indexPtr);
893                     for (uint j = 0; j < indexCount; ++j) {
894                         *dst++ = src[srcIndex++];
895                         srcIndex += indexStride;
896                     }
897                 } else {
898                     quint16 *dst = reinterpret_cast<quint16 *>(indexBuf.data());
899                     for (uint j = 0; j < indexCount; ++j) {
900                         *dst++ = indexPtr[srcIndex++];
901                         srcIndex += indexStride;
902                     }
903                 }
904 
905                 indexBufView.name = newBufferViewName();
906                 indexBufView.length = indexBuf.size();
907                 indexBufView.offset = vertexBufView.offset + vertexBufView.length;
908                 indexBufView.componentType = indexSize == sizeof(quint32)
909                         ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
910                 indexBufView.target = GL_ELEMENT_ARRAY_BUFFER;
911                 meshInfo.views.append(indexBufView);
912             }
913 
914             MeshInfo::Accessor acc;
915             uint startOffset = 0;
916 
917             acc.bufferView = vertexBufView.name;
918             acc.stride = stride * sizeof(float);
919             acc.count = vertexCount;
920             acc.componentType = vertexBufView.componentType;
921             for (int i = 0; i < attribCount; ++i) {
922                 const VertexAttrib &vAtt = vAttribs.at(i);
923                 acc.name = newAccessorName();
924                 acc.usage = vAtt.usage;
925                 acc.offset = startOffset * sizeof(float);
926                 switch (vAtt.att->vertexSize()) {
927                 case 1:
928                     acc.type = QStringLiteral("SCALAR");
929                     break;
930                 case 2:
931                     acc.type = QStringLiteral("VEC2");
932                     break;
933                 case 3:
934                     acc.type = QStringLiteral("VEC3");
935                     break;
936                 case 4:
937                     acc.type = QStringLiteral("VEC4");
938                     break;
939                 case 9:
940                     acc.type = QStringLiteral("MAT3");
941                     break;
942                 case 16:
943                     acc.type = QStringLiteral("MAT4");
944                     break;
945                 default:
946                     qCWarning(GLTFExporterLog, "Invalid vertex size: %d", vAtt.att->vertexSize());
947                     break;
948                 }
949                 meshInfo.accessors.append(acc);
950                 startOffset += vAtt.att->vertexSize();
951             }
952 
953             // Index
954             if (indexAttrib) {
955                 acc.name = newAccessorName();
956                 acc.usage = QStringLiteral("INDEX");
957                 acc.bufferView = indexBufView.name;
958                 acc.offset = 0;
959                 acc.stride = 0;
960                 acc.count = indexCount;
961                 acc.componentType = indexBufView.componentType;
962                 acc.type = QStringLiteral("SCALAR");
963                 meshInfo.accessors.append(acc);
964             }
965             m_buffer.append(vertexBuf);
966             m_buffer.append(indexBuf);
967 
968             if (GLTFExporterLog().isDebugEnabled()) {
969                 qCDebug(GLTFExporterLog, "  Mesh #%i: (%ls/%ls)", meshCount,
970                         qUtf16PrintableImpl(meshInfo.name), qUtf16PrintableImpl(meshInfo.originalName));
971                 qCDebug(GLTFExporterLog, "    Vertex count: %i", vertexCount);
972                 qCDebug(GLTFExporterLog, "    Bytes per vertex: %i", stride);
973                 qCDebug(GLTFExporterLog, "    Vertex buffer size (bytes): %i", vertexBuf.size());
974                 qCDebug(GLTFExporterLog, "    Index buffer size (bytes): %i", indexBuf.size());
975                 QStringList sl;
976                 const auto views = meshInfo.views;
977                 for (const auto &bv : views)
978                     sl << bv.name;
979                 qCDebug(GLTFExporterLog) << "    buffer views:" << sl;
980                 sl.clear();
981                 for (const auto &acc : qAsConst(meshInfo.accessors))
982                     sl << acc.name;
983                 qCDebug(GLTFExporterLog) << "    accessors:" << sl;
984                 qCDebug(GLTFExporterLog, "    material: '%ls'",
985                         qUtf16PrintableImpl(meshInfo.materialName));
986             }
987         }
988 
989         meshCount++;
990         m_meshInfo.insert(mesh, meshInfo);
991     }
992 
993     qCDebug(GLTFExporterLog, "Total buffer size: %i", m_buffer.size());
994 }
995 
parseCameras()996 void GLTFExporter::parseCameras()
997 {
998     qCDebug(GLTFExporterLog, "Parsing cameras...");
999     int cameraCount = 0;
1000 
1001     for (auto it = m_cameraMap.constBegin(); it != m_cameraMap.constEnd(); ++it) {
1002         QCameraLens *camera = it.value();
1003         CameraInfo c;
1004 
1005         if (camera->projectionType() == QCameraLens::PerspectiveProjection) {
1006             c.perspective = true;
1007             c.aspectRatio = camera->aspectRatio();
1008             c.yfov = qDegreesToRadians(camera->fieldOfView());
1009         } else {
1010             c.perspective = false;
1011             // Note that accurate conversion from four properties of QCameraLens to just two
1012             // properties of gltf orthographic cameras is not feasible. Only centered cases
1013             // convert properly.
1014             c.xmag = qAbs(camera->left() - camera->right());
1015             c.ymag = qAbs(camera->top() - camera->bottom());
1016         }
1017 
1018         c.originalName = camera->objectName();
1019         c.name = newCameraName();
1020         c.znear = camera->nearPlane();
1021         c.zfar = camera->farPlane();
1022 
1023         // GLTF cameras point in -Z by default, the rest is in the
1024         // node matrix, so no separate look-at params given here, unless it's actually QCamera.
1025         QCamera *cameraEntity = nullptr;
1026         const QVector<QEntity *> entities = camera->entities();
1027         if (entities.size() == 1)
1028             cameraEntity = qobject_cast<QCamera *>(entities.at(0));
1029         c.cameraEntity = cameraEntity;
1030 
1031         m_cameraInfo.insert(camera, c);
1032         if (GLTFExporterLog().isDebugEnabled()) {
1033             qCDebug(GLTFExporterLog, "  Camera: #%i: (%ls/%ls)", cameraCount++,
1034                     qUtf16PrintableImpl(c.name), qUtf16PrintableImpl(c.originalName));
1035             qCDebug(GLTFExporterLog, "    Aspect ratio: %f", c.aspectRatio);
1036             qCDebug(GLTFExporterLog, "    Fov: %f", c.yfov);
1037             qCDebug(GLTFExporterLog, "    Near: %f", c.znear);
1038             qCDebug(GLTFExporterLog, "    Far: %f", c.zfar);
1039         }
1040     }
1041 }
1042 
parseLights()1043 void GLTFExporter::parseLights()
1044 {
1045     qCDebug(GLTFExporterLog, "Parsing lights...");
1046     int lightCount = 0;
1047     for (auto it = m_lightMap.constBegin(); it != m_lightMap.constEnd(); ++it) {
1048         QAbstractLight *light = it.value();
1049         LightInfo lightInfo;
1050         lightInfo.direction = QVector3D();
1051         lightInfo.attenuation = QVector3D();
1052         lightInfo.cutOffAngle = 0.0f;
1053         lightInfo.type = light->type();
1054         if (light->type() == QAbstractLight::SpotLight) {
1055             QSpotLight *spot = qobject_cast<QSpotLight *>(light);
1056             lightInfo.direction = spot->localDirection();
1057             lightInfo.attenuation = QVector3D(spot->constantAttenuation(),
1058                                               spot->linearAttenuation(),
1059                                               spot->quadraticAttenuation());
1060             lightInfo.cutOffAngle = spot->cutOffAngle();
1061         } else if (light->type() == QAbstractLight::PointLight) {
1062             QPointLight *point = qobject_cast<QPointLight *>(light);
1063             lightInfo.attenuation = QVector3D(point->constantAttenuation(),
1064                                               point->linearAttenuation(),
1065                                               point->quadraticAttenuation());
1066         } else if (light->type() == QAbstractLight::DirectionalLight) {
1067             QDirectionalLight *directional = qobject_cast<QDirectionalLight *>(light);
1068             lightInfo.direction = directional->worldDirection();
1069         }
1070         lightInfo.color = light->color();
1071         lightInfo.intensity = light->intensity();
1072 
1073         lightInfo.originalName = light->objectName();
1074         lightInfo.name = newLightName();
1075 
1076         m_lightInfo.insert(light, lightInfo);
1077 
1078         if (GLTFExporterLog().isDebugEnabled()) {
1079             qCDebug(GLTFExporterLog, "  Light #%i: (%ls/%ls)", lightCount++,
1080                     qUtf16PrintableImpl(lightInfo.name), qUtf16PrintableImpl(lightInfo.originalName));
1081             qCDebug(GLTFExporterLog, "    Type: %i", lightInfo.type);
1082             qCDebug(GLTFExporterLog, "    Color: (%i, %i, %i, %i)", lightInfo.color.red(),
1083                     lightInfo.color.green(), lightInfo.color.blue(), lightInfo.color.alpha());
1084             qCDebug(GLTFExporterLog, "    Intensity: %f", lightInfo.intensity);
1085             qCDebug(GLTFExporterLog, "    Direction: (%f, %f, %f)", lightInfo.direction.x(),
1086                     lightInfo.direction.y(), lightInfo.direction.z());
1087             qCDebug(GLTFExporterLog, "    Attenuation: (%f, %f, %f)", lightInfo.attenuation.x(),
1088                     lightInfo.attenuation.y(), lightInfo.attenuation.z());
1089             qCDebug(GLTFExporterLog, "    CutOffAngle: %f", lightInfo.cutOffAngle);
1090         }
1091     }
1092 }
1093 
parseTechniques(QMaterial * material)1094 void GLTFExporter::parseTechniques(QMaterial *material)
1095 {
1096     int techniqueCount = 0;
1097     qCDebug(GLTFExporterLog, "  Parsing material techniques...");
1098 
1099     const auto techniques = material->effect()->techniques();
1100     for (auto technique : techniques) {
1101         QString techName;
1102         if (m_techniqueIdMap.contains(technique)) {
1103             techName = m_techniqueIdMap.value(technique);
1104         } else {
1105             techName = newTechniqueName();
1106             parseRenderPasses(technique);
1107 
1108         }
1109         m_techniqueIdMap.insert(technique, techName);
1110 
1111         techniqueCount++;
1112 
1113         if (GLTFExporterLog().isDebugEnabled()) {
1114             qCDebug(GLTFExporterLog, "    Technique #%i", techniqueCount);
1115             qCDebug(GLTFExporterLog, "      name: '%ls'", qUtf16PrintableImpl(techName));
1116         }
1117     }
1118 }
1119 
parseRenderPasses(QTechnique * technique)1120 void GLTFExporter::parseRenderPasses(QTechnique *technique)
1121 {
1122     int passCount = 0;
1123     qCDebug(GLTFExporterLog, "    Parsing render passes for technique...");
1124 
1125     const auto renderPasses = technique->renderPasses();
1126     for (auto pass : renderPasses) {
1127         QString name;
1128         if (m_renderPassIdMap.contains(pass)) {
1129             name = m_renderPassIdMap.value(pass);
1130         } else {
1131             name = newRenderPassName();
1132             m_renderPassIdMap.insert(pass, name);
1133             if (pass->shaderProgram() && !m_programInfo.contains(pass->shaderProgram())) {
1134                 ProgramInfo pi;
1135                 pi.name = newProgramName();
1136                 pi.vertexShader = addShaderInfo(QShaderProgram::Vertex,
1137                                                 pass->shaderProgram()->vertexShaderCode());
1138                 pi.tessellationControlShader =
1139                         addShaderInfo(QShaderProgram::Fragment,
1140                                       pass->shaderProgram()->tessellationControlShaderCode());
1141                 pi.tessellationEvaluationShader =
1142                         addShaderInfo(QShaderProgram::TessellationControl,
1143                                       pass->shaderProgram()->tessellationEvaluationShaderCode());
1144                 pi.geometryShader = addShaderInfo(QShaderProgram::TessellationEvaluation,
1145                                                   pass->shaderProgram()->geometryShaderCode());
1146                 pi.fragmentShader = addShaderInfo(QShaderProgram::Geometry,
1147                                                   pass->shaderProgram()->fragmentShaderCode());
1148                 pi.computeShader = addShaderInfo(QShaderProgram::Compute,
1149                                                  pass->shaderProgram()->computeShaderCode());
1150                 m_programInfo.insert(pass->shaderProgram(), pi);
1151                 qCDebug(GLTFExporterLog, "      program: '%ls'", qUtf16PrintableImpl(pi.name));
1152             }
1153         }
1154         passCount++;
1155 
1156         if (GLTFExporterLog().isDebugEnabled()) {
1157             qCDebug(GLTFExporterLog, "      Render pass #%i", passCount);
1158             qCDebug(GLTFExporterLog, "        name: '%ls'", qUtf16PrintableImpl(name));
1159         }
1160     }
1161 }
1162 
addShaderInfo(QShaderProgram::ShaderType type,QByteArray code)1163 QString GLTFExporter::addShaderInfo(QShaderProgram::ShaderType type, QByteArray code)
1164 {
1165     if (code.isEmpty())
1166         return QString();
1167 
1168     for (const auto &si : qAsConst(m_shaderInfo)) {
1169         if (si.type == QShaderProgram::Vertex && code == si.code)
1170             return si.name;
1171     }
1172 
1173     ShaderInfo newInfo;
1174     newInfo.type = type;
1175     newInfo.code = code;
1176     newInfo.name = newShaderName();
1177     newInfo.uri = newInfo.name + QStringLiteral(".glsl");
1178 
1179     m_shaderInfo.append(newInfo);
1180 
1181     qCDebug(GLTFExporterLog, "      shader: '%ls'", qUtf16PrintableImpl(newInfo.name));
1182 
1183     return newInfo.name;
1184 }
1185 
saveScene()1186 bool GLTFExporter::saveScene()
1187 {
1188     qCDebug(GLTFExporterLog, "Saving scene...");
1189 
1190     QVector<MeshInfo::BufferView> bvList;
1191     QVector<MeshInfo::Accessor> accList;
1192     for (auto it = m_meshInfo.begin(); it != m_meshInfo.end(); ++it) {
1193         auto &mi = it.value();
1194         for (auto &v : mi.views)
1195             bvList << v;
1196         for (auto &acc : mi.accessors)
1197             accList << acc;
1198     }
1199 
1200     m_obj = QJsonObject();
1201 
1202     QJsonObject asset;
1203     asset["generator"] = QString(QStringLiteral("GLTFExporter %1")).arg(qVersion());
1204     asset["version"] = QStringLiteral("1.0");
1205     asset["premultipliedAlpha"] = true;
1206     m_obj["asset"] = asset;
1207 
1208     QString bufName = m_exportName + QStringLiteral(".bin");
1209     QString binFileName = m_exportDir + bufName;
1210     QFile f(binFileName);
1211     QFileInfo fiBin(binFileName);
1212 
1213     if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1214         qCDebug(GLTFExporterLog, "  Writing '%ls'", qUtf16PrintableImpl(binFileName));
1215         m_exportedFiles.insert(fiBin.fileName());
1216         f.write(m_buffer);
1217         f.close();
1218     } else {
1219         qCWarning(GLTFExporterLog, "  Creating buffers file '%ls' failed!",
1220                   qUtf16PrintableImpl(binFileName));
1221         return false;
1222     }
1223 
1224     QJsonObject buffers;
1225     QJsonObject buffer;
1226     buffer["byteLength"] = m_buffer.size();
1227     buffer["type"] = QStringLiteral("arraybuffer");
1228     buffer["uri"] = bufName;
1229     buffers["buf"] = buffer;
1230     m_obj["buffers"] = buffers;
1231 
1232     QJsonObject bufferViews;
1233     for (const auto &bv : qAsConst(bvList)) {
1234         QJsonObject bufferView;
1235         bufferView["buffer"] = QStringLiteral("buf");
1236         bufferView["byteLength"] = int(bv.length);
1237         bufferView["byteOffset"] = int(bv.offset);
1238         if (bv.target)
1239             bufferView["target"] = int(bv.target);
1240         bufferViews[bv.name] = bufferView;
1241     }
1242     if (bufferViews.size())
1243         m_obj["bufferViews"] = bufferViews;
1244 
1245     QJsonObject accessors;
1246     for (const auto &acc : qAsConst(accList)) {
1247         QJsonObject accessor;
1248         accessor["bufferView"] = acc.bufferView;
1249         accessor["byteOffset"] = int(acc.offset);
1250         accessor["byteStride"] = int(acc.stride);
1251         accessor["count"] = int(acc.count);
1252         accessor["componentType"] = int(acc.componentType);
1253         accessor["type"] = acc.type;
1254         accessors[acc.name] = accessor;
1255     }
1256     if (accessors.size())
1257         m_obj["accessors"] = accessors;
1258 
1259     QJsonObject meshes;
1260     for (auto it = m_meshInfo.begin(); it != m_meshInfo.end(); ++it) {
1261         auto &meshInfo = it.value();
1262         QJsonObject mesh;
1263         mesh["name"] = meshInfo.originalName;
1264         if (meshInfo.meshType != TypeNone) {
1265             QJsonObject properties;
1266             exportGenericProperties(properties, meshInfo.meshType, meshInfo.meshComponent);
1267             mesh["type"] = meshInfo.meshTypeStr;
1268             mesh["properties"] = properties;
1269             mesh["material"] = meshInfo.materialName;
1270         } else {
1271             QJsonArray prims;
1272             QJsonObject prim;
1273             prim["mode"] = 4; // triangles
1274             QJsonObject attrs;
1275             const auto meshAccessors = meshInfo.accessors;
1276             for (const auto &acc : meshAccessors) {
1277                 if (acc.usage != QStringLiteral("INDEX"))
1278                     attrs[acc.usage] = acc.name;
1279                 else
1280                     prim["indices"] = acc.name;
1281             }
1282             prim["attributes"] = attrs;
1283             prim["material"] = meshInfo.materialName;
1284             prims.append(prim);
1285             mesh["primitives"] = prims;
1286         }
1287         meshes[meshInfo.name] = mesh;
1288     }
1289     if (meshes.size())
1290         m_obj["meshes"] = meshes;
1291 
1292     QJsonObject cameras;
1293     for (const auto &camInfo : qAsConst(m_cameraInfo)) {
1294         QJsonObject camera;
1295         QJsonObject proj;
1296         proj["znear"] = camInfo.znear;
1297         proj["zfar"] = camInfo.zfar;
1298         if (camInfo.perspective) {
1299             proj["aspect_ratio"] = camInfo.aspectRatio;
1300             proj["yfov"] = camInfo.yfov;
1301             camera["type"] = QStringLiteral("perspective");
1302             camera["perspective"] = proj;
1303         } else {
1304             proj["xmag"] = camInfo.xmag;
1305             proj["ymag"] = camInfo.ymag;
1306             camera["type"] = QStringLiteral("orthographic");
1307             camera["orthographic"] = proj;
1308         }
1309         if (camInfo.cameraEntity) {
1310             camera["position"] = vec2jsvec(camInfo.cameraEntity->position());
1311             camera["upVector"] = vec2jsvec(camInfo.cameraEntity->upVector());
1312             camera["viewCenter"] = vec2jsvec(camInfo.cameraEntity->viewCenter());
1313         }
1314         camera["name"] = camInfo.originalName;
1315         cameras[camInfo.name] = camera;
1316     }
1317     if (cameras.size())
1318         m_obj["cameras"] = cameras;
1319 
1320     QJsonArray sceneNodes;
1321     QJsonObject nodes;
1322     if (m_rootNodeEmpty) {
1323         // Don't export the root node if it is there just to group the scene, so we don't get
1324         // an extra empty node when we import the scene back.
1325         for (auto c : qAsConst(m_rootNode->children))
1326             sceneNodes << exportNodes(c, nodes);
1327     } else {
1328         sceneNodes << exportNodes(m_rootNode, nodes);
1329     }
1330     m_obj["nodes"] = nodes;
1331 
1332     QJsonObject scenes;
1333     QJsonObject defaultScene;
1334     defaultScene["nodes"] = sceneNodes;
1335     scenes["defaultScene"] = defaultScene;
1336     m_obj["scenes"] = scenes;
1337     m_obj["scene"] = QStringLiteral("defaultScene");
1338 
1339     QJsonObject materials;
1340 
1341     exportMaterials(materials);
1342     if (materials.size())
1343         m_obj["materials"] = materials;
1344 
1345     // Lights must be declared as extensions to the top-level glTF object
1346     QJsonObject lights;
1347     for (auto it = m_lightInfo.begin(); it != m_lightInfo.end(); ++it) {
1348         const auto &lightInfo = it.value();
1349         QJsonObject light;
1350         QJsonObject lightDetails;
1351         QString type;
1352         if (lightInfo.type == QAbstractLight::SpotLight) {
1353             type = QStringLiteral("spot");
1354             lightDetails["falloffAngle"] = lightInfo.cutOffAngle;
1355         } else if (lightInfo.type == QAbstractLight::PointLight) {
1356             type = QStringLiteral("point");
1357         } else if (lightInfo.type == QAbstractLight::DirectionalLight) {
1358             type = QStringLiteral("directional");
1359         }
1360         light["type"] = type;
1361         if (lightInfo.type == QAbstractLight::SpotLight
1362                 || lightInfo.type == QAbstractLight::DirectionalLight) {
1363             // The GLTF specs are bit unclear whether there is a direction parameter
1364             // for spot/directional lights, or are they supposed to just use the
1365             // parent transforms for direction, but we do need it in any case, so we add it.
1366             lightDetails["direction"] = vec2jsvec(lightInfo.direction);
1367 
1368         }
1369         if (lightInfo.type == QAbstractLight::SpotLight
1370                 || lightInfo.type == QAbstractLight::PointLight) {
1371             lightDetails["constantAttenuation"] = lightInfo.attenuation.x();
1372             lightDetails["linearAttenuation"] = lightInfo.attenuation.y();
1373             lightDetails["quadraticAttenuation"] = lightInfo.attenuation.z();
1374         }
1375         lightDetails["color"] = col2jsvec(lightInfo.color, false);
1376         lightDetails["intensity"] = lightInfo.intensity; // Not in spec but needed
1377         light["name"] = lightInfo.originalName; // Not in spec but we want to pass the name anyway
1378         light[type] = lightDetails;
1379         lights[lightInfo.name] = light;
1380     }
1381     if (lights.size()) {
1382         QJsonObject extensions;
1383         QJsonObject common;
1384         common["lights"] = lights;
1385         extensions["KHR_materials_common"] = common;
1386         m_obj["extensions"] = extensions;
1387     }
1388 
1389     // Save effects for custom materials
1390     // Note that we are not saving effects, techniques, render passes, shader programs, or shaders
1391     // strictly according to GLTF format, but rather in our expanded QGLTF custom format,
1392     // since the GLTF format doesn't quite match our needs.
1393     // Having our own format also vastly simplifies export and import of custom materials,
1394     // since we are not trying to push a round peg into a square hole.
1395     // If use cases arise in future where our exported GLTF scenes need to be loaded by third party
1396     // GLTF loaders, we could add an export option to do so, but the exported scene would never
1397     // be quite the same as the original.
1398     QJsonObject effects;
1399     for (auto it = m_effectIdMap.constBegin(); it != m_effectIdMap.constEnd(); ++it) {
1400         QEffect *effect = it.key();
1401         const QString effectName = it.value();
1402         QJsonObject effectObj;
1403         QJsonObject paramObj;
1404 
1405         const auto effectParameters = effect->parameters();
1406         for (QParameter *param : effectParameters)
1407             exportParameter(paramObj, param->name(), param->value());
1408         if (!effect->objectName().isEmpty())
1409             effectObj["name"] = effect->objectName();
1410         if (!paramObj.isEmpty())
1411             effectObj["parameters"] = paramObj;
1412         QJsonArray techs;
1413         const auto effectTechniques = effect->techniques();
1414         for (auto tech : effectTechniques)
1415             techs << m_techniqueIdMap.value(tech);
1416         effectObj["techniques"] = techs;
1417         effects[effectName] = effectObj;
1418     }
1419     if (effects.size())
1420         m_obj["effects"] = effects;
1421 
1422     // Save techniques for custom materials.
1423     QJsonObject techniques;
1424     for (auto it = m_techniqueIdMap.constBegin(); it != m_techniqueIdMap.constEnd(); ++it) {
1425         QTechnique *technique = it.key();
1426 
1427         QJsonObject techObj;
1428         QJsonObject filterKeyObj;
1429         QJsonObject paramObj;
1430         QJsonArray renderPassArr;
1431 
1432         const auto techniqueFilterKeys = technique->filterKeys();
1433         for (QFilterKey *filterKey : techniqueFilterKeys)
1434             setVarToJSonObject(filterKeyObj, filterKey->name(), filterKey->value());
1435 
1436         const auto techniqueRenderPasses = technique->renderPasses();
1437         for (QRenderPass *pass : techniqueRenderPasses)
1438             renderPassArr << m_renderPassIdMap.value(pass);
1439 
1440         const auto techniqueParameters = technique->parameters();
1441         for (QParameter *param : techniqueParameters)
1442             exportParameter(paramObj, param->name(), param->value());
1443 
1444         const QGraphicsApiFilter *gFilter = technique->graphicsApiFilter();
1445         if (gFilter) {
1446             QJsonObject graphicsApiFilterObj;
1447             graphicsApiFilterObj["api"] = gFilter->api();
1448             graphicsApiFilterObj["profile"] = gFilter->profile();
1449             graphicsApiFilterObj["minorVersion"] = gFilter->minorVersion();
1450             graphicsApiFilterObj["majorVersion"] = gFilter->majorVersion();
1451             if (!gFilter->vendor().isEmpty())
1452                 graphicsApiFilterObj["vendor"] = gFilter->vendor();
1453             QJsonArray extensions;
1454             for (const auto &extName : gFilter->extensions())
1455                 extensions << extName;
1456             if (!extensions.isEmpty())
1457                 graphicsApiFilterObj["extensions"] = extensions;
1458             techObj["gapifilter"] = graphicsApiFilterObj;
1459         }
1460         if (!technique->objectName().isEmpty())
1461             techObj["name"] = technique->objectName();
1462         if (!filterKeyObj.isEmpty())
1463             techObj["filterkeys"] = filterKeyObj;
1464         if (!paramObj.isEmpty())
1465             techObj["parameters"] = paramObj;
1466         if (!renderPassArr.isEmpty())
1467             techObj["renderpasses"] = renderPassArr;
1468         techniques[it.value()] = techObj;
1469     }
1470     if (techniques.size())
1471         m_obj["techniques"] = techniques;
1472 
1473     // Save render passes for custom materials.
1474     QJsonObject passes;
1475     for (auto it = m_renderPassIdMap.constBegin(); it != m_renderPassIdMap.constEnd(); ++it) {
1476         const QRenderPass *pass = it.key();
1477         const QString passId = it.value();
1478 
1479         QJsonObject passObj;
1480         QJsonObject filterKeyObj;
1481         QJsonObject paramObj;
1482         QJsonObject stateObj;
1483 
1484         for (QFilterKey *filterKey : pass->filterKeys())
1485             setVarToJSonObject(filterKeyObj, filterKey->name(), filterKey->value());
1486         for (QParameter *param : pass->parameters())
1487             exportParameter(paramObj, param->name(), param->value());
1488         exportRenderStates(stateObj, pass);
1489 
1490         if (!pass->objectName().isEmpty())
1491             passObj["name"] = pass->objectName();
1492         if (!filterKeyObj.isEmpty())
1493             passObj["filterkeys"] = filterKeyObj;
1494         if (!paramObj.isEmpty())
1495             passObj["parameters"] = paramObj;
1496         if (!stateObj.isEmpty())
1497             passObj["states"] = stateObj;
1498         passObj["program"] = m_programInfo.value(pass->shaderProgram()).name;
1499         passes[passId] = passObj;
1500 
1501     }
1502     if (passes.size())
1503         m_obj["renderpasses"] = passes;
1504 
1505     // Save programs for custom materials
1506     QJsonObject programs;
1507     for (auto it = m_programInfo.constBegin(); it != m_programInfo.constEnd(); ++it) {
1508         const QShaderProgram *program = it.key();
1509         const ProgramInfo pi = it.value();
1510 
1511         QJsonObject progObj;
1512         if (!program->objectName().isEmpty())
1513             progObj["name"] = program->objectName();
1514         progObj["vertexShader"] = pi.vertexShader;
1515         progObj["fragmentShader"] = pi.fragmentShader;
1516         // Qt3D additions
1517         if (!pi.tessellationControlShader.isEmpty())
1518             progObj["tessCtrlShader"] = pi.tessellationControlShader;
1519         if (!pi.tessellationEvaluationShader.isEmpty())
1520             progObj["tessEvalShader"] = pi.tessellationEvaluationShader;
1521         if (!pi.geometryShader.isEmpty())
1522             progObj["geometryShader"] = pi.geometryShader;
1523         if (!pi.computeShader.isEmpty())
1524             progObj["computeShader"] = pi.computeShader;
1525         programs[pi.name] = progObj;
1526 
1527     }
1528     if (programs.size())
1529         m_obj["programs"] = programs;
1530 
1531     // Save shaders for custom materials
1532     QJsonObject shaders;
1533     for (const auto &si : qAsConst(m_shaderInfo)) {
1534         QJsonObject shaderObj;
1535         shaderObj["uri"] = si.uri;
1536         shaders[si.name] = shaderObj;
1537 
1538     }
1539     if (shaders.size())
1540         m_obj["shaders"] = shaders;
1541 
1542     // Copy textures and shaders into temporary directory
1543     copyTextures();
1544     createShaders();
1545 
1546     QJsonObject textures;
1547     QHash<QString, QString> imageKeyMap; // uri -> key
1548     for (auto it = m_textureIdMap.constBegin(); it != m_textureIdMap.constEnd(); ++it) {
1549         QJsonObject texture;
1550         if (!imageKeyMap.contains(it.key()))
1551             imageKeyMap[it.key()] = newImageName();
1552         texture["source"] = imageKeyMap[it.key()];
1553         texture["format"] = GL_RGBA;
1554         texture["internalFormat"] = GL_RGBA;
1555         texture["sampler"] = QStringLiteral("sampler_mip_rep");
1556         texture["target"] = GL_TEXTURE_2D;
1557         texture["type"] = GL_UNSIGNED_BYTE;
1558         textures[it.value()] = texture;
1559     }
1560     if (textures.size()) {
1561         m_obj["textures"] = textures;
1562         QJsonObject samplers;
1563         QJsonObject sampler;
1564         sampler["magFilter"] = GL_LINEAR;
1565         sampler["minFilter"] = GL_LINEAR_MIPMAP_LINEAR;
1566         sampler["wrapS"] = GL_REPEAT;
1567         sampler["wrapT"] = GL_REPEAT;
1568         samplers["sampler_mip_rep"] = sampler;
1569         m_obj["samplers"] = samplers;
1570     }
1571 
1572     QJsonObject images;
1573     for (auto it = imageKeyMap.constBegin(); it != imageKeyMap.constEnd(); ++it) {
1574         QJsonObject image;
1575         image["uri"] = m_imageMap.value(it.key());
1576         images[it.value()] = image;
1577     }
1578     if (images.size())
1579         m_obj["images"] = images;
1580 
1581     m_doc.setObject(m_obj);
1582 
1583     QString gltfName = m_exportDir + m_exportName + QStringLiteral(".qgltf");
1584     f.setFileName(gltfName);
1585     qCDebug(GLTFExporterLog, "  Writing %sJSON file: '%ls'",
1586             m_gltfOpts.binaryJson ? "binary " : "", qUtf16PrintableImpl(gltfName));
1587 
1588     if (m_gltfOpts.binaryJson) {
1589         if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1590             m_exportedFiles.insert(QFileInfo(f.fileName()).fileName());
1591 QT_WARNING_PUSH
1592 QT_WARNING_DISABLE_DEPRECATED
1593             QByteArray json = m_doc.toBinaryData();
1594 QT_WARNING_POP
1595             f.write(json);
1596             f.close();
1597         } else {
1598             qCWarning(GLTFExporterLog, "  Writing binary JSON file '%ls' failed!",
1599                       qUtf16PrintableImpl(gltfName));
1600             return false;
1601         }
1602     } else {
1603         if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1604             m_exportedFiles.insert(QFileInfo(f.fileName()).fileName());
1605             QByteArray json = m_doc.toJson(m_gltfOpts.compactJson ? QJsonDocument::Compact
1606                                                                   : QJsonDocument::Indented);
1607             f.write(json);
1608             f.close();
1609         } else {
1610             qCWarning(GLTFExporterLog, "  Writing JSON file '%ls' failed!",
1611                       qUtf16PrintableImpl(gltfName));
1612             return false;
1613         }
1614     }
1615 
1616     QString qrcName = m_exportDir + m_exportName + QStringLiteral(".qrc");
1617     f.setFileName(qrcName);
1618     qCDebug(GLTFExporterLog, "Writing '%ls'", qUtf16PrintableImpl(qrcName));
1619     if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1620         QByteArray pre = "<RCC><qresource prefix=\"/gltf_res\">\n";
1621         QByteArray post = "</qresource></RCC>\n";
1622         f.write(pre);
1623         for (const auto &file : qAsConst(m_exportedFiles)) {
1624             QString line = QString(QStringLiteral("  <file>%1</file>\n")).arg(file);
1625             f.write(line.toUtf8());
1626         }
1627         f.write(post);
1628         f.close();
1629         m_exportedFiles.insert(QFileInfo(f.fileName()).fileName());
1630     } else {
1631         qCWarning(GLTFExporterLog, "  Creating qrc file '%ls' failed!",
1632                   qUtf16PrintableImpl(qrcName));
1633         return false;
1634     }
1635 
1636     qCDebug(GLTFExporterLog, "Saving done!");
1637 
1638     return true;
1639 }
1640 
delNode(GLTFExporter::Node * n)1641 void GLTFExporter::delNode(GLTFExporter::Node *n)
1642 {
1643     if (!n)
1644         return;
1645     for (auto *c : qAsConst(n->children))
1646         delNode(c);
1647     delete n;
1648 }
1649 
exportNodes(GLTFExporter::Node * n,QJsonObject & nodes)1650 QString GLTFExporter::exportNodes(GLTFExporter::Node *n, QJsonObject &nodes)
1651 {
1652     QJsonObject node;
1653     node["name"] = n->name;
1654     QJsonArray children;
1655     for (auto c : qAsConst(n->children))
1656         children << exportNodes(c, nodes);
1657     node["children"] = children;
1658     if (auto transform = m_transformMap.value(n))
1659         node["matrix"] = matrix2jsvec(transform->matrix());
1660 
1661     if (auto mesh = m_meshMap.value(n)) {
1662         QJsonArray meshList;
1663         meshList.append(m_meshInfo.value(mesh).name);
1664         node["meshes"] = meshList;
1665     }
1666 
1667     if (auto camera = m_cameraMap.value(n))
1668         node["camera"] = m_cameraInfo.value(camera).name;
1669 
1670     if (auto light = m_lightMap.value(n)) {
1671         QJsonObject extensions;
1672         QJsonObject lights;
1673         lights["light"] = m_lightInfo.value(light).name;
1674         extensions["KHR_materials_common"] = lights;
1675         node["extensions"] = extensions;
1676     }
1677 
1678     nodes[n->uniqueName] = node;
1679     return n->uniqueName;
1680 }
1681 
exportMaterials(QJsonObject & materials)1682 void GLTFExporter::exportMaterials(QJsonObject &materials)
1683 {
1684     QHash<QString, bool> imageHasAlpha;
1685 
1686     for (auto matIt = m_materialInfo.constBegin(); matIt != m_materialInfo.constEnd(); ++matIt) {
1687         const QMaterial *material = matIt.key();
1688         const MaterialInfo &matInfo = matIt.value();
1689 
1690         QJsonObject materialObj;
1691         materialObj["name"] = matInfo.originalName;
1692 
1693         if (matInfo.type == MaterialInfo::TypeCustom) {
1694             QVector<QParameter *> parameters = material->parameters();
1695             QJsonObject paramObj;
1696             for (auto param : parameters)
1697                 exportParameter(paramObj, param->name(), param->value());
1698             materialObj["effect"] = m_effectIdMap.value(material->effect());
1699             materialObj["parameters"] = paramObj;
1700         } else {
1701             bool opaque = true;
1702             QJsonObject vals;
1703             for (auto it = matInfo.textures.constBegin(); it != matInfo.textures.constEnd(); ++it) {
1704                 QString key = it.key();
1705                 if (key == QStringLiteral("normal")) // avoid clashing with the vertex normals
1706                     key = QStringLiteral("normalmap");
1707                 // Alpha is supported for diffuse textures, but have to check the image data to
1708                 // decide if blending is needed
1709                 if (key == QStringLiteral("diffuse")) {
1710                     QString imgFn = it.value();
1711                     if (imageHasAlpha.contains(imgFn)) {
1712                         if (imageHasAlpha[imgFn])
1713                             opaque = false;
1714                     } else {
1715                         QImage img(imgFn);
1716                         if (!img.isNull()) {
1717                             if (img.hasAlphaChannel()) {
1718                                 for (int y = 0; opaque && y < img.height(); ++y) {
1719                                     for (int x = 0; opaque && x < img.width(); ++x) {
1720                                         if (qAlpha(img.pixel(x, y)) < 255)
1721                                             opaque = false;
1722                                     }
1723                                 }
1724                             }
1725                             imageHasAlpha[imgFn] = !opaque;
1726                         } else {
1727                             qCWarning(GLTFExporterLog,
1728                                       "Cannot determine presence of alpha for '%ls'",
1729                                       qUtf16PrintableImpl(imgFn));
1730                         }
1731                     }
1732                 }
1733                 vals[key] = m_textureIdMap.value(it.value());
1734             }
1735             for (auto it = matInfo.values.constBegin(); it != matInfo.values.constEnd(); ++it) {
1736                 if (vals.contains(it.key()))
1737                     continue;
1738                 setVarToJSonObject(vals, it.key(), it.value());
1739             }
1740             for (auto it = matInfo.colors.constBegin(); it != matInfo.colors.constEnd(); ++it) {
1741                 if (vals.contains(it.key()))
1742                     continue;
1743                 // Alpha is supported for the diffuse color. < 1 will enable blending.
1744                 const bool alpha = (it.key() == QStringLiteral("diffuse"))
1745                         && (matInfo.type != MaterialInfo::TypeCustom);
1746                 if (alpha && it.value().alphaF() < 1.0f)
1747                     opaque = false;
1748                 vals[it.key()] = col2jsvec(it.value(), alpha);
1749             }
1750             // Material is a common material, so export it as such.
1751             QJsonObject commonMat;
1752             if (matInfo.type == MaterialInfo::TypeGooch)
1753                 commonMat["technique"] = QStringLiteral("GOOCH"); // Qt3D specific extension
1754             else if (matInfo.type == MaterialInfo::TypePerVertex)
1755                 commonMat["technique"] = QStringLiteral("PERVERTEX"); // Qt3D specific extension
1756             else
1757                 commonMat["technique"] = QStringLiteral("PHONG");
1758 
1759             // Set the values as-is. "normalmap" is our own extension, not in the spec.
1760             // However, RGB colors have to be promoted to RGBA since the spec uses
1761             // vec4, and all types are pre-defined for common material values.
1762             promoteColorsToRGBA(&vals);
1763             if (!vals.isEmpty())
1764                 commonMat["values"] = vals;
1765 
1766             // Blend function handling is our own extension used for Phong Alpha material.
1767             QJsonObject functions;
1768             if (!matInfo.blendEquations.isEmpty())
1769                 functions["blendEquationSeparate"] = vec2jsvec(matInfo.blendEquations);
1770             if (!matInfo.blendArguments.isEmpty())
1771                 functions["blendFuncSeparate"] = vec2jsvec(matInfo.blendArguments);
1772             if (!functions.isEmpty())
1773                 commonMat["functions"] = functions;
1774             QJsonObject extensions;
1775             extensions["KHR_materials_common"] = commonMat;
1776             materialObj["extensions"] = extensions;
1777         }
1778 
1779         materials[matInfo.name] = materialObj;
1780     }
1781 }
1782 
exportGenericProperties(QJsonObject & jsonObj,PropertyCacheType type,QObject * obj)1783 void GLTFExporter::exportGenericProperties(QJsonObject &jsonObj, PropertyCacheType type,
1784                                            QObject *obj)
1785 {
1786     QVector<QMetaProperty> properties = m_propertyCache.value(type);
1787     QObject *defaultObject = m_defaultObjectCache.value(type);
1788     for (const QMetaProperty &property : properties) {
1789         // Only output property if it is different from default
1790         QVariant defaultValue = defaultObject->property(property.name());
1791         QVariant objectValue = obj->property(property.name());
1792         if (defaultValue != objectValue)
1793             setVarToJSonObject(jsonObj, QString::fromLatin1(property.name()), objectValue);
1794     }
1795 }
1796 
clearOldExport(const QString & dir)1797 void GLTFExporter::clearOldExport(const QString &dir)
1798 {
1799     // Look for .qrc file with same name
1800     QRegularExpression re(QStringLiteral("<file>(.*)</file>"));
1801     QFile qrcFile(dir + m_exportName + QStringLiteral(".qrc"));
1802     if (qrcFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
1803         while (!qrcFile.atEnd()) {
1804             QByteArray line = qrcFile.readLine();
1805             QRegularExpressionMatch match = re.match(line);
1806             if (match.hasMatch()) {
1807                 QString fileName = match.captured(1);
1808                 QString filePathName = dir + fileName;
1809                 QFile::remove(filePathName);
1810                 qCDebug(GLTFExporterLog, "Removed old file: '%ls'",
1811                         qUtf16PrintableImpl(filePathName));
1812             }
1813         }
1814         qrcFile.close();
1815         qrcFile.remove();
1816         qCDebug(GLTFExporterLog, "Removed old file: '%ls'",
1817                 qUtf16PrintableImpl(qrcFile.fileName()));
1818     }
1819 }
1820 
exportParameter(QJsonObject & jsonObj,const QString & name,const QVariant & variant)1821 void GLTFExporter::exportParameter(QJsonObject &jsonObj, const QString &name,
1822                                    const QVariant &variant)
1823 {
1824     QLatin1String typeStr("type");
1825     QLatin1String valueStr("value");
1826 
1827     QJsonObject paramObj;
1828 
1829     if (variant.canConvert<QAbstractTexture *>()) {
1830         paramObj[typeStr] = GL_SAMPLER_2D;
1831         paramObj[valueStr] = m_textureIdMap.value(textureVariantToUrl(variant));
1832     } else {
1833         switch (QMetaType::Type(variant.type())) {
1834         case QMetaType::Bool:
1835             paramObj[typeStr] = GL_BOOL;
1836             paramObj[valueStr] = variant.toBool();
1837             break;
1838         case QMetaType::Int: // fall through
1839         case QMetaType::Long: // fall through
1840         case QMetaType::LongLong:
1841             paramObj[typeStr] = GL_INT;
1842             paramObj[valueStr] = variant.toInt();
1843             break;
1844         case QMetaType::UInt: // fall through
1845         case QMetaType::ULong: // fall through
1846         case QMetaType::ULongLong:
1847             paramObj[typeStr] = GL_UNSIGNED_INT;
1848             paramObj[valueStr] = variant.toInt();
1849             break;
1850         case QMetaType::Short:
1851             paramObj[typeStr] = GL_SHORT;
1852             paramObj[valueStr] = variant.toInt();
1853             break;
1854         case QMetaType::UShort:
1855             paramObj[typeStr] = GL_UNSIGNED_SHORT;
1856             paramObj[valueStr] = variant.toInt();
1857             break;
1858         case QMetaType::Char:
1859             paramObj[typeStr] = GL_BYTE;
1860             paramObj[valueStr] = variant.toInt();
1861             break;
1862         case QMetaType::UChar:
1863             paramObj[typeStr] = GL_UNSIGNED_BYTE;
1864             paramObj[valueStr] = variant.toInt();
1865             break;
1866         case QMetaType::QColor:
1867             paramObj[typeStr] = GL_FLOAT_VEC4;
1868             paramObj[valueStr] = col2jsvec(variant.value<QColor>(), true);
1869             break;
1870         case QMetaType::Float:
1871             paramObj[typeStr] = GL_FLOAT;
1872             paramObj[valueStr] = variant.value<float>();
1873             break;
1874         case QMetaType::QVector2D:
1875             paramObj[typeStr] = GL_FLOAT_VEC2;
1876             paramObj[valueStr] = vec2jsvec(variant.value<QVector2D>());
1877             break;
1878         case QMetaType::QVector3D:
1879             paramObj[typeStr] = GL_FLOAT_VEC3;
1880             paramObj[valueStr] = vec2jsvec(variant.value<QVector3D>());
1881             break;
1882         case QMetaType::QVector4D:
1883             paramObj[typeStr] = GL_FLOAT_VEC4;
1884             paramObj[valueStr] = vec2jsvec(variant.value<QVector4D>());
1885             break;
1886         case QMetaType::QMatrix4x4:
1887             paramObj[typeStr] = GL_FLOAT_MAT4;
1888             paramObj[valueStr] = matrix2jsvec(variant.value<QMatrix4x4>());
1889             break;
1890         default:
1891             qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(name));
1892             break;
1893         }
1894     }
1895 
1896     jsonObj[name] = paramObj;
1897 }
1898 
exportRenderStates(QJsonObject & jsonObj,const QRenderPass * pass)1899 void GLTFExporter::exportRenderStates(QJsonObject &jsonObj, const QRenderPass *pass)
1900 {
1901     QJsonArray enableStates;
1902     QJsonObject funcObj;
1903     const auto renderStates = pass->renderStates();
1904     for (QRenderState *state : renderStates) {
1905         QJsonArray arr;
1906         if (qobject_cast<QAlphaCoverage *>(state)) {
1907             enableStates << GL_SAMPLE_ALPHA_TO_COVERAGE;
1908         } else if (qobject_cast<QAlphaTest *>(state)) {
1909             auto s = qobject_cast<QAlphaTest *>(state);
1910             arr << s->alphaFunction();
1911             arr << s->referenceValue();
1912             funcObj["alphaTest"] = arr;
1913         } else if (qobject_cast<QBlendEquation *>(state)) {
1914             auto s = qobject_cast<QBlendEquation *>(state);
1915             arr << s->blendFunction();
1916             funcObj["blendEquationSeparate"] = arr;
1917         } else if (qobject_cast<QBlendEquationArguments *>(state)) {
1918             auto s = qobject_cast<QBlendEquationArguments *>(state);
1919             arr << s->sourceRgb();
1920             arr << s->sourceAlpha();
1921             arr << s->destinationRgb();
1922             arr << s->destinationAlpha();
1923             arr << s->bufferIndex();
1924             funcObj["blendFuncSeparate"] = arr;
1925         } else if (qobject_cast<QClipPlane *>(state)) {
1926             auto s = qobject_cast<QClipPlane *>(state);
1927             arr << s->planeIndex();
1928             arr << s->normal().x();
1929             arr << s->normal().y();
1930             arr << s->normal().z();
1931             arr << s->distance();
1932             funcObj["clipPlane"] = arr;
1933         } else if (qobject_cast<QColorMask *>(state)) {
1934             auto s = qobject_cast<QColorMask *>(state);
1935             arr << s->isRedMasked();
1936             arr << s->isGreenMasked();
1937             arr << s->isBlueMasked();
1938             arr << s->isAlphaMasked();
1939             funcObj["colorMask"] = arr;
1940         } else if (qobject_cast<QCullFace *>(state)) {
1941             auto s = qobject_cast<QCullFace *>(state);
1942             arr << s->mode();
1943             funcObj["cullFace"] = arr;
1944         } else if (qobject_cast<QDepthRange *>(state)) {
1945             auto s = qobject_cast<QDepthRange *>(state);
1946             arr << s->nearValue();
1947             arr << s->farValue();
1948             funcObj["depthRange"] = arr;
1949         } else if (qobject_cast<QDepthTest *>(state)) {
1950             auto s = qobject_cast<QDepthTest *>(state);
1951             arr << s->depthFunction();
1952             funcObj["depthFunc"] = arr;
1953         } else if (qobject_cast<QDithering *>(state)) {
1954             enableStates << GL_DITHER;
1955         } else if (qobject_cast<QFrontFace *>(state)) {
1956             auto s = qobject_cast<QFrontFace *>(state);
1957             arr << s->direction();
1958             funcObj["frontFace"] = arr;
1959         } else if (qobject_cast<QFrontFace *>(state)) {
1960             auto s = qobject_cast<QFrontFace *>(state);
1961             arr << s->direction();
1962             funcObj["frontFace"] = arr;
1963         } else if (qobject_cast<QMultiSampleAntiAliasing *>(state)) {
1964             enableStates << 0x809D; // GL_MULTISAMPLE
1965         } else if (qobject_cast<QNoDepthMask *>(state)) {
1966             arr << false;
1967             funcObj["depthMask"] = arr;
1968         } else if (qobject_cast<QPointSize *>(state)) {
1969             auto s = qobject_cast<QPointSize *>(state);
1970             arr << s->sizeMode();
1971             arr << s->value();
1972             funcObj["pointSize"] = arr;
1973         } else if (qobject_cast<QPolygonOffset *>(state)) {
1974             auto s = qobject_cast<QPolygonOffset *>(state);
1975             arr << s->scaleFactor();
1976             arr << s->depthSteps();
1977             funcObj["polygonOffset"] = arr;
1978         } else if (qobject_cast<QScissorTest *>(state)) {
1979             auto s = qobject_cast<QScissorTest *>(state);
1980             arr << s->left();
1981             arr << s->bottom();
1982             arr << s->width();
1983             arr << s->height();
1984             funcObj["scissor"] = arr;
1985         } else if (qobject_cast<QSeamlessCubemap *>(state)) {
1986             enableStates << 0x884F; // GL_TEXTURE_CUBE_MAP_SEAMLESS
1987         } else if (qobject_cast<QStencilMask *>(state)) {
1988             auto s = qobject_cast<QStencilMask *>(state);
1989             arr << int(s->frontOutputMask());
1990             arr << int(s->backOutputMask());
1991             funcObj["stencilMask"] = arr;
1992         } else if (qobject_cast<QStencilOperation *>(state)) {
1993             auto s = qobject_cast<QStencilOperation *>(state);
1994             arr << s->front()->stencilTestFailureOperation();
1995             arr << s->front()->depthTestFailureOperation();
1996             arr << s->front()->allTestsPassOperation();
1997             arr << s->back()->stencilTestFailureOperation();
1998             arr << s->back()->depthTestFailureOperation();
1999             arr << s->back()->allTestsPassOperation();
2000             funcObj["stencilOperation"] = arr;
2001         } else if (qobject_cast<QStencilTest *>(state)) {
2002             auto s = qobject_cast<QStencilTest *>(state);
2003             arr << int(s->front()->comparisonMask());
2004             arr << s->front()->referenceValue();
2005             arr << s->front()->stencilFunction();
2006             arr << int(s->back()->comparisonMask());
2007             arr << s->back()->referenceValue();
2008             arr << s->back()->stencilFunction();
2009             funcObj["stencilTest"] = arr;
2010         }
2011     }
2012     if (!enableStates.isEmpty())
2013         jsonObj["enable"] = enableStates;
2014     if (!funcObj.isEmpty())
2015         jsonObj["functions"] = funcObj;
2016 }
2017 
newBufferViewName()2018 QString GLTFExporter::newBufferViewName()
2019 {
2020     return QString(QStringLiteral("bufferView_%1")).arg(++m_bufferViewCount);
2021 }
2022 
newAccessorName()2023 QString GLTFExporter::newAccessorName()
2024 {
2025     return QString(QStringLiteral("accessor_%1")).arg(++m_accessorCount);
2026 }
2027 
newMeshName()2028 QString GLTFExporter::newMeshName()
2029 {
2030     return QString(QStringLiteral("mesh_%1")).arg(++m_meshCount);
2031 }
2032 
newMaterialName()2033 QString GLTFExporter::newMaterialName()
2034 {
2035     return QString(QStringLiteral("material_%1")).arg(++m_materialCount);
2036 }
2037 
newTechniqueName()2038 QString GLTFExporter::newTechniqueName()
2039 {
2040     return QString(QStringLiteral("technique_%1")).arg(++m_techniqueCount);
2041 }
2042 
newTextureName()2043 QString GLTFExporter::newTextureName()
2044 {
2045     return QString(QStringLiteral("texture_%1")).arg(++m_textureCount);
2046 }
2047 
newImageName()2048 QString GLTFExporter::newImageName()
2049 {
2050     return QString(QStringLiteral("image_%1")).arg(++m_imageCount);
2051 }
2052 
newShaderName()2053 QString GLTFExporter::newShaderName()
2054 {
2055     return QString(QStringLiteral("shader_%1")).arg(++m_shaderCount);
2056 }
2057 
newProgramName()2058 QString GLTFExporter::newProgramName()
2059 {
2060     return QString(QStringLiteral("program_%1")).arg(++m_programCount);
2061 }
2062 
newNodeName()2063 QString GLTFExporter::newNodeName()
2064 {
2065     return QString(QStringLiteral("node_%1")).arg(++m_nodeCount);
2066 }
2067 
newCameraName()2068 QString GLTFExporter::newCameraName()
2069 {
2070     return QString(QStringLiteral("camera_%1")).arg(++m_cameraCount);
2071 }
2072 
newLightName()2073 QString GLTFExporter::newLightName()
2074 {
2075     return QString(QStringLiteral("light_%1")).arg(++m_lightCount);
2076 }
2077 
newRenderPassName()2078 QString GLTFExporter::newRenderPassName()
2079 {
2080     return QString(QStringLiteral("renderpass_%1")).arg(++m_renderPassCount);
2081 }
2082 
newEffectName()2083 QString GLTFExporter::newEffectName()
2084 {
2085     return QString(QStringLiteral("effect_%1")).arg(++m_effectCount);
2086 }
2087 
textureVariantToUrl(const QVariant & var)2088 QString GLTFExporter::textureVariantToUrl(const QVariant &var)
2089 {
2090     QString urlString;
2091     QAbstractTexture *texture = var.value<QAbstractTexture *>();
2092     if (texture->textureImages().size()) {
2093         QTextureImage *image = qobject_cast<QTextureImage *>(texture->textureImages().at(0));
2094         if (image) {
2095             urlString = QUrlHelper::urlToLocalFileOrQrc(image->source());
2096             if (!m_textureIdMap.contains(urlString))
2097                 m_textureIdMap.insert(urlString, newTextureName());
2098         }
2099     }
2100     return urlString;
2101 }
2102 
setVarToJSonObject(QJsonObject & jsObj,const QString & key,const QVariant & var)2103 void GLTFExporter::setVarToJSonObject(QJsonObject &jsObj, const QString &key, const QVariant &var)
2104 {
2105     switch (QMetaType::Type(var.type())) {
2106     case QMetaType::Bool:
2107         jsObj[key] = var.toBool();
2108         break;
2109     case QMetaType::Int:
2110         jsObj[key] = var.toInt();
2111         break;
2112     case QMetaType::Float:
2113         jsObj[key] = var.value<float>();
2114         break;
2115     case QMetaType::QSize:
2116         jsObj[key] = size2jsvec(var.toSize());
2117         break;
2118     case QMetaType::QVector2D:
2119         jsObj[key] = vec2jsvec(var.value<QVector2D>());
2120         break;
2121     case QMetaType::QVector3D:
2122         jsObj[key] = vec2jsvec(var.value<QVector3D>());
2123         break;
2124     case QMetaType::QVector4D:
2125         jsObj[key] = vec2jsvec(var.value<QVector4D>());
2126         break;
2127     case QMetaType::QMatrix4x4:
2128         jsObj[key] = matrix2jsvec(var.value<QMatrix4x4>());
2129         break;
2130     case QMetaType::QString:
2131         jsObj[key] = var.toString();
2132         break;
2133     case QMetaType::QColor:
2134         jsObj[key] = col2jsvec(var.value<QColor>(), true);
2135         break;
2136     default:
2137         qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(key));
2138         break;
2139     }
2140 }
2141 
2142 } // namespace Qt3DRender
2143 
2144 QT_END_NAMESPACE
2145 
2146 #include "moc_gltfexporter.cpp"
2147