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
125namespace {
126
127inline 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
136template <typename T>
137inline 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
145inline QJsonArray size2jsvec(const QSize &size) {
146 QJsonArray arr;
147 arr << size.width() << size.height();
148 return arr;
149}
150
151inline QJsonArray vec2jsvec(const QVector2D &v)
152{
153 QJsonArray arr;
154 arr << v.x() << v.y();
155 return arr;
156}
157
158inline QJsonArray vec2jsvec(const QVector3D &v)
159{
160 QJsonArray arr;
161 arr << v.x() << v.y() << v.z();
162 return arr;
163}
164
165inline 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
173inline 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
182inline 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
192inline 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(value: *mtxp++);
198 return jm;
199}
200
201inline 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(value: 1);
215 *it = arr;
216 }
217 }
218 ++it;
219 }
220}
221
222} // namespace
223
224QT_BEGIN_NAMESPACE
225
226using namespace Qt3DCore;
227using namespace Qt3DExtras;
228
229namespace Qt3DRender {
230
231Q_LOGGING_CATEGORY(GLTFExporterLog, "Qt3D.GLTFExport", QtWarningMsg)
232
233const QString MATERIAL_DIFFUSE_COLOR = QStringLiteral("kd");
234const QString MATERIAL_SPECULAR_COLOR = QStringLiteral("ks");
235const QString MATERIAL_AMBIENT_COLOR = QStringLiteral("ka");
236
237const QString MATERIAL_DIFFUSE_TEXTURE = QStringLiteral("diffuseTexture");
238const QString MATERIAL_SPECULAR_TEXTURE = QStringLiteral("specularTexture");
239const QString MATERIAL_NORMALS_TEXTURE = QStringLiteral("normalTexture");
240
241const QString MATERIAL_SHININESS = QStringLiteral("shininess");
242const QString MATERIAL_ALPHA = QStringLiteral("alpha");
243
244// Custom extension for Qt3D
245const QString MATERIAL_TEXTURE_SCALE = QStringLiteral("texCoordScale");
246
247// Custom gooch material values
248const QString MATERIAL_BETA = QStringLiteral("beta");
249const QString MATERIAL_COOL_COLOR = QStringLiteral("kblue");
250const QString MATERIAL_WARM_COLOR = QStringLiteral("kyellow");
251
252const QString VERTICES_ATTRIBUTE_NAME = QAttribute::defaultPositionAttributeName();
253const QString NORMAL_ATTRIBUTE_NAME = QAttribute::defaultNormalAttributeName();
254const QString TANGENT_ATTRIBUTE_NAME = QAttribute::defaultTangentAttributeName();
255const QString TEXTCOORD_ATTRIBUTE_NAME = QAttribute::defaultTextureCoordinateAttributeName();
256const QString COLOR_ATTRIBUTE_NAME = QAttribute::defaultColorAttributeName();
257
258GLTFExporter::GLTFExporter() : QSceneExporter()
259 , m_sceneRoot(nullptr)
260 , m_rootNode(nullptr)
261 , m_rootNodeEmpty(false)
262
263{
264}
265
266GLTFExporter::~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
307bool 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 adefaultValue: QVariant(false)).toBool();
327 m_gltfOpts.compactJson = options.value(QStringLiteral("compactJson"),
328 adefaultValue: QVariant(false)).toBool();
329
330 QFileInfo outDirFileInfo(outDir);
331 QString absoluteOutDir = outDirFileInfo.absoluteFilePath();
332 if (!absoluteOutDir.endsWith(c: QLatin1Char('/')))
333 absoluteOutDir.append(c: QLatin1Char('/'));
334 m_exportName = exportName;
335 m_sceneRoot = sceneRoot;
336 QString finalExportDir = absoluteOutDir + m_exportName;
337 if (!finalExportDir.endsWith(c: QLatin1Char('/')))
338 finalExportDir.append(c: 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(dirPath: 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(dirPath: 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(dir: 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(t&: 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(newName: 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(c: m_defaultObjectCache);
443 m_defaultObjectCache.clear();
444 m_propertyCache.clear();
445
446 delNode(n: m_rootNode);
447
448 return true;
449}
450
451void GLTFExporter::cacheDefaultProperties(GLTFExporter::PropertyCacheType type)
452{
453 if (m_defaultObjectCache.contains(akey: 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(akey: type, avalue: 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(asize: meta->propertyCount() - meta->propertyOffset());
488 for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
489 if (meta->property(index: i).isWritable())
490 properties.append(t: meta->property(index: i));
491 }
492
493 m_propertyCache.insert(akey: type, avalue: properties);
494}
495
496// Copies textures from original locations to the temporary export directory.
497// If texture names conflict, they are renamed.
498void 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(akey: absoluteFilePath)) {
510 // Texture has already been copied
511 qCDebug(GLTFExporterLog, " Skipped copying duplicate texture: '%ls'",
512 qUtf16PrintableImpl(absoluteFilePath));
513 if (!m_imageMap.contains(akey: texIt.key()))
514 m_imageMap.insert(akey: texIt.key(), avalue: copiedMap.value(akey: absoluteFilePath));
515 } else {
516 QString fileName = fi.fileName();
517 QString outFile = m_exportDir;
518 outFile.append(s: 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(a: base).arg(a: counter++).arg(a: suffix);
528 tryFile = m_exportDir;
529 tryFile.append(s: fileName);
530 fiTry.setFile(tryFile);
531 }
532 outFile = tryFile;
533 }
534 if (!QFile(absoluteFilePath).copy(newName: 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(akey: absoluteFilePath, avalue: fileName);
543 m_exportedFiles.insert(value: fileName);
544 m_imageMap.insert(akey: texIt.key(), avalue: fileName);
545 }
546 }
547}
548
549// Creates shaders to the temporary export directory.
550void GLTFExporter::createShaders()
551{
552 qCDebug(GLTFExporterLog, "Creating shaders...");
553 for (const auto &si : qAsConst(t&: m_shaderInfo)) {
554 const QString fileName = m_exportDir + si.uri;
555 QFile f(fileName);
556 if (f.open(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
557 m_exportedFiles.insert(value: QFileInfo(f.fileName()).fileName());
558 f.write(data: si.code);
559 f.close();
560 } else {
561 qCWarning(GLTFExporterLog, " Writing shaderfile '%ls' failed!",
562 qUtf16PrintableImpl(fileName));
563 }
564 }
565}
566
567
568void 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 *>(object: component))
579 m_meshMap.insert(akey: node, avalue: mesh);
580 else if (auto material = qobject_cast<QMaterial *>(object: component))
581 m_materialMap.insert(akey: node, avalue: material);
582 else if (auto transform = qobject_cast<Qt3DCore::QTransform *>(object: component))
583 m_transformMap.insert(akey: node, avalue: transform);
584 else if (auto camera = qobject_cast<QCameraLens *>(object: component))
585 m_cameraMap.insert(akey: node, avalue: camera);
586 else if (auto light = qobject_cast<QAbstractLight *>(object: component))
587 m_lightMap.insert(akey: node, avalue: 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(t: 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(entity: qobject_cast<QEntity *>(object: child), parentNode: node);
603 }
604}
605
606void GLTFExporter::parseScene()
607{
608 parseEntities(entity: m_sceneRoot, parentNode: nullptr);
609 parseMaterials();
610 parseMeshes();
611 parseCameras();
612 parseLights();
613}
614
615void 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 *>(object: material)) {
629 matInfo.type = MaterialInfo::TypePhong;
630 } else if (auto phongAlpha = qobject_cast<QPhongAlphaMaterial *>(object: material)) {
631 matInfo.type = MaterialInfo::TypePhongAlpha;
632 matInfo.blendArguments.resize(asize: 4);
633 matInfo.blendEquations.resize(asize: 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 *>(object: material)) {
641 matInfo.type = MaterialInfo::TypeDiffuseMap;
642 } else if (qobject_cast<QDiffuseSpecularMapMaterial *>(object: material)) {
643 matInfo.type = MaterialInfo::TypeDiffuseSpecularMap;
644 } else if (qobject_cast<QNormalDiffuseMapAlphaMaterial *>(object: material)) {
645 matInfo.values.insert(QStringLiteral("transparent"), avalue: QVariant(true));
646 matInfo.type = MaterialInfo::TypeNormalDiffuseMapAlpha;
647 } else if (qobject_cast<QNormalDiffuseMapMaterial *>(object: material)) {
648 matInfo.type = MaterialInfo::TypeNormalDiffuseMap;
649 } else if (qobject_cast<QNormalDiffuseSpecularMapMaterial *>(object: material)) {
650 matInfo.type = MaterialInfo::TypeNormalDiffuseSpecularMap;
651 } else if (qobject_cast<QGoochMaterial *>(object: material)) {
652 matInfo.type = MaterialInfo::TypeGooch;
653 } else if (qobject_cast<QPerVertexColorMaterial *>(object: 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(akey: material->effect()))
662 m_effectIdMap.insert(akey: material->effect(), avalue: 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"), avalue: color);
675 } else if (param->name() == MATERIAL_DIFFUSE_COLOR) {
676 if (matInfo.type == MaterialInfo::TypePhongAlpha) {
677 matInfo.values.insert(QStringLiteral("transparency"), avalue: float(color.alphaF()));
678 color.setAlphaF(1.0f);
679 }
680 matInfo.colors.insert(QStringLiteral("diffuse"), avalue: color);
681 } else if (param->name() == MATERIAL_SPECULAR_COLOR) {
682 matInfo.colors.insert(QStringLiteral("specular"), avalue: color);
683 } else if (param->name() == MATERIAL_COOL_COLOR) { // Custom Qt3D gooch
684 matInfo.colors.insert(QStringLiteral("cool"), avalue: color);
685 } else if (param->name() == MATERIAL_WARM_COLOR) { // Custom Qt3D gooch
686 matInfo.colors.insert(QStringLiteral("warm"), avalue: color);
687 } else {
688 matInfo.colors.insert(akey: param->name(), avalue: color);
689 }
690 } else if (param->value().canConvert<QAbstractTexture *>()) {
691 const QString urlString = textureVariantToUrl(var: param->value());
692 if (param->name() == MATERIAL_DIFFUSE_TEXTURE)
693 matInfo.textures.insert(QStringLiteral("diffuse"), avalue: urlString);
694 else if (param->name() == MATERIAL_SPECULAR_TEXTURE)
695 matInfo.textures.insert(QStringLiteral("specular"), avalue: urlString);
696 else if (param->name() == MATERIAL_NORMALS_TEXTURE)
697 matInfo.textures.insert(QStringLiteral("normal"), avalue: urlString);
698 else
699 matInfo.textures.insert(akey: param->name(), avalue: urlString);
700 } else if (param->name() == MATERIAL_SHININESS) {
701 matInfo.values.insert(QStringLiteral("shininess"), avalue: param->value());
702 } else if (param->name() == MATERIAL_BETA) { // Custom Qt3D param for gooch
703 matInfo.values.insert(QStringLiteral("beta"), avalue: param->value());
704 } else if (param->name() == MATERIAL_ALPHA) {
705 if (matInfo.type == MaterialInfo::TypeGooch)
706 matInfo.values.insert(QStringLiteral("alpha"), avalue: param->value());
707 else
708 matInfo.values.insert(QStringLiteral("transparency"), avalue: param->value());
709 } else if (param->name() == MATERIAL_TEXTURE_SCALE) { // Custom Qt3D param
710 matInfo.values.insert(QStringLiteral("textureScale"), avalue: 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(akey: material, avalue: matInfo);
732 materialCount++;
733 }
734}
735
736void 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(akey: m_materialMap.value(akey: node)).name;
749
750 if (qobject_cast<QConeMesh *>(object: mesh)) {
751 meshInfo.meshType = TypeConeMesh;
752 meshInfo.meshTypeStr = QStringLiteral("cone");
753 } else if (qobject_cast<QCuboidMesh *>(object: mesh)) {
754 meshInfo.meshType = TypeCuboidMesh;
755 meshInfo.meshTypeStr = QStringLiteral("cuboid");
756 } else if (qobject_cast<QCylinderMesh *>(object: mesh)) {
757 meshInfo.meshType = TypeCylinderMesh;
758 meshInfo.meshTypeStr = QStringLiteral("cylinder");
759 } else if (qobject_cast<QPlaneMesh *>(object: mesh)) {
760 meshInfo.meshType = TypePlaneMesh;
761 meshInfo.meshTypeStr = QStringLiteral("plane");
762 } else if (qobject_cast<QSphereMesh *>(object: mesh)) {
763 meshInfo.meshType = TypeSphereMesh;
764 meshInfo.meshTypeStr = QStringLiteral("sphere");
765 } else if (qobject_cast<QTorusMesh *>(object: 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(type: 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(asize: 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(i: 0).att->count();
858 vertexBuf.resize(size: 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(t: 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(size: 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(t: 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(t: 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(t: acc);
964 }
965 m_buffer.append(a: vertexBuf);
966 m_buffer.append(a: 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(t&: 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(akey: mesh, avalue: meshInfo);
991 }
992
993 qCDebug(GLTFExporterLog, "Total buffer size: %i", m_buffer.size());
994}
995
996void 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(degrees: 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(t: camera->left() - camera->right());
1015 c.ymag = qAbs(t: 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 *>(object: entities.at(i: 0));
1029 c.cameraEntity = cameraEntity;
1030
1031 m_cameraInfo.insert(akey: camera, avalue: 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
1043void 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 *>(object: 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 *>(object: 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 *>(object: 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(akey: light, avalue: 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
1094void 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(akey: technique)) {
1103 techName = m_techniqueIdMap.value(akey: technique);
1104 } else {
1105 techName = newTechniqueName();
1106 parseRenderPasses(technique);
1107
1108 }
1109 m_techniqueIdMap.insert(akey: technique, avalue: 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
1120void 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(akey: pass)) {
1129 name = m_renderPassIdMap.value(akey: pass);
1130 } else {
1131 name = newRenderPassName();
1132 m_renderPassIdMap.insert(akey: pass, avalue: name);
1133 if (pass->shaderProgram() && !m_programInfo.contains(akey: pass->shaderProgram())) {
1134 ProgramInfo pi;
1135 pi.name = newProgramName();
1136 pi.vertexShader = addShaderInfo(type: QShaderProgram::Vertex,
1137 code: pass->shaderProgram()->vertexShaderCode());
1138 pi.tessellationControlShader =
1139 addShaderInfo(type: QShaderProgram::Fragment,
1140 code: pass->shaderProgram()->tessellationControlShaderCode());
1141 pi.tessellationEvaluationShader =
1142 addShaderInfo(type: QShaderProgram::TessellationControl,
1143 code: pass->shaderProgram()->tessellationEvaluationShaderCode());
1144 pi.geometryShader = addShaderInfo(type: QShaderProgram::TessellationEvaluation,
1145 code: pass->shaderProgram()->geometryShaderCode());
1146 pi.fragmentShader = addShaderInfo(type: QShaderProgram::Geometry,
1147 code: pass->shaderProgram()->fragmentShaderCode());
1148 pi.computeShader = addShaderInfo(type: QShaderProgram::Compute,
1149 code: pass->shaderProgram()->computeShaderCode());
1150 m_programInfo.insert(akey: pass->shaderProgram(), avalue: 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
1163QString GLTFExporter::addShaderInfo(QShaderProgram::ShaderType type, QByteArray code)
1164{
1165 if (code.isEmpty())
1166 return QString();
1167
1168 for (const auto &si : qAsConst(t&: 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(t: newInfo);
1180
1181 qCDebug(GLTFExporterLog, " shader: '%ls'", qUtf16PrintableImpl(newInfo.name));
1182
1183 return newInfo.name;
1184}
1185
1186bool 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(a: 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(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
1214 qCDebug(GLTFExporterLog, " Writing '%ls'", qUtf16PrintableImpl(binFileName));
1215 m_exportedFiles.insert(value: fiBin.fileName());
1216 f.write(data: 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(t&: 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(t&: 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(jsonObj&: properties, type: meshInfo.meshType, obj: 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(value: 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(t&: 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(v: camInfo.cameraEntity->position());
1311 camera["upVector"] = vec2jsvec(v: camInfo.cameraEntity->upVector());
1312 camera["viewCenter"] = vec2jsvec(v: 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(t&: m_rootNode->children))
1326 sceneNodes << exportNodes(n: c, nodes);
1327 } else {
1328 sceneNodes << exportNodes(n: 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(v: 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(color: lightInfo.color, alpha: 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(jsonObj&: paramObj, name: param->name(), variant: 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(akey: 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(jsObj&: filterKeyObj, key: filterKey->name(), var: filterKey->value());
1435
1436 const auto techniqueRenderPasses = technique->renderPasses();
1437 for (QRenderPass *pass : techniqueRenderPasses)
1438 renderPassArr << m_renderPassIdMap.value(akey: pass);
1439
1440 const auto techniqueParameters = technique->parameters();
1441 for (QParameter *param : techniqueParameters)
1442 exportParameter(jsonObj&: paramObj, name: param->name(), variant: 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(jsObj&: filterKeyObj, key: filterKey->name(), var: filterKey->value());
1486 for (QParameter *param : pass->parameters())
1487 exportParameter(jsonObj&: paramObj, name: param->name(), variant: param->value());
1488 exportRenderStates(jsonObj&: 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(akey: 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(t&: 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(akey: 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(akey: 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(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
1590 m_exportedFiles.insert(value: QFileInfo(f.fileName()).fileName());
1591QT_WARNING_PUSH
1592QT_WARNING_DISABLE_DEPRECATED
1593 QByteArray json = m_doc.toBinaryData();
1594QT_WARNING_POP
1595 f.write(data: 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(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1604 m_exportedFiles.insert(value: QFileInfo(f.fileName()).fileName());
1605 QByteArray json = m_doc.toJson(format: m_gltfOpts.compactJson ? QJsonDocument::Compact
1606 : QJsonDocument::Indented);
1607 f.write(data: 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(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
1620 QByteArray pre = "<RCC><qresource prefix=\"/gltf_res\">\n";
1621 QByteArray post = "</qresource></RCC>\n";
1622 f.write(data: pre);
1623 for (const auto &file : qAsConst(t&: m_exportedFiles)) {
1624 QString line = QString(QStringLiteral(" <file>%1</file>\n")).arg(a: file);
1625 f.write(data: line.toUtf8());
1626 }
1627 f.write(data: post);
1628 f.close();
1629 m_exportedFiles.insert(value: 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
1641void GLTFExporter::delNode(GLTFExporter::Node *n)
1642{
1643 if (!n)
1644 return;
1645 for (auto *c : qAsConst(t&: n->children))
1646 delNode(n: c);
1647 delete n;
1648}
1649
1650QString GLTFExporter::exportNodes(GLTFExporter::Node *n, QJsonObject &nodes)
1651{
1652 QJsonObject node;
1653 node["name"] = n->name;
1654 QJsonArray children;
1655 for (auto c : qAsConst(t&: n->children))
1656 children << exportNodes(n: c, nodes);
1657 node["children"] = children;
1658 if (auto transform = m_transformMap.value(akey: n))
1659 node["matrix"] = matrix2jsvec(matrix: transform->matrix());
1660
1661 if (auto mesh = m_meshMap.value(akey: n)) {
1662 QJsonArray meshList;
1663 meshList.append(value: m_meshInfo.value(akey: mesh).name);
1664 node["meshes"] = meshList;
1665 }
1666
1667 if (auto camera = m_cameraMap.value(akey: n))
1668 node["camera"] = m_cameraInfo.value(akey: camera).name;
1669
1670 if (auto light = m_lightMap.value(akey: n)) {
1671 QJsonObject extensions;
1672 QJsonObject lights;
1673 lights["light"] = m_lightInfo.value(akey: 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
1682void 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(jsonObj&: paramObj, name: param->name(), variant: param->value());
1698 materialObj["effect"] = m_effectIdMap.value(akey: 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(akey: 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(rgb: 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(akey: it.value());
1734 }
1735 for (auto it = matInfo.values.constBegin(); it != matInfo.values.constEnd(); ++it) {
1736 if (vals.contains(key: it.key()))
1737 continue;
1738 setVarToJSonObject(jsObj&: vals, key: it.key(), var: it.value());
1739 }
1740 for (auto it = matInfo.colors.constBegin(); it != matInfo.colors.constEnd(); ++it) {
1741 if (vals.contains(key: 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(color: 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(obj: &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(v: matInfo.blendEquations);
1770 if (!matInfo.blendArguments.isEmpty())
1771 functions["blendFuncSeparate"] = vec2jsvec(v: 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
1783void GLTFExporter::exportGenericProperties(QJsonObject &jsonObj, PropertyCacheType type,
1784 QObject *obj)
1785{
1786 QVector<QMetaProperty> properties = m_propertyCache.value(akey: type);
1787 QObject *defaultObject = m_defaultObjectCache.value(akey: type);
1788 for (const QMetaProperty &property : properties) {
1789 // Only output property if it is different from default
1790 QVariant defaultValue = defaultObject->property(name: property.name());
1791 QVariant objectValue = obj->property(name: property.name());
1792 if (defaultValue != objectValue)
1793 setVarToJSonObject(jsObj&: jsonObj, key: QString::fromLatin1(str: property.name()), var: objectValue);
1794 }
1795}
1796
1797void 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(flags: QIODevice::ReadOnly | QIODevice::Text)) {
1803 while (!qrcFile.atEnd()) {
1804 QByteArray line = qrcFile.readLine();
1805 QRegularExpressionMatch match = re.match(subject: line);
1806 if (match.hasMatch()) {
1807 QString fileName = match.captured(nth: 1);
1808 QString filePathName = dir + fileName;
1809 QFile::remove(fileName: 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
1821void 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(akey: textureVariantToUrl(var: 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(color: variant.value<QColor>(), alpha: 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(v: variant.value<QVector2D>());
1877 break;
1878 case QMetaType::QVector3D:
1879 paramObj[typeStr] = GL_FLOAT_VEC3;
1880 paramObj[valueStr] = vec2jsvec(v: variant.value<QVector3D>());
1881 break;
1882 case QMetaType::QVector4D:
1883 paramObj[typeStr] = GL_FLOAT_VEC4;
1884 paramObj[valueStr] = vec2jsvec(v: variant.value<QVector4D>());
1885 break;
1886 case QMetaType::QMatrix4x4:
1887 paramObj[typeStr] = GL_FLOAT_MAT4;
1888 paramObj[valueStr] = matrix2jsvec(matrix: 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
1899void 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 *>(object: state)) {
1907 enableStates << GL_SAMPLE_ALPHA_TO_COVERAGE;
1908 } else if (qobject_cast<QAlphaTest *>(object: state)) {
1909 auto s = qobject_cast<QAlphaTest *>(object: state);
1910 arr << s->alphaFunction();
1911 arr << s->referenceValue();
1912 funcObj["alphaTest"] = arr;
1913 } else if (qobject_cast<QBlendEquation *>(object: state)) {
1914 auto s = qobject_cast<QBlendEquation *>(object: state);
1915 arr << s->blendFunction();
1916 funcObj["blendEquationSeparate"] = arr;
1917 } else if (qobject_cast<QBlendEquationArguments *>(object: state)) {
1918 auto s = qobject_cast<QBlendEquationArguments *>(object: 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 *>(object: state)) {
1926 auto s = qobject_cast<QClipPlane *>(object: 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 *>(object: state)) {
1934 auto s = qobject_cast<QColorMask *>(object: 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 *>(object: state)) {
1941 auto s = qobject_cast<QCullFace *>(object: state);
1942 arr << s->mode();
1943 funcObj["cullFace"] = arr;
1944 } else if (qobject_cast<QDepthRange *>(object: state)) {
1945 auto s = qobject_cast<QDepthRange *>(object: state);
1946 arr << s->nearValue();
1947 arr << s->farValue();
1948 funcObj["depthRange"] = arr;
1949 } else if (qobject_cast<QDepthTest *>(object: state)) {
1950 auto s = qobject_cast<QDepthTest *>(object: state);
1951 arr << s->depthFunction();
1952 funcObj["depthFunc"] = arr;
1953 } else if (qobject_cast<QDithering *>(object: state)) {
1954 enableStates << GL_DITHER;
1955 } else if (qobject_cast<QFrontFace *>(object: state)) {
1956 auto s = qobject_cast<QFrontFace *>(object: state);
1957 arr << s->direction();
1958 funcObj["frontFace"] = arr;
1959 } else if (qobject_cast<QFrontFace *>(object: state)) {
1960 auto s = qobject_cast<QFrontFace *>(object: state);
1961 arr << s->direction();
1962 funcObj["frontFace"] = arr;
1963 } else if (qobject_cast<QMultiSampleAntiAliasing *>(object: state)) {
1964 enableStates << 0x809D; // GL_MULTISAMPLE
1965 } else if (qobject_cast<QNoDepthMask *>(object: state)) {
1966 arr << false;
1967 funcObj["depthMask"] = arr;
1968 } else if (qobject_cast<QPointSize *>(object: state)) {
1969 auto s = qobject_cast<QPointSize *>(object: state);
1970 arr << s->sizeMode();
1971 arr << s->value();
1972 funcObj["pointSize"] = arr;
1973 } else if (qobject_cast<QPolygonOffset *>(object: state)) {
1974 auto s = qobject_cast<QPolygonOffset *>(object: state);
1975 arr << s->scaleFactor();
1976 arr << s->depthSteps();
1977 funcObj["polygonOffset"] = arr;
1978 } else if (qobject_cast<QScissorTest *>(object: state)) {
1979 auto s = qobject_cast<QScissorTest *>(object: 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 *>(object: state)) {
1986 enableStates << 0x884F; // GL_TEXTURE_CUBE_MAP_SEAMLESS
1987 } else if (qobject_cast<QStencilMask *>(object: state)) {
1988 auto s = qobject_cast<QStencilMask *>(object: state);
1989 arr << int(s->frontOutputMask());
1990 arr << int(s->backOutputMask());
1991 funcObj["stencilMask"] = arr;
1992 } else if (qobject_cast<QStencilOperation *>(object: state)) {
1993 auto s = qobject_cast<QStencilOperation *>(object: 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 *>(object: state)) {
2002 auto s = qobject_cast<QStencilTest *>(object: 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
2018QString GLTFExporter::newBufferViewName()
2019{
2020 return QString(QStringLiteral("bufferView_%1")).arg(a: ++m_bufferViewCount);
2021}
2022
2023QString GLTFExporter::newAccessorName()
2024{
2025 return QString(QStringLiteral("accessor_%1")).arg(a: ++m_accessorCount);
2026}
2027
2028QString GLTFExporter::newMeshName()
2029{
2030 return QString(QStringLiteral("mesh_%1")).arg(a: ++m_meshCount);
2031}
2032
2033QString GLTFExporter::newMaterialName()
2034{
2035 return QString(QStringLiteral("material_%1")).arg(a: ++m_materialCount);
2036}
2037
2038QString GLTFExporter::newTechniqueName()
2039{
2040 return QString(QStringLiteral("technique_%1")).arg(a: ++m_techniqueCount);
2041}
2042
2043QString GLTFExporter::newTextureName()
2044{
2045 return QString(QStringLiteral("texture_%1")).arg(a: ++m_textureCount);
2046}
2047
2048QString GLTFExporter::newImageName()
2049{
2050 return QString(QStringLiteral("image_%1")).arg(a: ++m_imageCount);
2051}
2052
2053QString GLTFExporter::newShaderName()
2054{
2055 return QString(QStringLiteral("shader_%1")).arg(a: ++m_shaderCount);
2056}
2057
2058QString GLTFExporter::newProgramName()
2059{
2060 return QString(QStringLiteral("program_%1")).arg(a: ++m_programCount);
2061}
2062
2063QString GLTFExporter::newNodeName()
2064{
2065 return QString(QStringLiteral("node_%1")).arg(a: ++m_nodeCount);
2066}
2067
2068QString GLTFExporter::newCameraName()
2069{
2070 return QString(QStringLiteral("camera_%1")).arg(a: ++m_cameraCount);
2071}
2072
2073QString GLTFExporter::newLightName()
2074{
2075 return QString(QStringLiteral("light_%1")).arg(a: ++m_lightCount);
2076}
2077
2078QString GLTFExporter::newRenderPassName()
2079{
2080 return QString(QStringLiteral("renderpass_%1")).arg(a: ++m_renderPassCount);
2081}
2082
2083QString GLTFExporter::newEffectName()
2084{
2085 return QString(QStringLiteral("effect_%1")).arg(a: ++m_effectCount);
2086}
2087
2088QString 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 *>(object: texture->textureImages().at(i: 0));
2094 if (image) {
2095 urlString = QUrlHelper::urlToLocalFileOrQrc(url: image->source());
2096 if (!m_textureIdMap.contains(akey: urlString))
2097 m_textureIdMap.insert(akey: urlString, avalue: newTextureName());
2098 }
2099 }
2100 return urlString;
2101}
2102
2103void 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(size: var.toSize());
2117 break;
2118 case QMetaType::QVector2D:
2119 jsObj[key] = vec2jsvec(v: var.value<QVector2D>());
2120 break;
2121 case QMetaType::QVector3D:
2122 jsObj[key] = vec2jsvec(v: var.value<QVector3D>());
2123 break;
2124 case QMetaType::QVector4D:
2125 jsObj[key] = vec2jsvec(v: var.value<QVector4D>());
2126 break;
2127 case QMetaType::QMatrix4x4:
2128 jsObj[key] = matrix2jsvec(matrix: var.value<QMatrix4x4>());
2129 break;
2130 case QMetaType::QString:
2131 jsObj[key] = var.toString();
2132 break;
2133 case QMetaType::QColor:
2134 jsObj[key] = col2jsvec(color: var.value<QColor>(), alpha: true);
2135 break;
2136 default:
2137 qCWarning(GLTFExporterLog, "Unknown value type for '%ls'", qUtf16PrintableImpl(key));
2138 break;
2139 }
2140}
2141
2142} // namespace Qt3DRender
2143
2144QT_END_NAMESPACE
2145
2146#include "moc_gltfexporter.cpp"
2147

source code of qt3d/src/plugins/sceneparsers/gltfexport/gltfexporter.cpp