1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "gltfexporter.h" |
41 | |
42 | #include <QtCore/qiodevice.h> |
43 | #include <QtCore/qfile.h> |
44 | #include <QtCore/qfileinfo.h> |
45 | #include <QtCore/qdir.h> |
46 | #include <QtCore/qhash.h> |
47 | #include <QtCore/qdebug.h> |
48 | #include <QtCore/qcoreapplication.h> |
49 | #include <QtCore/qjsondocument.h> |
50 | #include <QtCore/qjsonobject.h> |
51 | #include <QtCore/qjsonarray.h> |
52 | #include <QtCore/qmath.h> |
53 | #include <QtCore/qtemporarydir.h> |
54 | #include <QtCore/qregularexpression.h> |
55 | #include <QtCore/qmetaobject.h> |
56 | #include <QtGui/qvector2d.h> |
57 | #include <QtGui/qvector4d.h> |
58 | #include <QtGui/qmatrix4x4.h> |
59 | |
60 | #include <Qt3DCore/qentity.h> |
61 | #include <Qt3DCore/qtransform.h> |
62 | #include <Qt3DRender/qcameralens.h> |
63 | #include <Qt3DRender/qcamera.h> |
64 | #include <Qt3DRender/qblendequation.h> |
65 | #include <Qt3DRender/qblendequationarguments.h> |
66 | #include <Qt3DRender/qeffect.h> |
67 | #include <Qt3DRender/qattribute.h> |
68 | #include <Qt3DRender/qbuffer.h> |
69 | #include <Qt3DRender/qbufferdatagenerator.h> |
70 | #include <Qt3DRender/qmaterial.h> |
71 | #include <Qt3DRender/qgraphicsapifilter.h> |
72 | #include <Qt3DRender/qparameter.h> |
73 | #include <Qt3DRender/qtexture.h> |
74 | #include <Qt3DRender/qabstractlight.h> |
75 | #include <Qt3DRender/qpointlight.h> |
76 | #include <Qt3DRender/qspotlight.h> |
77 | #include <Qt3DRender/qdirectionallight.h> |
78 | #include <Qt3DRender/qgeometry.h> |
79 | #include <Qt3DRender/qgeometryrenderer.h> |
80 | #include <Qt3DRender/qgeometryfactory.h> |
81 | #include <Qt3DRender/qtechnique.h> |
82 | #include <Qt3DRender/qalphacoverage.h> |
83 | #include <Qt3DRender/qalphatest.h> |
84 | #include <Qt3DRender/qclipplane.h> |
85 | #include <Qt3DRender/qcolormask.h> |
86 | #include <Qt3DRender/qcullface.h> |
87 | #include <Qt3DRender/qdepthrange.h> |
88 | #include <Qt3DRender/qdepthtest.h> |
89 | #include <Qt3DRender/qdithering.h> |
90 | #include <Qt3DRender/qfrontface.h> |
91 | #include <Qt3DRender/qmultisampleantialiasing.h> |
92 | #include <Qt3DRender/qnodepthmask.h> |
93 | #include <Qt3DRender/qpointsize.h> |
94 | #include <Qt3DRender/qpolygonoffset.h> |
95 | #include <Qt3DRender/qscissortest.h> |
96 | #include <Qt3DRender/qseamlesscubemap.h> |
97 | #include <Qt3DRender/qstencilmask.h> |
98 | #include <Qt3DRender/qstenciloperation.h> |
99 | #include <Qt3DRender/qstenciloperationarguments.h> |
100 | #include <Qt3DRender/qstenciltest.h> |
101 | #include <Qt3DRender/qstenciltestarguments.h> |
102 | #include <Qt3DExtras/qconemesh.h> |
103 | #include <Qt3DExtras/qcuboidmesh.h> |
104 | #include <Qt3DExtras/qcylindermesh.h> |
105 | #include <Qt3DExtras/qplanemesh.h> |
106 | #include <Qt3DExtras/qspheremesh.h> |
107 | #include <Qt3DExtras/qtorusmesh.h> |
108 | #include <Qt3DExtras/qphongmaterial.h> |
109 | #include <Qt3DExtras/qphongalphamaterial.h> |
110 | #include <Qt3DExtras/qdiffusemapmaterial.h> |
111 | #include <Qt3DExtras/qdiffusespecularmapmaterial.h> |
112 | #include <Qt3DExtras/qnormaldiffusemapmaterial.h> |
113 | #include <Qt3DExtras/qnormaldiffusemapalphamaterial.h> |
114 | #include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h> |
115 | #include <Qt3DExtras/qgoochmaterial.h> |
116 | #include <Qt3DExtras/qpervertexcolormaterial.h> |
117 | |
118 | #include <private/qurlhelper_p.h> |
119 | |
120 | #ifndef qUtf16PrintableImpl |
121 | # define qUtf16PrintableImpl(string) \ |
122 | static_cast<const wchar_t*>(static_cast<const void*>(string.utf16())) |
123 | #endif |
124 | |
125 | namespace { |
126 | |
127 | inline QJsonArray col2jsvec(const QColor &color, bool alpha = false) |
128 | { |
129 | QJsonArray arr; |
130 | arr << color.redF() << color.greenF() << color.blueF(); |
131 | if (alpha) |
132 | arr << color.alphaF(); |
133 | return arr; |
134 | } |
135 | |
136 | template <typename T> |
137 | inline QJsonArray vec2jsvec(const QVector<T> &v) |
138 | { |
139 | QJsonArray arr; |
140 | for (int i = 0; i < v.count(); ++i) |
141 | arr << v.at(i); |
142 | return arr; |
143 | } |
144 | |
145 | inline QJsonArray size2jsvec(const QSize &size) { |
146 | QJsonArray arr; |
147 | arr << size.width() << size.height(); |
148 | return arr; |
149 | } |
150 | |
151 | inline QJsonArray vec2jsvec(const QVector2D &v) |
152 | { |
153 | QJsonArray arr; |
154 | arr << v.x() << v.y(); |
155 | return arr; |
156 | } |
157 | |
158 | inline QJsonArray vec2jsvec(const QVector3D &v) |
159 | { |
160 | QJsonArray arr; |
161 | arr << v.x() << v.y() << v.z(); |
162 | return arr; |
163 | } |
164 | |
165 | inline QJsonArray vec2jsvec(const QVector4D &v) |
166 | { |
167 | QJsonArray arr; |
168 | arr << v.x() << v.y() << v.z() << v.w(); |
169 | return arr; |
170 | } |
171 | |
172 | #if 0 // unused for now |
173 | inline QJsonArray matrix2jsvec(const QMatrix2x2 &matrix) |
174 | { |
175 | QJsonArray jm; |
176 | const float *mtxp = matrix.constData(); |
177 | for (int j = 0; j < 4; ++j) |
178 | jm.append(*mtxp++); |
179 | return jm; |
180 | } |
181 | |
182 | inline QJsonArray matrix2jsvec(const QMatrix3x3 &matrix) |
183 | { |
184 | QJsonArray jm; |
185 | const float *mtxp = matrix.constData(); |
186 | for (int j = 0; j < 9; ++j) |
187 | jm.append(*mtxp++); |
188 | return jm; |
189 | } |
190 | #endif |
191 | |
192 | inline QJsonArray matrix2jsvec(const QMatrix4x4 &matrix) |
193 | { |
194 | QJsonArray jm; |
195 | const float *mtxp = matrix.constData(); |
196 | for (int j = 0; j < 16; ++j) |
197 | jm.append(value: *mtxp++); |
198 | return jm; |
199 | } |
200 | |
201 | inline void promoteColorsToRGBA(QJsonObject *obj) |
202 | { |
203 | auto it = obj->begin(); |
204 | auto itEnd = obj->end(); |
205 | while (it != itEnd) { |
206 | QJsonArray arr = it.value().toArray(); |
207 | if (arr.count() == 3) { |
208 | const QString key = it.key(); |
209 | if (key == QStringLiteral("ambient" ) |
210 | || key == QStringLiteral("diffuse" ) |
211 | || key == QStringLiteral("specular" ) |
212 | || key == QStringLiteral("warm" ) |
213 | || key == QStringLiteral("cool" )) { |
214 | arr.append(value: 1); |
215 | *it = arr; |
216 | } |
217 | } |
218 | ++it; |
219 | } |
220 | } |
221 | |
222 | } // namespace |
223 | |
224 | QT_BEGIN_NAMESPACE |
225 | |
226 | using namespace Qt3DCore; |
227 | using namespace Qt3DExtras; |
228 | |
229 | namespace Qt3DRender { |
230 | |
231 | Q_LOGGING_CATEGORY(GLTFExporterLog, "Qt3D.GLTFExport" , QtWarningMsg) |
232 | |
233 | const QString MATERIAL_DIFFUSE_COLOR = QStringLiteral("kd" ); |
234 | const QString MATERIAL_SPECULAR_COLOR = QStringLiteral("ks" ); |
235 | const QString MATERIAL_AMBIENT_COLOR = QStringLiteral("ka" ); |
236 | |
237 | const QString MATERIAL_DIFFUSE_TEXTURE = QStringLiteral("diffuseTexture" ); |
238 | const QString MATERIAL_SPECULAR_TEXTURE = QStringLiteral("specularTexture" ); |
239 | const QString MATERIAL_NORMALS_TEXTURE = QStringLiteral("normalTexture" ); |
240 | |
241 | const QString MATERIAL_SHININESS = QStringLiteral("shininess" ); |
242 | const QString MATERIAL_ALPHA = QStringLiteral("alpha" ); |
243 | |
244 | // Custom extension for Qt3D |
245 | const QString MATERIAL_TEXTURE_SCALE = QStringLiteral("texCoordScale" ); |
246 | |
247 | // Custom gooch material values |
248 | const QString MATERIAL_BETA = QStringLiteral("beta" ); |
249 | const QString MATERIAL_COOL_COLOR = QStringLiteral("kblue" ); |
250 | const QString MATERIAL_WARM_COLOR = QStringLiteral("kyellow" ); |
251 | |
252 | const QString VERTICES_ATTRIBUTE_NAME = QAttribute::defaultPositionAttributeName(); |
253 | const QString NORMAL_ATTRIBUTE_NAME = QAttribute::defaultNormalAttributeName(); |
254 | const QString TANGENT_ATTRIBUTE_NAME = QAttribute::defaultTangentAttributeName(); |
255 | const QString TEXTCOORD_ATTRIBUTE_NAME = QAttribute::defaultTextureCoordinateAttributeName(); |
256 | const QString COLOR_ATTRIBUTE_NAME = QAttribute::defaultColorAttributeName(); |
257 | |
258 | GLTFExporter::GLTFExporter() : QSceneExporter() |
259 | , m_sceneRoot(nullptr) |
260 | , m_rootNode(nullptr) |
261 | , m_rootNodeEmpty(false) |
262 | |
263 | { |
264 | } |
265 | |
266 | GLTFExporter::~GLTFExporter() |
267 | { |
268 | } |
269 | |
270 | /*! |
271 | \class Qt3DRender::GLTFExporter |
272 | \inmodule Qt3DRender |
273 | \internal |
274 | \brief Manages the export of a 3D scene to the GLTF format. |
275 | |
276 | Handles the export of a 3D scene to the GLTF format. |
277 | */ |
278 | // sceneRoot : The root entity that contains the exported scene. If the sceneRoot doesn't have |
279 | // any exportable components, it is not exported itself. This is because importing a |
280 | // scene creates an empty top level entity to hold the scene. |
281 | // outDir : The directory where the scene export directory is created in. |
282 | // exportName : Name of the directory created in outDir to hold the exported scene. Also used as |
283 | // the file name base for generated files. |
284 | // options : Export options. |
285 | // |
286 | // Supported options are: |
287 | // "binaryJson" (bool): Generates a binary JSON file, which is more efficient to parse. |
288 | // "compactJson" (bool): Removes unnecessary whitespace from the generated JSON file. |
289 | // Ignored if "binaryJson" option is true. |
290 | |
291 | /*! |
292 | Exports the scene to the GLTF format |
293 | |
294 | \a sceneRoot is the root entity that will be exported. |
295 | If the sceneRoot does not have any exportable components, it is not exported itself. |
296 | |
297 | \a outDir is the directory in which the scene export is created. |
298 | |
299 | \a exportName is the name of the directory created in \c outDir that will hold |
300 | the exported scene. |
301 | |
302 | \a options contain the export options. |
303 | |
304 | Returns true if the export was carried out successfully. |
305 | */ |
306 | |
307 | bool GLTFExporter::exportScene(QEntity *sceneRoot, const QString &outDir, |
308 | const QString &exportName, const QVariantHash &options) |
309 | { |
310 | m_bufferViewCount = 0; |
311 | m_accessorCount = 0; |
312 | m_meshCount = 0; |
313 | m_materialCount = 0; |
314 | m_techniqueCount = 0; |
315 | m_textureCount = 0; |
316 | m_imageCount = 0; |
317 | m_shaderCount = 0; |
318 | m_programCount = 0; |
319 | m_nodeCount = 0; |
320 | m_cameraCount = 0; |
321 | m_lightCount = 0; |
322 | m_renderPassCount = 0; |
323 | m_effectCount = 0; |
324 | |
325 | m_gltfOpts.binaryJson = options.value(QStringLiteral("binaryJson" ), |
326 | 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 | |
451 | void 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. |
498 | void GLTFExporter::copyTextures() |
499 | { |
500 | qCDebug(GLTFExporterLog, "Copying textures..." ); |
501 | QHash<QString, QString> copiedMap; |
502 | for (auto texIt = m_textureIdMap.constBegin(); texIt != m_textureIdMap.constEnd(); ++texIt) { |
503 | QFileInfo fi(texIt.key()); |
504 | QString absoluteFilePath; |
505 | if (texIt.key().startsWith(QStringLiteral(":" ))) |
506 | absoluteFilePath = texIt.key(); |
507 | else |
508 | absoluteFilePath = fi.absoluteFilePath(); |
509 | if (copiedMap.contains(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. |
550 | void 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 | |
568 | void GLTFExporter::parseEntities(const QEntity *entity, Node *parentNode) |
569 | { |
570 | if (entity) { |
571 | Node *node = new Node; |
572 | node->name = entity->objectName(); |
573 | node->uniqueName = newNodeName(); |
574 | |
575 | int irrelevantComponents = 0; |
576 | const auto components = entity->components(); |
577 | for (auto component : components) { |
578 | if (auto mesh = qobject_cast<QGeometryRenderer *>(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 | |
606 | void GLTFExporter::parseScene() |
607 | { |
608 | parseEntities(entity: m_sceneRoot, parentNode: nullptr); |
609 | parseMaterials(); |
610 | parseMeshes(); |
611 | parseCameras(); |
612 | parseLights(); |
613 | } |
614 | |
615 | void GLTFExporter::parseMaterials() |
616 | { |
617 | qCDebug(GLTFExporterLog, "Parsing materials..." ); |
618 | |
619 | int materialCount = 0; |
620 | for (auto it = m_materialMap.constBegin(); it != m_materialMap.constEnd(); ++it) { |
621 | QMaterial *material = it.value(); |
622 | |
623 | MaterialInfo matInfo; |
624 | matInfo.name = newMaterialName(); |
625 | matInfo.originalName = material->objectName(); |
626 | |
627 | // Is material common or custom? |
628 | if (qobject_cast<QPhongMaterial *>(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 | |
736 | void GLTFExporter::parseMeshes() |
737 | { |
738 | qCDebug(GLTFExporterLog, "Parsing meshes..." ); |
739 | |
740 | int meshCount = 0; |
741 | for (auto it = m_meshMap.constBegin(); it != m_meshMap.constEnd(); ++it) { |
742 | Node *node = it.key(); |
743 | QGeometryRenderer *mesh = it.value(); |
744 | |
745 | MeshInfo meshInfo; |
746 | meshInfo.originalName = mesh->objectName(); |
747 | meshInfo.name = newMeshName(); |
748 | meshInfo.materialName = m_materialInfo.value(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 | |
996 | void GLTFExporter::parseCameras() |
997 | { |
998 | qCDebug(GLTFExporterLog, "Parsing cameras..." ); |
999 | int cameraCount = 0; |
1000 | |
1001 | for (auto it = m_cameraMap.constBegin(); it != m_cameraMap.constEnd(); ++it) { |
1002 | QCameraLens *camera = it.value(); |
1003 | CameraInfo c; |
1004 | |
1005 | if (camera->projectionType() == QCameraLens::PerspectiveProjection) { |
1006 | c.perspective = true; |
1007 | c.aspectRatio = camera->aspectRatio(); |
1008 | c.yfov = qDegreesToRadians(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 | |
1043 | void GLTFExporter::parseLights() |
1044 | { |
1045 | qCDebug(GLTFExporterLog, "Parsing lights..." ); |
1046 | int lightCount = 0; |
1047 | for (auto it = m_lightMap.constBegin(); it != m_lightMap.constEnd(); ++it) { |
1048 | QAbstractLight *light = it.value(); |
1049 | LightInfo lightInfo; |
1050 | lightInfo.direction = QVector3D(); |
1051 | lightInfo.attenuation = QVector3D(); |
1052 | lightInfo.cutOffAngle = 0.0f; |
1053 | lightInfo.type = light->type(); |
1054 | if (light->type() == QAbstractLight::SpotLight) { |
1055 | QSpotLight *spot = qobject_cast<QSpotLight *>(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 | |
1094 | void GLTFExporter::parseTechniques(QMaterial *material) |
1095 | { |
1096 | int techniqueCount = 0; |
1097 | qCDebug(GLTFExporterLog, " Parsing material techniques..." ); |
1098 | |
1099 | const auto techniques = material->effect()->techniques(); |
1100 | for (auto technique : techniques) { |
1101 | QString techName; |
1102 | if (m_techniqueIdMap.contains(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 | |
1120 | void GLTFExporter::parseRenderPasses(QTechnique *technique) |
1121 | { |
1122 | int passCount = 0; |
1123 | qCDebug(GLTFExporterLog, " Parsing render passes for technique..." ); |
1124 | |
1125 | const auto renderPasses = technique->renderPasses(); |
1126 | for (auto pass : renderPasses) { |
1127 | QString name; |
1128 | if (m_renderPassIdMap.contains(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 | |
1163 | QString 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 | |
1186 | bool GLTFExporter::saveScene() |
1187 | { |
1188 | qCDebug(GLTFExporterLog, "Saving scene..." ); |
1189 | |
1190 | QVector<MeshInfo::BufferView> bvList; |
1191 | QVector<MeshInfo::Accessor> accList; |
1192 | for (auto it = m_meshInfo.begin(); it != m_meshInfo.end(); ++it) { |
1193 | auto &mi = it.value(); |
1194 | for (auto &v : mi.views) |
1195 | bvList << v; |
1196 | for (auto &acc : mi.accessors) |
1197 | accList << acc; |
1198 | } |
1199 | |
1200 | m_obj = QJsonObject(); |
1201 | |
1202 | QJsonObject asset; |
1203 | asset["generator" ] = QString(QStringLiteral("GLTFExporter %1" )).arg(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()); |
1591 | QT_WARNING_PUSH |
1592 | QT_WARNING_DISABLE_DEPRECATED |
1593 | QByteArray json = m_doc.toBinaryData(); |
1594 | QT_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 | |
1641 | void 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 | |
1650 | QString GLTFExporter::exportNodes(GLTFExporter::Node *n, QJsonObject &nodes) |
1651 | { |
1652 | QJsonObject node; |
1653 | node["name" ] = n->name; |
1654 | QJsonArray children; |
1655 | for (auto c : qAsConst(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 | |
1682 | void GLTFExporter::exportMaterials(QJsonObject &materials) |
1683 | { |
1684 | QHash<QString, bool> imageHasAlpha; |
1685 | |
1686 | for (auto matIt = m_materialInfo.constBegin(); matIt != m_materialInfo.constEnd(); ++matIt) { |
1687 | const QMaterial *material = matIt.key(); |
1688 | const MaterialInfo &matInfo = matIt.value(); |
1689 | |
1690 | QJsonObject materialObj; |
1691 | materialObj["name" ] = matInfo.originalName; |
1692 | |
1693 | if (matInfo.type == MaterialInfo::TypeCustom) { |
1694 | QVector<QParameter *> parameters = material->parameters(); |
1695 | QJsonObject paramObj; |
1696 | for (auto param : parameters) |
1697 | exportParameter(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 | |
1783 | void 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 | |
1797 | void GLTFExporter::clearOldExport(const QString &dir) |
1798 | { |
1799 | // Look for .qrc file with same name |
1800 | QRegularExpression re(QStringLiteral("<file>(.*)</file>" )); |
1801 | QFile qrcFile(dir + m_exportName + QStringLiteral(".qrc" )); |
1802 | if (qrcFile.open(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 | |
1821 | void GLTFExporter::exportParameter(QJsonObject &jsonObj, const QString &name, |
1822 | const QVariant &variant) |
1823 | { |
1824 | QLatin1String typeStr("type" ); |
1825 | QLatin1String valueStr("value" ); |
1826 | |
1827 | QJsonObject paramObj; |
1828 | |
1829 | if (variant.canConvert<QAbstractTexture *>()) { |
1830 | paramObj[typeStr] = GL_SAMPLER_2D; |
1831 | paramObj[valueStr] = m_textureIdMap.value(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 | |
1899 | void GLTFExporter::exportRenderStates(QJsonObject &jsonObj, const QRenderPass *pass) |
1900 | { |
1901 | QJsonArray enableStates; |
1902 | QJsonObject funcObj; |
1903 | const auto renderStates = pass->renderStates(); |
1904 | for (QRenderState *state : renderStates) { |
1905 | QJsonArray arr; |
1906 | if (qobject_cast<QAlphaCoverage *>(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 | |
2018 | QString GLTFExporter::newBufferViewName() |
2019 | { |
2020 | return QString(QStringLiteral("bufferView_%1" )).arg(a: ++m_bufferViewCount); |
2021 | } |
2022 | |
2023 | QString GLTFExporter::newAccessorName() |
2024 | { |
2025 | return QString(QStringLiteral("accessor_%1" )).arg(a: ++m_accessorCount); |
2026 | } |
2027 | |
2028 | QString GLTFExporter::newMeshName() |
2029 | { |
2030 | return QString(QStringLiteral("mesh_%1" )).arg(a: ++m_meshCount); |
2031 | } |
2032 | |
2033 | QString GLTFExporter::newMaterialName() |
2034 | { |
2035 | return QString(QStringLiteral("material_%1" )).arg(a: ++m_materialCount); |
2036 | } |
2037 | |
2038 | QString GLTFExporter::newTechniqueName() |
2039 | { |
2040 | return QString(QStringLiteral("technique_%1" )).arg(a: ++m_techniqueCount); |
2041 | } |
2042 | |
2043 | QString GLTFExporter::newTextureName() |
2044 | { |
2045 | return QString(QStringLiteral("texture_%1" )).arg(a: ++m_textureCount); |
2046 | } |
2047 | |
2048 | QString GLTFExporter::newImageName() |
2049 | { |
2050 | return QString(QStringLiteral("image_%1" )).arg(a: ++m_imageCount); |
2051 | } |
2052 | |
2053 | QString GLTFExporter::newShaderName() |
2054 | { |
2055 | return QString(QStringLiteral("shader_%1" )).arg(a: ++m_shaderCount); |
2056 | } |
2057 | |
2058 | QString GLTFExporter::newProgramName() |
2059 | { |
2060 | return QString(QStringLiteral("program_%1" )).arg(a: ++m_programCount); |
2061 | } |
2062 | |
2063 | QString GLTFExporter::newNodeName() |
2064 | { |
2065 | return QString(QStringLiteral("node_%1" )).arg(a: ++m_nodeCount); |
2066 | } |
2067 | |
2068 | QString GLTFExporter::newCameraName() |
2069 | { |
2070 | return QString(QStringLiteral("camera_%1" )).arg(a: ++m_cameraCount); |
2071 | } |
2072 | |
2073 | QString GLTFExporter::newLightName() |
2074 | { |
2075 | return QString(QStringLiteral("light_%1" )).arg(a: ++m_lightCount); |
2076 | } |
2077 | |
2078 | QString GLTFExporter::newRenderPassName() |
2079 | { |
2080 | return QString(QStringLiteral("renderpass_%1" )).arg(a: ++m_renderPassCount); |
2081 | } |
2082 | |
2083 | QString GLTFExporter::newEffectName() |
2084 | { |
2085 | return QString(QStringLiteral("effect_%1" )).arg(a: ++m_effectCount); |
2086 | } |
2087 | |
2088 | QString GLTFExporter::textureVariantToUrl(const QVariant &var) |
2089 | { |
2090 | QString urlString; |
2091 | QAbstractTexture *texture = var.value<QAbstractTexture *>(); |
2092 | if (texture->textureImages().size()) { |
2093 | QTextureImage *image = qobject_cast<QTextureImage *>(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 | |
2103 | void GLTFExporter::setVarToJSonObject(QJsonObject &jsObj, const QString &key, const QVariant &var) |
2104 | { |
2105 | switch (QMetaType::Type(var.type())) { |
2106 | case QMetaType::Bool: |
2107 | jsObj[key] = var.toBool(); |
2108 | break; |
2109 | case QMetaType::Int: |
2110 | jsObj[key] = var.toInt(); |
2111 | break; |
2112 | case QMetaType::Float: |
2113 | jsObj[key] = var.value<float>(); |
2114 | break; |
2115 | case QMetaType::QSize: |
2116 | jsObj[key] = size2jsvec(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 | |
2144 | QT_END_NAMESPACE |
2145 | |
2146 | #include "moc_gltfexporter.cpp" |
2147 | |