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

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