| 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 |  |