Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgqmlutilities.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
8
9#include <QVector2D>
10#include <QVector3D>
11#include <QVector4D>
12#include <QQuaternion>
13#include <QDebug>
14#include <QRegularExpression>
15#include <QtCore/qdir.h>
16#include <QtCore/qfile.h>
17#include <QtCore/qbuffer.h>
18
19#include <QtGui/qimage.h>
20#include <QtGui/qimagereader.h>
21
22#include <QtQuick3DUtils/private/qssgmesh_p.h>
23#include <QtQuick3DUtils/private/qssgassert_p.h>
24
25#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
26
27#ifdef QT_QUICK3D_ENABLE_RT_ANIMATIONS
28#include <QtCore/QCborStreamWriter>
29#include <QtQuickTimeline/private/qquicktimeline_p.h>
30#endif // QT_QUICK3D_ENABLE_RT_ANIMATIONS
31
33
34using namespace Qt::StringLiterals;
35
37
39{
40public:
42
44
45 PropertiesMap propertiesForType(QSSGSceneDesc::Node::RuntimeType type);
46 QVariant getDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property);
47 bool isDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property, const QVariant &value);
48
49private:
50 PropertyMap();
51
52 QHash<QSSGSceneDesc::Node::RuntimeType, PropertiesMap> m_properties;
53
54};
55
56QString qmlComponentName(const QString &name) {
57 QString nameCopy = name;
58 if (nameCopy.isEmpty())
59 return QStringLiteral("Presentation");
60
61 nameCopy = sanitizeQmlId(nameCopy);
62
63 if (nameCopy[0].isLower())
64 nameCopy[0] = nameCopy[0].toUpper();
65
66 return nameCopy;
67}
68
69QString colorToQml(const QColor &color) {
70 QString colorString;
71 colorString = QLatin1Char('\"') + color.name(QColor::HexArgb) + QLatin1Char('\"');
72 return colorString;
73}
74
75QString variantToQml(const QVariant &variant) {
76 switch (variant.typeId()) {
77 case QMetaType::Float: {
78 auto value = variant.toDouble();
79 return QString::number(value);
80 }
81 case QMetaType::QVector2D: {
82 auto value = variant.value<QVector2D>();
83 return QString(QStringLiteral("Qt.vector2d(") + QString::number(double(value.x())) +
84 QStringLiteral(", ") + QString::number(double(value.y())) +
85 QStringLiteral(")"));
86 }
87 case QMetaType::QVector3D: {
88 auto value = variant.value<QVector3D>();
89 return QString(QStringLiteral("Qt.vector3d(") + QString::number(double(value.x())) +
90 QStringLiteral(", ") + QString::number(double(value.y())) +
91 QStringLiteral(", ") + QString::number(double(value.z())) +
92 QStringLiteral(")"));
93 }
94 case QMetaType::QVector4D: {
95 auto value = variant.value<QVector4D>();
96 return QString(QStringLiteral("Qt.vector4d(") + QString::number(double(value.x())) +
97 QStringLiteral(", ") + QString::number(double(value.y())) +
98 QStringLiteral(", ") + QString::number(double(value.z())) +
99 QStringLiteral(", ") + QString::number(double(value.w())) +
100 QStringLiteral(")"));
101 }
102 case QMetaType::QColor: {
103 auto value = variant.value<QColor>();
104 return colorToQml(value);
105 }
106 case QMetaType::QQuaternion: {
107 auto value = variant.value<QQuaternion>();
108 return QString(QStringLiteral("Qt.quaternion(") + QString::number(double(value.scalar())) +
109 QStringLiteral(", ") + QString::number(double(value.x())) +
110 QStringLiteral(", ") + QString::number(double(value.y())) +
111 QStringLiteral(", ") + QString::number(double(value.z())) +
112 QStringLiteral(")"));
113 }
114 default:
115 return variant.toString();
116 }
117}
118
119QString sanitizeQmlId(const QString &id)
120{
121 QString idCopy = id;
122 // If the id starts with a number...
123 if (!idCopy.isEmpty() && idCopy.at(0).isNumber())
124 idCopy.prepend(QStringLiteral("node"));
125
126 // sometimes first letter is a # (don't replace with underscore)
127 if (idCopy.startsWith(QChar::fromLatin1('#')))
128 idCopy.remove(0, 1);
129
130 // Replace all the characters other than ascii letters, numbers or underscore to underscores.
131 static QRegularExpression regExp(QStringLiteral("\\W"));
132 idCopy.replace(regExp, QStringLiteral("_"));
133
134 // first letter of id can not be upper case
135 // to make it look nicer, lower-case the initial run of all-upper-case characters
136 if (!idCopy.isEmpty() && idCopy[0].isUpper()) {
137
138 int i = 0;
139 int len = idCopy.length();
140 while (i < len && idCopy[i].isUpper()) {
141 idCopy[i] = idCopy[i].toLower();
142 ++i;
143 }
144 }
145
146 // ### qml keywords as names
147 static QSet<QByteArray> keywords {
148 "x",
149 "y",
150 "as",
151 "do",
152 "if",
153 "in",
154 "on",
155 "of",
156 "for",
157 "get",
158 "int",
159 "let",
160 "new",
161 "set",
162 "try",
163 "var",
164 "top",
165 "byte",
166 "case",
167 "char",
168 "else",
169 "num",
170 "from",
171 "goto",
172 "null",
173 "this",
174 "true",
175 "void",
176 "with",
177 "clip",
178 "item",
179 "flow",
180 "font",
181 "text",
182 "left",
183 "data",
184 "alias",
185 "break",
186 "state",
187 "scale",
188 "color",
189 "right",
190 "catch",
191 "class",
192 "const",
193 "false",
194 "float",
195 "layer", // Design Studio doesn't like "layer" as an id
196 "short",
197 "super",
198 "throw",
199 "while",
200 "yield",
201 "border",
202 "source",
203 "delete",
204 "double",
205 "export",
206 "import",
207 "native",
208 "public",
209 "pragma",
210 "return",
211 "signal",
212 "static",
213 "switch",
214 "throws",
215 "bottom",
216 "parent",
217 "typeof",
218 "boolean",
219 "opacity",
220 "enabled",
221 "anchors",
222 "padding",
223 "default",
224 "extends",
225 "finally",
226 "package",
227 "private",
228 "abstract",
229 "continue",
230 "debugger",
231 "function",
232 "property",
233 "readonly",
234 "children",
235 "volatile",
236 "interface",
237 "protected",
238 "transient",
239 "implements",
240 "instanceof",
241 "synchronized"
242 };
243 if (keywords.contains(idCopy.toUtf8())) {
244 idCopy += QStringLiteral("_");
245 }
246
247 // We may have removed all the characters by now
248 if (idCopy.isEmpty())
249 idCopy = QStringLiteral("node");
250
251 return idCopy;
252}
253
254QString sanitizeQmlSourcePath(const QString &source, bool removeParentDirectory)
255{
256 QString sourceCopy = source;
257
258 if (removeParentDirectory)
259 sourceCopy = QSSGQmlUtilities::stripParentDirectory(sourceCopy);
260
261 sourceCopy.replace(QChar::fromLatin1('\\'), QChar::fromLatin1('/'));
262
263 // must be surrounded in quotes
264 return QString(QStringLiteral("\"") + sourceCopy + QStringLiteral("\""));
265}
266
268{
269 static PropertyMap p;
270 return &p;
271}
272
277
278QVariant PropertyMap::getDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property)
279{
281
285 }
286
287 return value;
288}
289
290bool PropertyMap::isDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property, const QVariant &value)
291{
293 return isTheSame;
294}
295
297 PropertyMap::PropertiesMap propertiesMap;
298 auto metaObject = object->metaObject();
299 for (auto i = 0; i < metaObject->propertyCount(); ++i) {
300 auto property = metaObject->property(i);
301 const auto name = property.name();
302 const auto value = property.read(object);
303 propertiesMap.insert(name, value);
304 }
305 return propertiesMap;
306}
307
308PropertyMap::PropertyMap()
309{
310 // Create a table containing the default values for each property for each supported type
311 {
312 QQuick3DNode node;
313 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Node, getObjectPropertiesMap(&node));
314 }
315 {
316 QQuick3DPrincipledMaterial principledMaterial;
317 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::PrincipledMaterial, getObjectPropertiesMap(&principledMaterial));
318 }
319 {
320 QQuick3DSpecularGlossyMaterial specularGlossyMaterial;
321 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::SpecularGlossyMaterial, getObjectPropertiesMap(&specularGlossyMaterial));
322 }
323 {
324 QQuick3DCustomMaterial customMaterial;
325 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::CustomMaterial, getObjectPropertiesMap(&customMaterial));
326 }
327 {
328 QQuick3DTexture texture;
329 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Image2D, getObjectPropertiesMap(&texture));
330 }
331 {
332 QQuick3DCubeMapTexture cubeMapTexture;
333 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::ImageCube, getObjectPropertiesMap(&cubeMapTexture));
334 }
335 {
336 QQuick3DTextureData textureData;
337 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::TextureData, getObjectPropertiesMap(&textureData));
338 }
339 {
340 QQuick3DModel model;
341 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Model, getObjectPropertiesMap(&model));
342 }
343 {
344 QQuick3DOrthographicCamera orthographicCamera;
345 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::OrthographicCamera, getObjectPropertiesMap(&orthographicCamera));
346 }
347 {
348 QQuick3DPerspectiveCamera perspectiveCamera;
349 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::PerspectiveCamera, getObjectPropertiesMap(&perspectiveCamera));
350 }
351 {
352 QQuick3DDirectionalLight directionalLight;
353 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::DirectionalLight, getObjectPropertiesMap(&directionalLight));
354 }
355 {
356 QQuick3DPointLight pointLight;
357 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::PointLight, getObjectPropertiesMap(&pointLight));
358 }
359 {
360 QQuick3DSpotLight spotLight;
361 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::SpotLight, getObjectPropertiesMap(&spotLight));
362 }
363 {
364 QQuick3DSkeleton skeleton;
365 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Skeleton, getObjectPropertiesMap(&skeleton));
366 }
367 {
368 QQuick3DJoint joint;
369 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Joint, getObjectPropertiesMap(&joint));
370 }
371 {
372 QQuick3DSkin skin;
373 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::Skin, getObjectPropertiesMap(&skin));
374 }
375 {
376 QQuick3DMorphTarget morphTarget;
377 m_properties.insert(QSSGSceneDesc::Node::RuntimeType::MorphTarget, getObjectPropertiesMap(&morphTarget));
378 }
379}
380
398
399template<QSSGSceneDesc::Material::RuntimeType T>
400const char *qmlElementName() { static_assert(!std::is_same_v<decltype(T), decltype(T)>, "Unknown type"); return nullptr; }
401template<> const char *qmlElementName<QSSGSceneDesc::Node::RuntimeType::Node>() { return "Node"; }
402
403template<> const char *qmlElementName<QSSGSceneDesc::Material::RuntimeType::SpecularGlossyMaterial>() { return "SpecularGlossyMaterial"; }
404template<> const char *qmlElementName<QSSGSceneDesc::Material::RuntimeType::PrincipledMaterial>() { return "PrincipledMaterial"; }
405template<> const char *qmlElementName<QSSGSceneDesc::Material::RuntimeType::CustomMaterial>() { return "CustomMaterial"; }
406template<> const char *qmlElementName<QSSGSceneDesc::Material::RuntimeType::OrthographicCamera>() { return "OrthographicCamera"; }
407template<> const char *qmlElementName<QSSGSceneDesc::Material::RuntimeType::PerspectiveCamera>() { return "PerspectiveCamera"; }
408
409template<> const char *qmlElementName<QSSGSceneDesc::Node::RuntimeType::Model>() { return "Model"; }
410
411template<> const char *qmlElementName<QSSGSceneDesc::Texture::RuntimeType::Image2D>() { return "Texture"; }
412template<> const char *qmlElementName<QSSGSceneDesc::Texture::RuntimeType::ImageCube>() { return "CubeMapTexture"; }
413template<> const char *qmlElementName<QSSGSceneDesc::Texture::RuntimeType::TextureData>() { return "TextureData"; }
414
415template<> const char *qmlElementName<QSSGSceneDesc::Camera::RuntimeType::DirectionalLight>() { return "DirectionalLight"; }
416template<> const char *qmlElementName<QSSGSceneDesc::Camera::RuntimeType::SpotLight>() { return "SpotLight"; }
417template<> const char *qmlElementName<QSSGSceneDesc::Camera::RuntimeType::PointLight>() { return "PointLight"; }
418
419template<> const char *qmlElementName<QSSGSceneDesc::Joint::RuntimeType::Joint>() { return "Joint"; }
420template<> const char *qmlElementName<QSSGSceneDesc::Skeleton::RuntimeType::Skeleton>() { return "Skeleton"; }
421template<> const char *qmlElementName<QSSGSceneDesc::Node::RuntimeType::Skin>() { return "Skin"; }
422template<> const char *qmlElementName<QSSGSceneDesc::Node::RuntimeType::MorphTarget>() { return "MorphTarget"; }
423template<> const char *qmlElementName<QSSGSceneDesc::Node::RuntimeType::ModelInstance>() { return "FileInstancing"; }
424
425const char *getQmlElementName(const QSSGSceneDesc::Node &node)
426{
427 using RuntimeType = QSSGSceneDesc::Node::RuntimeType;
428 switch (node.runtimeType) {
429 case RuntimeType::Node:
430 return qmlElementName<RuntimeType::Node>();
431 case RuntimeType::PrincipledMaterial:
432 return qmlElementName<RuntimeType::PrincipledMaterial>();
433 case RuntimeType::SpecularGlossyMaterial:
434 return qmlElementName<RuntimeType::SpecularGlossyMaterial>();
435 case RuntimeType::CustomMaterial:
436 return qmlElementName<RuntimeType::CustomMaterial>();
437 case RuntimeType::Image2D:
438 return qmlElementName<RuntimeType::Image2D>();
439 case RuntimeType::ImageCube:
440 return qmlElementName<RuntimeType::ImageCube>();
441 case RuntimeType::TextureData:
442 return qmlElementName<RuntimeType::TextureData>();
443 case RuntimeType::Model:
444 return qmlElementName<RuntimeType::Model>();
445 case RuntimeType::OrthographicCamera:
446 return qmlElementName<RuntimeType::OrthographicCamera>();
447 case RuntimeType::PerspectiveCamera:
448 return qmlElementName<RuntimeType::PerspectiveCamera>();
449 case RuntimeType::DirectionalLight:
450 return qmlElementName<RuntimeType::DirectionalLight>();
451 case RuntimeType::PointLight:
452 return qmlElementName<RuntimeType::PointLight>();
453 case RuntimeType::SpotLight:
454 return qmlElementName<RuntimeType::SpotLight>();
455 case RuntimeType::Skeleton:
456 return qmlElementName<RuntimeType::Skeleton>();
457 case RuntimeType::Joint:
458 return qmlElementName<RuntimeType::Joint>();
459 case RuntimeType::Skin:
460 return qmlElementName<RuntimeType::Skin>();
461 case RuntimeType::MorphTarget:
462 return qmlElementName<RuntimeType::MorphTarget>();
463 case RuntimeType::ModelInstance:
464 return qmlElementName<RuntimeType::ModelInstance>();
465 default:
466 return "UNKNOWN_TYPE";
467 }
468}
469
493
494static constexpr QByteArrayView qml_basic_types[] {
495 "bool",
496 "double",
497 "int",
498 "list",
499 "real",
500 "string",
501 "url",
502 "var",
503 "color",
504 "date",
505 "font",
506 "matrix4x4",
507 "point",
508 "quaternion",
509 "rect",
510 "size",
511 "vector2d",
512 "vector3d",
513 "vector4d"
514};
515
516static_assert(std::size(qml_basic_types) == QMLBasicType::Unknown_Count, "Missing type?");
517
518static QByteArrayView typeName(QMetaType mt)
519{
520 switch (mt.id()) {
521 case QMetaType::Bool:
522 return qml_basic_types[QMLBasicType::Bool];
523 case QMetaType::Char:
524 case QMetaType::SChar:
525 case QMetaType::UChar:
526 case QMetaType::Char16:
527 case QMetaType::Char32:
528 case QMetaType::QChar:
529 case QMetaType::Short:
530 case QMetaType::UShort:
531 case QMetaType::Int:
532 case QMetaType::UInt:
533 case QMetaType::Long:
534 case QMetaType::ULong:
535 case QMetaType::LongLong:
536 case QMetaType::ULongLong:
537 return qml_basic_types[QMLBasicType::Int];
538 case QMetaType::Float:
539 case QMetaType::Double:
540 return qml_basic_types[QMLBasicType::Real];
541 case QMetaType::QByteArray:
542 case QMetaType::QString:
543 return qml_basic_types[QMLBasicType::String];
544 case QMetaType::QDate:
545 case QMetaType::QTime:
546 case QMetaType::QDateTime:
547 return qml_basic_types[QMLBasicType::Date];
548 case QMetaType::QUrl:
549 return qml_basic_types[QMLBasicType::Url];
550 case QMetaType::QRect:
551 case QMetaType::QRectF:
552 return qml_basic_types[QMLBasicType::Rect];
553 case QMetaType::QSize:
554 case QMetaType::QSizeF:
555 return qml_basic_types[QMLBasicType::Size];
556 case QMetaType::QPoint:
557 case QMetaType::QPointF:
558 return qml_basic_types[QMLBasicType::Point];
559 case QMetaType::QVariant:
560 return qml_basic_types[QMLBasicType::Var];
561 case QMetaType::QColor:
562 return qml_basic_types[QMLBasicType::Color];
563 case QMetaType::QMatrix4x4:
564 return qml_basic_types[QMLBasicType::Mat44];
565 case QMetaType::QVector2D:
566 return qml_basic_types[QMLBasicType::Vector2D];
567 case QMetaType::QVector3D:
568 return qml_basic_types[QMLBasicType::Vector3D];
569 case QMetaType::QVector4D:
570 return qml_basic_types[QMLBasicType::Vector4D];
571 case QMetaType::QQuaternion:
572 return qml_basic_types[QMLBasicType::Quaternion];
573 case QMetaType::QFont:
574 return qml_basic_types[QMLBasicType::Font];
575 default:
576 return qml_basic_types[QMLBasicType::Var];
577 }
578}
579
584// Normally g_idMap will contain all the ids but in some cases
585// (like Animation, not Node) the ids will just be stored
586// to avoid conflict.
587// Now, Animations will be processed after all the Nodes,
588// For Nodes, it is not used.
590Q_GLOBAL_STATIC(UniqueIdOthers, g_idOthers)
591
592// The id allocator is process-wide and g_nodeNameMap is keyed by node pointer,
593// so it has to start empty for every component that is written out. A tool can
594// write several in one process - balsam converts every positional argument,
595// the material editor exports repeatedly - and each new scene's nodes are
596// allocated at addresses the previous scene's freed nodes occupied, so a stale
597// entry would otherwise take a false hit and hand a new node the earlier id.
598static void resetIdAllocator()
599{
600 g_nodeNameMap->clear();
601 g_idMap->clear();
602 g_idOthers->clear();
603}
604
605static QString getIdForNode(const QSSGSceneDesc::Node &node)
606{
607 static constexpr const char *typeNames[] = {
608 "", // Transform
609 "_camera",
610 "", // Model
611 "_texture",
612 "_material",
613 "_light",
614 "_mesh",
615 "_skin",
616 "_skeleton",
617 "_joint",
618 "_morphtarget",
619 "_instancing",
620 "_unknown"
621 };
622 constexpr uint nameCount = sizeof(typeNames)/sizeof(const char*);
623 const bool nodeHasName = (node.name.size() > 0);
624 uint nameIdx = qMin(uint(node.nodeType), nameCount);
625 QString name = nodeHasName ? QString::fromUtf8(node.name + typeNames[nameIdx]) : QString::fromLatin1(getQmlElementName(node));
626 QString sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
627
628 // Make sure we return a unique id.
629 if (const auto it = g_nodeNameMap->constFind(&node); it != g_nodeNameMap->constEnd())
630 return *it;
631
632 quint64 id = node.id;
633 int attempts = 1000;
634 QString candidate = sanitizedName;
635 do {
636 if (const auto it = g_idMap->constFind(candidate); it == g_idMap->constEnd()) {
637 g_idMap->insert(candidate, &node);
638 g_nodeNameMap->insert(&node, candidate);
639 return candidate;
640 }
641
642 candidate = QStringLiteral("%1%2").arg(sanitizedName).arg(id++);
643 } while (--attempts);
644
645 return candidate;
646}
647
648static QString getIdForAnimation(const QByteArray &inName)
649{
650 QString name = !inName.isEmpty() ? QString::fromUtf8(inName + "_timeline") : "timeline0"_L1;
651 QString sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
652
653 int attempts = 1000;
654 quint16 id = 0;
655 QString candidate = sanitizedName;
656 do {
657 if (const auto it = g_idMap->constFind(candidate); it == g_idMap->constEnd()) {
658 if (const auto oIt = g_idOthers->constFind(candidate); oIt == g_idOthers->constEnd()) {
659 g_idOthers->insert(candidate);
660 return candidate;
661 }
662 }
663
664 candidate = QStringLiteral("%1%2").arg(sanitizedName).arg(++id);
665 } while (--attempts);
666
667 return candidate;
668}
669
670QString stripParentDirectory(const QString &filePath) {
671 QString sourceCopy = filePath;
672 while (sourceCopy.startsWith(QChar::fromLatin1('.')) || sourceCopy.startsWith(QChar::fromLatin1('/')) || sourceCopy.startsWith(QChar::fromLatin1('\\')))
673 sourceCopy.remove(0, 1);
674 return sourceCopy;
675}
676
677static const char *blockBegin() { return " {\n"; }
678static const char *blockEnd() { return "}\n"; }
679static const char *comment() { return "// "; }
680static const char *indent() { return " "; }
681
683{
684 enum : quint8 { QSSG_INDENT = 4 };
685 explicit QSSGQmlScopedIndent(OutputContext &out) : output(out) { out.indent += QSSG_INDENT; };
686 ~QSSGQmlScopedIndent() { output.indent = qMax(output.indent - QSSG_INDENT, 0); }
688};
689
690static QString indentString(OutputContext &output)
691{
692 QString str;
693 for (quint8 i = 0; i < output.indent; i += QSSGQmlScopedIndent::QSSG_INDENT)
694 str += QString::fromLatin1(indent());
695 return str;
696}
697
699{
700 for (quint8 i = 0; i < output.indent; i += QSSGQmlScopedIndent::QSSG_INDENT)
701 output.stream << indent();
702 return output.stream;
703}
704
705static const char *blockBegin(OutputContext &output)
706{
707 ++output.scopeDepth;
708 return blockBegin();
709}
710
711static const char *blockEnd(OutputContext &output)
712{
713 output.scopeDepth = qMax(0, output.scopeDepth - 1);
714 return blockEnd();
715}
716
717static void writeImportHeader(OutputContext &output, bool hasAnimation = false)
718{
719 output.stream << "import QtQuick\n"
720 << "import QtQuick3D\n\n";
721 if (hasAnimation)
722 output.stream << "import QtQuick.Timeline\n\n";
723}
724
725static QString toQuotedString(const QString &text) { return QStringLiteral("\"%1\"").arg(text); }
726
727static inline QString getMeshFolder() { return QStringLiteral("meshes/"); }
728static inline QString getMeshExtension() { return QStringLiteral(".mesh"); }
729
730QString getMeshSourceName(const QString &name)
731{
732 const auto meshFolder = getMeshFolder();
733 const auto extension = getMeshExtension();
734
735 return QString(meshFolder + name + extension);
736}
737
738static inline QString getTextureFolder() { return QStringLiteral("maps/"); }
739
740static inline QString getAnimationFolder() { return QStringLiteral("animations/"); }
741static inline QString getAnimationExtension() { return QStringLiteral(".qad"); }
742QString getAnimationSourceName(const QString &id, const QString &property, qsizetype index)
743{
744 const auto animationFolder = getAnimationFolder();
745 const auto extension = getAnimationExtension();
746 return QString(animationFolder + id + QStringLiteral("_")
747 + property + QStringLiteral("_")
748 + QString::number(index) + extension);
749}
750
751QString asString(const QVariant &var)
752{
753 return var.toString();
754}
755
756QString builtinQmlType(const QVariant &var)
757{
758 switch (var.metaType().id()) {
759 case QMetaType::QVector2D: {
760 const auto vec2 = qvariant_cast<QVector2D>(var);
761 return QLatin1String("Qt.vector2d(") + QString::number(vec2.x()) + QLatin1String(", ") + QString::number(vec2.y()) + QLatin1Char(')');
762 }
763 case QMetaType::QVector3D: {
764 const auto vec3 = qvariant_cast<QVector3D>(var);
765 return QLatin1String("Qt.vector3d(") + QString::number(vec3.x()) + QLatin1String(", ")
766 + QString::number(vec3.y()) + QLatin1String(", ")
767 + QString::number(vec3.z()) + QLatin1Char(')');
768 }
769 case QMetaType::QVector4D: {
770 const auto vec4 = qvariant_cast<QVector4D>(var);
771 return QLatin1String("Qt.vector4d(") + QString::number(vec4.x()) + QLatin1String(", ")
772 + QString::number(vec4.y()) + QLatin1String(", ")
773 + QString::number(vec4.z()) + QLatin1String(", ")
774 + QString::number(vec4.w()) + QLatin1Char(')');
775 }
776 case QMetaType::QColor: {
777 const auto color = qvariant_cast<QColor>(var);
778 return colorToQml(color);
779 }
780 case QMetaType::QQuaternion: {
781 const auto &quat = qvariant_cast<QQuaternion>(var);
782 return QLatin1String("Qt.quaternion(") + QString::number(quat.scalar()) + QLatin1String(", ")
783 + QString::number(quat.x()) + QLatin1String(", ")
784 + QString::number(quat.y()) + QLatin1String(", ")
785 + QString::number(quat.z()) + QLatin1Char(')');
786 }
787 case QMetaType::QMatrix4x4: {
788 const auto mat44 = qvariant_cast<QMatrix4x4>(var);
789 return QLatin1String("Qt.matrix4x4(")
790 + QString::number(mat44(0, 0)) + u", " + QString::number(mat44(0, 1)) + u", " + QString::number(mat44(0, 2)) + u", " + QString::number(mat44(0, 3)) + u", "
791 + QString::number(mat44(1, 0)) + u", " + QString::number(mat44(1, 1)) + u", " + QString::number(mat44(1, 2)) + u", " + QString::number(mat44(1, 3)) + u", "
792 + QString::number(mat44(2, 0)) + u", " + QString::number(mat44(2, 1)) + u", " + QString::number(mat44(2, 2)) + u", " + QString::number(mat44(2, 3)) + u", "
793 + QString::number(mat44(3, 0)) + u", " + QString::number(mat44(3, 1)) + u", " + QString::number(mat44(3, 2)) + u", " + QString::number(mat44(3, 3)) + u')';
794 }
795 case QMetaType::Float:
796 case QMetaType::Double:
797 case QMetaType::Int:
798 case QMetaType::Char:
799 case QMetaType::Long:
800 case QMetaType::LongLong:
801 case QMetaType::ULong:
802 case QMetaType::ULongLong:
803 case QMetaType::Bool:
804 return var.toString();
805 case QMetaType::QUrl: // QUrl needs special handling. Return empty string to trigger that.
806 default:
807 break;
808 }
809
810 return QString();
811}
812
814{
816 return QStringLiteral("position");
818 return QStringLiteral("rotation");
820 return QStringLiteral("scale");
822 return QStringLiteral("weight");
823
824 return QStringLiteral("unknown");
825}
826
827static std::pair<QString, QString> meshAssetName(const QSSGSceneDesc::Scene &scene, const QSSGSceneDesc::Mesh &meshNode, const QDir &outdir)
828{
829 // Returns {name, notValidReason}
830
831 const auto meshFolder = getMeshFolder();
832 const auto meshId = QSSGQmlUtilities::getIdForNode(meshNode);
833 const auto meshSourceName = QSSGQmlUtilities::getMeshSourceName(meshId);
834 Q_ASSERT(scene.meshStorage.size() > meshNode.idx);
835 const auto &mesh = scene.meshStorage.at(meshNode.idx);
836
837 // If a mesh folder does not exist, then create one
838 if (!outdir.exists(meshFolder) && !outdir.mkdir(meshFolder)) {
839 qDebug() << "Failed to create meshes folder at" << outdir;
840 return {}; // Error out
841 }
842
843 const QString path = outdir.path() + QDir::separator() + meshSourceName;
844 QFile file(path);
845 if (!file.open(QIODevice::WriteOnly)) {
846 return {QString(), QStringLiteral("Failed to find mesh at ") + path};
847 }
848
849 if (mesh.save(&file) == 0) {
850 return {};
851 }
852
853 return {meshSourceName, QString()};
854};
855
856static std::pair<QString, QString> copyTextureAsset(const QUrl &texturePath, OutputContext &output)
857{
858 // Returns {path, notValidReason}
859
860 // TODO: Use QUrl::resolved() instead of manual string manipulation
861 QString assetPath = output.outdir.isAbsolutePath(texturePath.path()) ? texturePath.toString() : texturePath.path();
862 QFileInfo fi(assetPath);
863 if (fi.isRelative() && !output.sourceDir.isEmpty()) {
864 fi = QFileInfo(output.sourceDir + QChar(u'/') + assetPath);
865 }
866 if (!fi.exists()) {
867 indent(output) << comment() << "Source texture path expected: " << getTextureFolder() + texturePath.fileName() << "\n";
868 return {QString(), QStringLiteral("Failed to find texture at ") + assetPath};
869 }
870
871 const auto mapsFolder = getTextureFolder();
872 // If a maps folder does not exist, then create one
873 if (!output.outdir.exists(mapsFolder) && !output.outdir.mkdir(mapsFolder)) {
874 qDebug() << "Failed to create maps folder at" << output.outdir;
875 return {}; // Error out
876 }
877
878 const QString relpath = mapsFolder + fi.fileName();
879 const auto newfilepath = QString(output.outdir.canonicalPath() + QDir::separator() + relpath);
880 if (!QFile::exists(newfilepath) && !QFile::copy(fi.canonicalFilePath(), newfilepath)) {
881 qDebug() << "Failed to copy file from" << fi.canonicalFilePath() << "to" << newfilepath;
882 return {};
883 }
884
885 return {relpath, QString()};
886};
887
888static QStringList expandComponents(const QString &value, QMetaType mt)
889{
890 static const QRegularExpression re(QLatin1String("^Qt.[a-z0-9]*\\‍(([0-9.e\\+\\-, ]*)\\‍)"));
891 Q_ASSERT(re.isValid());
892
893 switch (mt.id()) {
894 case QMetaType::QVector2D: {
895 QRegularExpressionMatch match = re.match(value);
896 if (match.hasMatch()) {
897 const auto comp = match.captured(1).split(QLatin1Char(','));
898 if (comp.size() == 2) {
899 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
900 QLatin1String(".y: ") + comp.at(1).trimmed() };
901 }
902 }
903 break;
904 }
905 case QMetaType::QVector3D: {
906 QRegularExpressionMatch match = re.match(value);
907 if (match.hasMatch()) {
908 const auto comp = match.captured(1).split(QLatin1Char(','));
909 if (comp.size() == 3) {
910 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
911 QLatin1String(".y: ") + comp.at(1).trimmed(),
912 QLatin1String(".z: ") + comp.at(2).trimmed() };
913 }
914 }
915 break;
916 }
917 case QMetaType::QVector4D: {
918 QRegularExpressionMatch match = re.match(value);
919 if (match.hasMatch()) {
920 const auto comp = match.captured(1).split(QLatin1Char(','));
921 if (comp.size() == 4) {
922 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
923 QLatin1String(".y: ") + comp.at(1).trimmed(),
924 QLatin1String(".z: ") + comp.at(2).trimmed(),
925 QLatin1String(".w: ") + comp.at(3).trimmed() };
926 }
927 }
928 break;
929 }
930 case QMetaType::QQuaternion: {
931 QRegularExpressionMatch match = re.match(value);
932 if (match.hasMatch()) {
933 const auto comp = match.captured(1).split(QLatin1Char(','));
934 if (comp.size() == 4) {
935 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
936 QLatin1String(".y: ") + comp.at(1).trimmed(),
937 QLatin1String(".z: ") + comp.at(2).trimmed(),
938 QLatin1String(".scalar: ") + comp.at(3).trimmed() };
939 }
940 }
941 break;
942 }
943 default:
944 break;
945 }
946
947 return { value };
948}
949
950static QStringList expandComponentsPartially(const QString &value, QMetaType mt)
951{
952 // Workaround for DS
953 if (mt.id() != QMetaType::QQuaternion)
954 return expandComponents(value, mt);
955
956 return { value };
957}
958
960 bool ok = false;
961 QString name;
962 QString value;
964 bool isDynamicProperty = false;
966};
967
968static ValueToQmlResult valueToQml(const QSSGSceneDesc::Node &target, const QSSGSceneDesc::Property &property, OutputContext &output)
969{
970 ValueToQmlResult result;
971 if (property.value.isNull()) {
972 result.ok = false;
973 result.notValidReason = QStringLiteral("Property value is null");
974 return result;
975 }
976
977 const QVariant &value = property.value;
978 result.name = QString::fromUtf8(property.name);
980
981 // Built-in types
982 QString valueAsString = builtinQmlType(value);
983 if (valueAsString.size() > 0) {
984 result.value = valueAsString;
985 result.ok = true;
986 } else if (value.metaType().flags() & (QMetaType::IsEnumeration | QMetaType::IsUnsignedEnumeration)) {
987 static const auto qmlEnumString = [](const QLatin1String &element, const QString &enumString) {
988 return QStringLiteral("%1.%2").arg(element).arg(enumString);
989 };
990 QLatin1String qmlElementName(getQmlElementName(target));
991 QString enumValue = asString(value);
992 if (enumValue.size() > 0) {
993 result.value = qmlEnumString(qmlElementName, enumValue);
994 result.ok = true;
995 }
996 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Flag>()) {
997 QByteArray element(getQmlElementName(target));
998 if (element.size() > 0) {
999 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(value);
1000 QByteArray keysString = flag.me.valueToKeys(int(flag.value));
1001 if (keysString.size() > 0) {
1002 keysString.prepend(element + '.');
1003 QByteArray replacement(" | " + element + '.');
1004 keysString.replace('|', replacement);
1005 result.value = QString::fromLatin1(keysString);
1006 result.ok = true;
1007 }
1008 }
1009 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::NodeList *>()) {
1010 const auto *list = qvariant_cast<QSSGSceneDesc::NodeList *>(value);
1011 if (list->count > 0) {
1012 const QString indentStr = indentString(output);
1013 QSSGQmlScopedIndent scopedIndent(output);
1014 const QString listIndentStr = indentString(output);
1015
1016 QString str;
1017 str.append(u"[\n");
1018
1019 for (int i = 0, end = list->count; i != end; ++i) {
1020 if (i != 0)
1021 str.append(u",\n");
1022 str.append(listIndentStr);
1023 str.append(getIdForNode(*(list->head[i])));
1024 }
1025
1026 str.append(u'\n' + indentStr + u']');
1027
1028 result.value = str;
1029 result.ok = true;
1030 }
1031 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::ListView *>()) {
1032 const auto &list = *qvariant_cast<QSSGSceneDesc::ListView *>(value);
1033 if (list.count > 0) {
1034 const QString indentStr = indentString(output);
1035 QSSGQmlScopedIndent scopedIndent(output);
1036 const QString listIndentStr = indentString(output);
1037
1038 QString str;
1039 str.append(u"[\n");
1040
1041 char *vptr = reinterpret_cast<char *>(list.data);
1042 auto size = list.mt.sizeOf();
1043
1044 for (int i = 0, end = list.count; i != end; ++i) {
1045 if (i != 0)
1046 str.append(u",\n");
1047
1048 const QVariant var{list.mt, reinterpret_cast<void *>(vptr + (size * i))};
1049 QString valueString = builtinQmlType(var);
1050 if (valueString.isEmpty())
1051 valueString = asString(var);
1052
1053 str.append(listIndentStr);
1054 str.append(valueString);
1055 }
1056
1057 str.append(u'\n' + indentStr + u']');
1058
1059 result.value = str;
1060 result.ok = true;
1061 }
1062 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Node *>()) {
1063 if (const auto node = qvariant_cast<QSSGSceneDesc::Node *>(value)) {
1064 // If this assert is triggerd it likely means that the node never got added
1065 // to the scene tree (see: addNode()) or that it's a type not handled as a resource, see:
1066 // writeQmlForResources()
1067 Q_ASSERT(node->id != 0);
1068 // The 'TextureData' node will have its data written out and become
1069 // a source url.
1070
1071 if (node->runtimeType == QSSGSceneDesc::Node::RuntimeType::TextureData) {
1072 result.name = QStringLiteral("source");
1073 result.value = getIdForNode(*node->scene->root) + QLatin1Char('.') + getIdForNode(*node);
1074 } else {
1075 result.value = getIdForNode(*node);
1076 }
1077 result.ok = true;
1078 }
1079 } else if (value.metaType() == QMetaType::fromType<QSSGSceneDesc::Mesh *>()) {
1080 if (const auto meshNode = qvariant_cast<const QSSGSceneDesc::Mesh *>(value)) {
1081 Q_ASSERT(meshNode->nodeType == QSSGSceneDesc::Node::Type::Mesh);
1082 Q_ASSERT(meshNode->scene);
1083 const auto &scene = *meshNode->scene;
1084 const auto& [meshSourceName, notValidReason] = meshAssetName(scene, *meshNode, output.outdir);
1085 result.notValidReason = notValidReason;
1086 if (!meshSourceName.isEmpty()) {
1087 result.value = toQuotedString(meshSourceName);
1088 result.ok = true;
1089 }
1090 }
1091 } else if (value.metaType() == QMetaType::fromType<QUrl>()) {
1092 if (const auto url = qvariant_cast<QUrl>(value); !url.isEmpty()) {
1093 // We need to adjust source url(s) as those should contain the canonical path
1094 QString path;
1095 if (QSSGRenderGraphObject::isTexture(target.runtimeType)) {
1096 const auto& [relpath, notValidReason] = copyTextureAsset(url, output);
1097 result.notValidReason = notValidReason;
1098 if (!relpath.isEmpty()) {
1099 path = relpath;
1100 }
1101 } else
1102 path = url.path();
1103
1104 if (!path.isEmpty()) {
1105 result.value = toQuotedString(path);
1106 result.ok = true;
1107 }
1108 }
1109 } else if (target.runtimeType == QSSGSceneDesc::Material::RuntimeType::CustomMaterial) {
1110 // Workaround the TextureInput item that wraps textures for the Custom material.
1111 if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Texture *>()) {
1112 if (const auto texture = qvariant_cast<QSSGSceneDesc::Texture *>(value)) {
1113 Q_ASSERT(QSSGRenderGraphObject::isTexture(texture->runtimeType));
1114 result.value = QLatin1String("TextureInput { texture: ") +
1115 getIdForNode(*texture) + QLatin1String(" }");
1116 result.ok = true;
1117 }
1118 }
1119 } else if (value.metaType() == QMetaType::fromType<QString>()) {
1120 // Plain strings in the scenedesc should map to QML string values
1121 result.value = toQuotedString(value.toString());
1122 result.ok = true;
1123 } else {
1124 result.notValidReason = QStringLiteral("Unsupported value type: ") + QString::fromUtf8(value.metaType().name());
1125 qWarning() << result.notValidReason;
1126 result.ok = false;
1127 }
1128
1129 if (result.ok && (output.options & OutputContext::Options::ExpandValueComponents)) {
1130 result.expandedProperties = ((output.options & OutputContext::Options::DesignStudioWorkarounds) == OutputContext::Options::DesignStudioWorkarounds)
1131 ? expandComponentsPartially(result.value, value.metaType())
1132 : expandComponents(result.value, value.metaType());
1133 }
1134
1135 return result;
1136}
1137
1138static void writeNodeProperties(const QSSGSceneDesc::Node &node, OutputContext &output,
1139 const char *skipProperty = nullptr)
1140{
1141 QSSGQmlScopedIndent scopedIndent(output);
1142
1143 indent(output) << u"id: "_s << getIdForNode(node) << u'\n';
1144
1145 // Set Object Name if one exists
1146 if (node.name.size()) {
1147 const QString objectName = QString::fromLocal8Bit(node.name);
1148 if (!objectName.startsWith(u'*'))
1149 indent(output) << u"objectName: \""_s << node.name << u"\"\n"_s;
1150 }
1151
1152 const auto &properties = node.properties;
1153 auto it = properties.begin();
1154 const auto end = properties.end();
1155 for (; it != end; ++it) {
1156 const auto &property = *it;
1157 if (skipProperty && qstrcmp(property->name, skipProperty) == 0)
1158 continue;
1159
1160 const ValueToQmlResult result = valueToQml(node, *property, output);
1161 if (result.ok) {
1162 if (result.isDynamicProperty) {
1163 indent(output) << "property " << typeName(property->value.metaType()).toByteArray() << ' ' << result.name << u": "_s << result.value << u'\n';
1164 } else if (!QSSGQmlUtilities::PropertyMap::instance()->isDefaultValue(node.runtimeType, property->name, property->value)) {
1165 if (result.expandedProperties.size() > 1) {
1166 for (const auto &va : result.expandedProperties)
1167 indent(output) << result.name << va << u'\n';
1168 } else {
1169 indent(output) << result.name << u": "_s << result.value << u'\n';
1170 }
1171 }
1172 } else if (!result.isDynamicProperty) {
1173 QString message = u"Skipped property: "_s + QString::fromUtf8(property->name);
1174 if (!result.notValidReason.isEmpty())
1175 message.append(u", reason: "_s + result.notValidReason);
1176 qDebug() << message;
1177 indent(output) << comment() << message + u'\n';
1178 }
1179 }
1180}
1181
1182static void writeQml(const QSSGSceneDesc::Node &transform, OutputContext &output)
1183{
1184 using namespace QSSGSceneDesc;
1185 Q_ASSERT(transform.nodeType == QSSGSceneDesc::Node::Type::Transform && transform.runtimeType == QSSGSceneDesc::Node::RuntimeType::Node);
1186 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::Node>() << blockBegin(output);
1187 writeNodeProperties(transform, output);
1188}
1189
1190void writeQml(const QSSGSceneDesc::Material &material, OutputContext &output)
1191{
1192 using namespace QSSGSceneDesc;
1193 Q_ASSERT(material.nodeType == QSSGSceneDesc::Model::Type::Material);
1194 if (material.runtimeType == QSSGSceneDesc::Model::RuntimeType::SpecularGlossyMaterial) {
1195 indent(output) << qmlElementName<Material::RuntimeType::SpecularGlossyMaterial>() << blockBegin(output);
1196 } else if (material.runtimeType == Model::RuntimeType::PrincipledMaterial) {
1197 indent(output) << qmlElementName<Material::RuntimeType::PrincipledMaterial>() << blockBegin(output);
1198 } else if (material.runtimeType == Material::RuntimeType::CustomMaterial) {
1199 indent(output) << qmlElementName<Material::RuntimeType::CustomMaterial>() << blockBegin(output);
1200 } else if (material.runtimeType == Material::RuntimeType::SpecularGlossyMaterial) {
1201 indent(output) << qmlElementName<Material::RuntimeType::SpecularGlossyMaterial>() << blockBegin(output);
1202 } else {
1203 Q_UNREACHABLE();
1204 }
1205
1206 writeNodeProperties(material, output);
1207}
1208
1209static QString toQmlStringLiteral(const QString &value)
1210{
1211 QString escaped = value;
1212 escaped.replace(u'\\', u"\\\\"_s);
1213 escaped.replace(u'"', u"\\\""_s);
1214 escaped.replace(u'\n', u"\\n"_s);
1215 escaped.replace(u'\r', u"\\r"_s);
1216 return u'"' + escaped + u'"';
1217}
1218
1219static void writeQml(const QSSGSceneDesc::Model &model, OutputContext &output)
1220{
1221 using namespace QSSGSceneDesc;
1222 Q_ASSERT(model.nodeType == Node::Type::Model);
1223 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::Model>() << blockBegin(output);
1224 const bool hasVariants = !model.variantMaterials.isEmpty() && model.scene
1225 && !model.scene->materialVariants.isEmpty();
1226 writeNodeProperties(model, output, hasVariants ? "materials" : nullptr);
1227 if (hasVariants) {
1228 // Unknown variant names select the default materials
1229 const auto materialListString = [](const QList<Node *> &materials) {
1230 QStringList ids;
1231 for (const Node *material : materials)
1232 ids.append(getIdForNode(*material));
1233 return u'[' + ids.join(u", "_s) + u']';
1234 };
1235 QString expression;
1236 const QStringList &names = model.scene->materialVariants;
1237 const QString rootId = getIdForNode(*model.scene->root);
1238 for (qsizetype variant = 0; variant < model.variantMaterials.size() && variant < names.size(); ++variant) {
1239 expression += rootId + u".materialVariant === "_s + toQmlStringLiteral(names.at(variant)) + u" ? "_s
1240 + materialListString(model.variantMaterials.at(variant)) + u" : "_s;
1241 }
1242 expression += materialListString(model.defaultMaterials);
1243 QSSGQmlScopedIndent scopedIndent(output);
1244 indent(output) << u"materials: "_s << expression << u'\n';
1245 }
1246}
1247
1248static void writeQml(const QSSGSceneDesc::Camera &camera, OutputContext &output)
1249{
1250 using namespace QSSGSceneDesc;
1251 Q_ASSERT(camera.nodeType == Node::Type::Camera);
1252 if (camera.runtimeType == Camera::RuntimeType::PerspectiveCamera)
1253 indent(output) << qmlElementName<Camera::RuntimeType::PerspectiveCamera>() << blockBegin(output);
1254 else if (camera.runtimeType == Camera::RuntimeType::OrthographicCamera)
1255 indent(output) << qmlElementName<Camera::RuntimeType::OrthographicCamera>() << blockBegin(output);
1256 else
1257 Q_UNREACHABLE();
1258 writeNodeProperties(camera, output);
1259}
1260
1261static void writeQml(const QSSGSceneDesc::Texture &texture, OutputContext &output)
1262{
1263 using namespace QSSGSceneDesc;
1264 Q_ASSERT(texture.nodeType == Node::Type::Texture && QSSGRenderGraphObject::isTexture(texture.runtimeType));
1265 if (texture.runtimeType == Texture::RuntimeType::Image2D)
1266 indent(output) << qmlElementName<Texture::RuntimeType::Image2D>() << blockBegin(output);
1267 else if (texture.runtimeType == Texture::RuntimeType::ImageCube)
1268 indent(output) << qmlElementName<Texture::RuntimeType::ImageCube>() << blockBegin(output);
1269 writeNodeProperties(texture, output);
1270}
1271
1272static void writeQml(const QSSGSceneDesc::Skin &skin, OutputContext &output)
1273{
1274 using namespace QSSGSceneDesc;
1275 Q_ASSERT(skin.nodeType == Node::Type::Skin && skin.runtimeType == Node::RuntimeType::Skin);
1276 indent(output) << qmlElementName<Node::RuntimeType::Skin>() << blockBegin(output);
1277 writeNodeProperties(skin, output);
1278}
1279
1280static void writeQml(const QSSGSceneDesc::MorphTarget &morphTarget, OutputContext &output)
1281{
1282 using namespace QSSGSceneDesc;
1283 Q_ASSERT(morphTarget.nodeType == Node::Type::MorphTarget);
1284 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::MorphTarget>() << blockBegin(output);
1285 writeNodeProperties(morphTarget, output);
1286}
1287
1288QString getTextureSourceName(const QString &name, const QString &fmt)
1289{
1290 const auto textureFolder = getTextureFolder();
1291
1292 const auto sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
1293 // Raw pixel data has no fmt and is written as PNG
1294 const auto ext = (fmt.isEmpty() || fmt.length() > 4) ? u".png"_s
1295 : u"."_s + fmt;
1296
1297 return QString(textureFolder + sanitizedName + ext);
1298}
1299
1300static QString outputTextureAsset(const QSSGSceneDesc::TextureData &textureData, const QDir &outdir)
1301{
1302 if (textureData.data.isEmpty())
1303 return QString();
1304
1305 const auto mapsFolder = getTextureFolder();
1306 const auto id = getIdForNode(textureData);
1307 const bool isCompressed = ((textureData.flgs & quint8(QSSGSceneDesc::TextureData::Flags::Compressed)) != 0);
1308 const QString textureSourceName = getTextureSourceName(id, isCompressed ? QString::fromUtf8(textureData.fmt)
1309 : QString());
1310
1311 // If a maps folder does not exist, then create one
1312 if (!outdir.exists(mapsFolder) && !outdir.mkdir(mapsFolder))
1313 return QString(); // Error out
1314
1315 const auto imagePath = QString(outdir.path() + QDir::separator() + textureSourceName);
1316
1317 if (isCompressed) {
1318 QFile file(imagePath);
1319 if (!file.open(QIODevice::WriteOnly)) {
1320 qWarning("Failed to open file %s: %s",
1321 qPrintable(file.fileName()), qPrintable(file.errorString()));
1322 return {};
1323 } else {
1324 file.write(textureData.data);
1325 file.close();
1326 }
1327 } else {
1328 const auto &texData = textureData.data;
1329 const auto &size = textureData.sz;
1330 QImage image;
1331 image = QImage(reinterpret_cast<const uchar *>(texData.data()), size.width(), size.height(), QImage::Format::Format_RGBA8888);
1332 if (!image.save(imagePath))
1333 return QString();
1334 }
1335
1336 return textureSourceName;
1337}
1338
1339static void writeQml(const QSSGSceneDesc::TextureData &textureData, OutputContext &output)
1340{
1341 using namespace QSSGSceneDesc;
1342 Q_ASSERT(textureData.nodeType == Node::Type::Texture && textureData.runtimeType == Node::RuntimeType::TextureData);
1343
1344 QString textureSourcePath = outputTextureAsset(textureData, output.outdir);
1345
1346 static const auto writeProperty = [](const QString &type, const QString &name, const QString &value) {
1347 return QString::fromLatin1("property %1 %2: %3").arg(type, name, value);
1348 };
1349
1350 if (!textureSourcePath.isEmpty()) {
1351 const auto type = QLatin1String("url");
1352 const auto name = getIdForNode(textureData);
1353
1354 indent(output) << writeProperty(type, name, toQuotedString(textureSourcePath)) << '\n';
1355 }
1356}
1357
1358static inline QString getInstancesFolder() { return QStringLiteral("instances/"); }
1359
1360static QString outputInstanceTableAsset(const QSSGSceneDesc::Instancing &instancing, const QDir &outdir)
1361{
1362 const QString folder = getInstancesFolder();
1363 if (!outdir.exists(folder) && !outdir.mkdir(folder)) {
1364 qWarning() << "Failed to create instances folder at" << outdir;
1365 return {};
1366 }
1367 const QString sourceName = folder + getIdForNode(instancing) + QStringLiteral(".bin");
1368 QFile file(outdir.path() + QDir::separator() + sourceName);
1369 if (!file.open(QIODevice::WriteOnly)) {
1370 qWarning("Failed to open file %s: %s", qPrintable(file.fileName()), qPrintable(file.errorString()));
1371 return {};
1372 }
1373 if (!QQuick3DFileInstancing::writeInstanceTable(&file, instancing.instanceData, int(instancing.instanceCount)))
1374 return {};
1375 return sourceName;
1376}
1377
1378static void writeQml(const QSSGSceneDesc::Instancing &instancing, OutputContext &output)
1379{
1380 using namespace QSSGSceneDesc;
1381 Q_ASSERT(instancing.nodeType == Node::Type::Instancing
1382 && instancing.runtimeType == Node::RuntimeType::ModelInstance);
1383
1384 const QString instanceSourcePath = outputInstanceTableAsset(instancing, output.outdir);
1385
1386 indent(output) << qmlElementName<Node::RuntimeType::ModelInstance>() << blockBegin(output);
1387 writeNodeProperties(instancing, output);
1388 if (!instanceSourcePath.isEmpty()) {
1389 QSSGQmlScopedIndent scopedIndent(output);
1390 indent(output) << u"source: "_s << toQuotedString(instanceSourcePath) << u'\n';
1391 }
1392}
1393
1394static void writeQml(const QSSGSceneDesc::Light &light, OutputContext &output)
1395{
1396 using namespace QSSGSceneDesc;
1397 Q_ASSERT(light.nodeType == Node::Type::Light);
1398 if (light.runtimeType == Light::RuntimeType::DirectionalLight)
1399 indent(output) << qmlElementName<Light::RuntimeType::DirectionalLight>() << blockBegin(output);
1400 else if (light.runtimeType == Light::RuntimeType::SpotLight)
1401 indent(output) << qmlElementName<Light::RuntimeType::SpotLight>() << blockBegin(output);
1402 else if (light.runtimeType == Light::RuntimeType::PointLight)
1403 indent(output) << qmlElementName<Light::RuntimeType::PointLight>() << blockBegin(output);
1404 else
1405 Q_UNREACHABLE();
1406 writeNodeProperties(light, output);
1407}
1408
1409static void writeQml(const QSSGSceneDesc::Skeleton &skeleton, OutputContext &output)
1410{
1411 using namespace QSSGSceneDesc;
1412 Q_ASSERT(skeleton.nodeType == Node::Type::Skeleton && skeleton.runtimeType == Node::RuntimeType::Skeleton);
1413 indent(output) << qmlElementName<Node::RuntimeType::Skeleton>() << blockBegin(output);
1414 writeNodeProperties(skeleton, output);
1415}
1416
1417static void writeQml(const QSSGSceneDesc::Joint &joint, OutputContext &output)
1418{
1419 using namespace QSSGSceneDesc;
1420 Q_ASSERT(joint.nodeType == Node::Type::Joint && joint.runtimeType == Node::RuntimeType::Joint);
1421 indent(output) << qmlElementName<Node::RuntimeType::Joint>() << blockBegin(output);
1422 writeNodeProperties(joint, output);
1423}
1424
1425static void writeQmlForResourceNode(const QSSGSceneDesc::Node &node, OutputContext &output)
1426{
1427 using namespace QSSGSceneDesc;
1428 Q_ASSERT(output.type == OutputContext::Resource);
1429 Q_ASSERT(QSSGRenderGraphObject::isResource(node.runtimeType) || node.nodeType == Node::Type::Mesh || node.nodeType == Node::Type::Skeleton);
1430
1431 const bool processNode = !node.properties.isEmpty() || (output.type == OutputContext::Resource);
1432 if (processNode) {
1433 QSSGQmlScopedIndent scopedIndent(output);
1434 switch (node.nodeType) {
1435 case Node::Type::Skin:
1436 writeQml(static_cast<const Skin &>(node), output);
1437 break;
1438 case Node::Type::MorphTarget:
1439 writeQml(static_cast<const MorphTarget &>(node), output);
1440 break;
1441 case Node::Type::Instancing:
1442 writeQml(static_cast<const Instancing &>(node), output);
1443 break;
1444 case Node::Type::Skeleton:
1445 writeQml(static_cast<const Skeleton &>(node), output);
1446 break;
1447 case Node::Type::Texture:
1448 if (node.runtimeType == Node::RuntimeType::Image2D)
1449 writeQml(static_cast<const Texture &>(node), output);
1450 else if (node.runtimeType == Node::RuntimeType::ImageCube)
1451 writeQml(static_cast<const Texture &>(node), output);
1452 else if (node.runtimeType == Node::RuntimeType::TextureData)
1453 writeQml(static_cast<const TextureData &>(node), output);
1454 else
1455 Q_UNREACHABLE();
1456 break;
1457 case Node::Type::Material:
1458 writeQml(static_cast<const Material &>(node), output);
1459 break;
1460 case Node::Type::Mesh:
1461 // Only handled as a property (see: valueToQml())
1462 break;
1463 default:
1464 qWarning("Unhandled resource type \'%d\'?", int(node.runtimeType));
1465 break;
1466 }
1467 }
1468
1469 // Do something more convenient if this starts expending to more types...
1470 // NOTE: The TextureData type is written out as a url property...
1471 const bool skipBlockEnd = (node.runtimeType == Node::RuntimeType::TextureData || node.nodeType == Node::Type::Mesh);
1472 if (!skipBlockEnd && processNode && output.scopeDepth != 0) {
1473 QSSGQmlScopedIndent scopedIndent(output);
1474 indent(output) << blockEnd(output);
1475 }
1476}
1477
1478static void writeQmlForNode(const QSSGSceneDesc::Node &node, OutputContext &output)
1479{
1480 using namespace QSSGSceneDesc;
1481
1482 const bool processNode = !(node.properties.isEmpty() && node.children.isEmpty())
1483 || (output.type == OutputContext::Resource);
1484 if (processNode) {
1485 QSSGQmlScopedIndent scopedIndent(output);
1486 switch (node.nodeType) {
1487 case Node::Type::Skeleton:
1488 writeQml(static_cast<const Skeleton &>(node), output);
1489 break;
1490 case Node::Type::Joint:
1491 writeQml(static_cast<const Joint &>(node), output);
1492 break;
1493 case Node::Type::Light:
1494 writeQml(static_cast<const Light &>(node), output);
1495 break;
1496 case Node::Type::Transform:
1497 writeQml(node, output);
1498 break;
1499 case Node::Type::Camera:
1500 writeQml(static_cast<const Camera &>(node), output);
1501 break;
1502 case Node::Type::Model:
1503 writeQml(static_cast<const Model &>(node), output);
1504 break;
1505 default:
1506 break;
1507 }
1508 }
1509
1510 for (const auto &cld : node.children) {
1511 if (!QSSGRenderGraphObject::isResource(cld->runtimeType) && output.type == OutputContext::NodeTree) {
1512 QSSGQmlScopedIndent scopedIndent(output);
1513 writeQmlForNode(*cld, output);
1514 }
1515 }
1516
1517 // Do something more convenient if this starts expending to more types...
1518 // NOTE: The TextureData type is written out as a url property...
1519 const bool skipBlockEnd = (node.runtimeType == Node::RuntimeType::TextureData || node.nodeType == Node::Type::Mesh);
1520 if (!skipBlockEnd && processNode && output.scopeDepth != 0) {
1521 QSSGQmlScopedIndent scopedIndent(output);
1522 indent(output) << blockEnd(output);
1523 }
1524}
1525
1526void writeQmlForResources(const QSSGSceneDesc::Scene::ResourceNodes &resources, OutputContext &output)
1527{
1528 auto sortedResources = resources;
1529 std::sort(sortedResources.begin(), sortedResources.end(), [](const QSSGSceneDesc::Node *a, const QSSGSceneDesc::Node *b) {
1530 using RType = QSSGSceneDesc::Node::RuntimeType;
1531 if (a->runtimeType == RType::TextureData && b->runtimeType != RType::TextureData)
1532 return true;
1533 if (a->runtimeType == RType::ImageCube && (b->runtimeType != RType::TextureData && b->runtimeType != RType::ImageCube))
1534 return true;
1535 if (a->runtimeType == RType::Image2D && (b->runtimeType != RType::TextureData && b->runtimeType != RType::Image2D))
1536 return true;
1537
1538 return false;
1539 });
1540 for (const auto &res : std::as_const(sortedResources))
1541 writeQmlForResourceNode(*res, output);
1542}
1543
1544static void generateKeyframeData(const QSSGSceneDesc::Animation::Channel &channel, QByteArray &keyframeData)
1545{
1546#ifdef QT_QUICK3D_ENABLE_RT_ANIMATIONS
1547 QCborStreamWriter writer(&keyframeData);
1548 // Start root array
1549 writer.startArray();
1550 // header name
1551 writer.append("QTimelineKeyframes");
1552 // file version. Increase this if the format changes.
1553 const int keyframesDataVersion = 1;
1554 writer.append(keyframesDataVersion);
1555 writer.append(int(channel.keys.at(0)->getValueQMetaType()));
1556
1557 // Start Keyframes array
1558 writer.startArray();
1559 quint8 compEnd = quint8(channel.keys.at(0)->getValueType());
1560 bool isQuaternion = false;
1561 if (compEnd == quint8(QSSGSceneDesc::Animation::KeyPosition::ValueType::Quaternion)) {
1562 isQuaternion = true;
1563 compEnd = 3;
1564 } else {
1565 compEnd++;
1566 }
1567 for (const auto &key : channel.keys) {
1568 writer.append(key->time);
1569 // Easing always linear
1570 writer.append(QEasingCurve::Linear);
1571 if (isQuaternion)
1572 writer.append(key->value[3]);
1573 for (quint8 i = 0; i < compEnd; ++i)
1574 writer.append(key->value[i]);
1575 }
1576 // End Keyframes array
1577 writer.endArray();
1578 // End root array
1579 writer.endArray();
1580#else
1581 Q_UNUSED(channel)
1582 Q_UNUSED(keyframeData)
1583#endif // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1584}
1585
1586QPair<QString, QString> writeQmlForAnimation(const QSSGSceneDesc::Animation &anim, qsizetype index, OutputContext &output, bool useBinaryKeyframes = true, bool generateTimelineAnimations = true)
1587{
1588 indent(output) << "Timeline {\n";
1589
1590 QSSGQmlScopedIndent scopedIndent(output);
1591 // The duration property of the TimelineAnimation is an int...
1592 const int duration = qCeil(anim.length);
1593 // Use the same name for objectName and id
1594 const QString animationId = getIdForAnimation(anim.name);
1595 indent(output) << "id: " << animationId << "\n";
1596 QString animationName = animationId;
1597 if (!anim.name.isEmpty())
1598 animationName = QString::fromLocal8Bit(anim.name);
1599 indent(output) << "objectName: \"" << animationName << "\"\n";
1600 indent(output) << "property real framesPerSecond: " << anim.framesPerSecond << "\n";
1601 indent(output) << "startFrame: 0\n";
1602 indent(output) << "endFrame: " << duration << "\n";
1603 indent(output) << "currentFrame: 0\n";
1604 // Only generate the TimelineAnimation component here if requested
1605 // enabled is only set to true up front if we expect to autoplay
1606 // the generated TimelineAnimation
1607 if (generateTimelineAnimations) {
1608 indent(output) << "enabled: true\n";
1609 indent(output) << "animations: TimelineAnimation {\n";
1610 {
1611 QSSGQmlScopedIndent scopedIndent(output);
1612 indent(output) << "duration: " << duration << "\n";
1613 indent(output) << "from: 0\n";
1614 indent(output) << "to: " << duration << "\n";
1615 indent(output) << "running: true\n";
1616 indent(output) << "loops: Animation.Infinite\n";
1617 }
1618 indent(output) << blockEnd(output);
1619 }
1620
1621 for (const auto &channel : anim.channels) {
1622 QString id = getIdForNode(*channel->target);
1623 QString propertyName = asString(channel->targetProperty);
1624
1625 indent(output) << "KeyframeGroup {\n";
1626 {
1627 QSSGQmlScopedIndent scopedIndent(output);
1628 indent(output) << "target: " << id << "\n";
1629 indent(output) << "property: " << toQuotedString(propertyName) << "\n";
1630 if (useBinaryKeyframes && channel->keys.size() != 1) {
1631 const auto animFolder = getAnimationFolder();
1632 const auto animSourceName = getAnimationSourceName(id, propertyName, index);
1633 if (!output.outdir.exists(animFolder) && !output.outdir.mkdir(animFolder)) {
1634 // Make a warning
1635 continue;
1636 }
1637 QFile file(output.outdir.path() + QDir::separator() + animSourceName);
1638 if (!file.open(QIODevice::WriteOnly))
1639 continue;
1640 QByteArray keyframeData;
1641 // It is possible to store this keyframeData but we have to consider
1642 // all the cases including runtime only or writeQml only.
1643 // For now, we will generate it for each case.
1644 generateKeyframeData(*channel, keyframeData);
1645 file.write(keyframeData);
1646 file.close();
1647 indent(output) << "keyframeSource: " << toQuotedString(animSourceName) << "\n";
1648 } else {
1649 Q_ASSERT(!channel->keys.isEmpty());
1650 for (const auto &key : std::as_const(channel->keys)) {
1651 indent(output) << "Keyframe {\n";
1652 {
1653 QSSGQmlScopedIndent scopedIndent(output);
1654 indent(output) << "frame: " << key->time << "\n";
1655 indent(output) << "value: " << variantToQml(key->getValue()) << "\n";
1656 }
1657 indent(output) << blockEnd(output);
1658 }
1659 }
1660 }
1661 indent(output) << blockEnd(output);
1662 }
1663 return {animationName, animationId};
1664}
1665
1666void writeQml(const QSSGSceneDesc::Scene &scene, QTextStream &stream, const QDir &outdir, const QJsonObject &optionsObject)
1667{
1668 static const auto checkBooleanOption = [](const QLatin1String &optionName, const QJsonObject &options, bool defaultValue = false) {
1669 const auto it = options.constFind(optionName);
1670 const auto end = options.constEnd();
1671 QJsonValue value;
1672 if (it != end) {
1673 if (it->isObject())
1674 value = it->toObject().value(QLatin1String("value"));
1675 else
1676 value = it.value();
1677 }
1678 return value.toBool(defaultValue);
1679 };
1680
1681 auto root = scene.root;
1682 Q_ASSERT(root);
1683
1685
1686 QJsonObject options = optionsObject;
1687
1688 if (auto it = options.constFind(QLatin1String("options")), end = options.constEnd(); it != end)
1689 options = it->toObject();
1690
1691 quint8 outputOptions{ OutputContext::Options::None };
1692 if (checkBooleanOption(QLatin1String("expandValueComponents"), options))
1694
1695 // Workaround for design studio type components
1696 if (checkBooleanOption(QLatin1String("designStudioWorkarounds"), options))
1698
1699 const bool useBinaryKeyframes = checkBooleanOption("useBinaryKeyframes"_L1, options);
1700 const bool generateTimelineAnimations = !checkBooleanOption("manualAnimations"_L1, options);
1701
1702 OutputContext output { stream, outdir, scene.sourceDir, 0, OutputContext::Header, outputOptions };
1703
1704 writeImportHeader(output, scene.animations.count() > 0);
1705
1707 writeQml(*root, output); // Block scope will be left open!
1708
1709 if (!scene.materialVariants.isEmpty()) {
1710 // Models with variant materials bind to these
1711 QSSGQmlScopedIndent scopedIndent(output);
1712 QStringList variantNames;
1713 for (const QString &name : scene.materialVariants)
1714 variantNames.append(toQmlStringLiteral(name));
1715 indent(output) << u"property string materialVariant: \"\"\n"_s;
1716 indent(output) << u"readonly property list<string> materialVariants: ["_s << variantNames.join(u", "_s)
1717 << u"]\n"_s;
1718 }
1719
1720 stream << "\n";
1721 stream << indent() << "// Resources\n";
1723 writeQmlForResources(scene.resources, output);
1725 stream << "\n";
1726 stream << indent() << "// Nodes:\n";
1727 for (const auto &cld : std::as_const(root->children))
1728 writeQmlForNode(*cld, output);
1729
1730 // animations
1731 qsizetype animId = 0;
1732 stream << "\n";
1733 stream << indent() << "// Animations:\n";
1734 QList<QPair<QString, QString>> animationMap;
1735 for (const auto &cld : scene.animations) {
1736 QSSGQmlScopedIndent scopedIndent(output);
1737 auto mapValues = writeQmlForAnimation(*cld, animId++, output, useBinaryKeyframes, generateTimelineAnimations);
1738 animationMap.append(mapValues);
1739 indent(output) << blockEnd(output);
1740 }
1741
1742 if (!generateTimelineAnimations) {
1743 // Expose a map of timelines
1744 stream << "\n";
1745 stream << indent() << "// An exported mapping of Timelines (--manualAnimations)\n";
1746 stream << indent() << "property var timelineMap: {\n";
1747 QSSGQmlScopedIndent scopedIndent(output);
1748 for (const auto &mapValues : animationMap) {
1749 QSSGQmlScopedIndent scopedIndent(output);
1750 indent(output) << "\"" << mapValues.first << "\": " << mapValues.second << ",\n";
1751 }
1752 indent(output) << blockEnd(output);
1753 stream << indent() << "// A simple list of Timelines (--manualAnimations)\n";
1754 stream << indent() << "property var timelineList: [\n";
1755 for (const auto &mapValues : animationMap) {
1756 QSSGQmlScopedIndent scopedIndent(output);
1757 indent(output) << mapValues.second << ",\n";
1758 }
1759 indent(output) << "]\n";
1760 }
1761
1762
1763 // close the root
1764 indent(output) << blockEnd(output);
1765}
1766
1767void createTimelineAnimation(const QSSGSceneDesc::Animation &anim, QObject *parent, bool isEnabled, bool useBinaryKeyframes)
1768{
1769#ifdef QT_QUICK3D_ENABLE_RT_ANIMATIONS
1770 auto timeline = new QQuickTimeline(parent);
1771 auto timelineKeyframeGroup = timeline->keyframeGroups();
1772 for (const auto &channel : anim.channels) {
1773 auto keyframeGroup = new QQuickKeyframeGroup(timeline);
1774 keyframeGroup->setTargetObject(channel->target->obj);
1775 keyframeGroup->setProperty(asString(channel->targetProperty));
1776
1777 Q_ASSERT(!channel->keys.isEmpty());
1778 if (useBinaryKeyframes) {
1779 QByteArray keyframeData;
1780 generateKeyframeData(*channel, keyframeData);
1781
1782 keyframeGroup->setKeyframeData(keyframeData);
1783 } else {
1784 auto keyframes = keyframeGroup->keyframes();
1785 for (const auto &key : std::as_const(channel->keys)) {
1786 auto keyframe = new QQuickKeyframe(keyframeGroup);
1787 keyframe->setFrame(key->time);
1788 keyframe->setValue(key->getValue());
1789 keyframes.append(&keyframes, keyframe);
1790 }
1791 }
1792 (qobject_cast<QQmlParserStatus *>(keyframeGroup))->componentComplete();
1793 timelineKeyframeGroup.append(&timelineKeyframeGroup, keyframeGroup);
1794 }
1795 timeline->setEndFrame(anim.length);
1796 timeline->setEnabled(isEnabled);
1797
1798 auto timelineAnimation = new QQuickTimelineAnimation(timeline);
1799 timelineAnimation->setObjectName(anim.name);
1800 timelineAnimation->setDuration(int(anim.length));
1801 timelineAnimation->setFrom(0.0f);
1802 timelineAnimation->setTo(anim.length);
1803 timelineAnimation->setLoops(QQuickTimelineAnimation::Infinite);
1804 timelineAnimation->setTargetObject(timeline);
1805
1806 (qobject_cast<QQmlParserStatus *>(timeline))->componentComplete();
1807
1808 timelineAnimation->setRunning(true);
1809#else // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1810 Q_UNUSED(anim)
1811 Q_UNUSED(parent)
1812 Q_UNUSED(isEnabled)
1813 Q_UNUSED(useBinaryKeyframes)
1814#endif // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1815}
1816
1817void writeQmlComponent(const QSSGSceneDesc::Node &node, QTextStream &stream, const QDir &outDir)
1818{
1819 using namespace QSSGSceneDesc;
1820
1821 QSSG_ASSERT(node.scene != nullptr, return);
1822
1824
1825 if (node.runtimeType == Material::RuntimeType::CustomMaterial) {
1826 QString sourceDir = node.scene->sourceDir;
1827 OutputContext output { stream, outDir, sourceDir, 0, OutputContext::Resource };
1828 writeImportHeader(output);
1829 writeQml(static_cast<const Material &>(node), output);
1830 // Resources, if any, are written out as properties on the component
1831 const auto &resources = node.scene->resources;
1832 writeQmlForResources(resources, output);
1833 indent(output) << blockEnd(output);
1834 } else {
1835 Q_UNREACHABLE(); // Only implemented for Custom material at this point.
1836 }
1837}
1838
1839}
1840
1841QT_END_NAMESPACE
bool isDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property, const QVariant &value)
PropertiesMap propertiesForType(QSSGSceneDesc::Node::RuntimeType type)
static PropertyMap * instance()
QVariant getDefaultValue(QSSGSceneDesc::Node::RuntimeType type, const char *property)
QHash< QByteArray, QVariant > PropertiesMap
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:34
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:178
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:341
static QString getInstancesFolder()
static const char * blockEnd()
static constexpr QByteArrayView qml_basic_types[]
QString builtinQmlType(const QVariant &var)
static void generateKeyframeData(const QSSGSceneDesc::Animation::Channel &channel, QByteArray &keyframeData)
void writeQmlComponent(const QSSGSceneDesc::Node &node, QTextStream &stream, const QDir &outDir)
static QString getIdForNode(const QSSGSceneDesc::Node &node)
QString asString(const QVariant &var)
static void writeNodeProperties(const QSSGSceneDesc::Node &node, OutputContext &output, const char *skipProperty=nullptr)
static QString getTextureFolder()
QString qmlComponentName(const QString &name)
static PropertyMap::PropertiesMap getObjectPropertiesMap(QObject *object)
static QString getAnimationFolder()
static QString getAnimationExtension()
static ValueToQmlResult valueToQml(const QSSGSceneDesc::Node &target, const QSSGSceneDesc::Property &property, OutputContext &output)
static QString outputInstanceTableAsset(const QSSGSceneDesc::Instancing &instancing, const QDir &outdir)
static void writeQml(const QSSGSceneDesc::Model &model, OutputContext &output)
QString colorToQml(const QColor &color)
void createTimelineAnimation(const QSSGSceneDesc::Animation &anim, QObject *parent, bool isEnabled, bool useBinaryKeyframes)
static QString getMeshExtension()
static const char * blockBegin()
static const char * blockBegin(OutputContext &output)
static void writeQml(const QSSGSceneDesc::Node &transform, OutputContext &output)
static QStringList expandComponents(const QString &value, QMetaType mt)
void writeQmlForResources(const QSSGSceneDesc::Scene::ResourceNodes &resources, OutputContext &output)
const char * qmlElementName()
QString sanitizeQmlId(const QString &id)
static QString toQmlStringLiteral(const QString &value)
static const char * blockEnd(OutputContext &output)
static QString outputTextureAsset(const QSSGSceneDesc::TextureData &textureData, const QDir &outdir)
static const char * comment()
static QTextStream & indent(OutputContext &output)
static QString toQuotedString(const QString &text)
static QString getIdForAnimation(const QByteArray &inName)
static QString getMeshFolder()
static void writeImportHeader(OutputContext &output, bool hasAnimation=false)
static QStringList expandComponentsPartially(const QString &value, QMetaType mt)
QPair< QString, QString > writeQmlForAnimation(const QSSGSceneDesc::Animation &anim, qsizetype index, OutputContext &output, bool useBinaryKeyframes=true, bool generateTimelineAnimations=true)
const char * getQmlElementName(const QSSGSceneDesc::Node &node)
static std::pair< QString, QString > copyTextureAsset(const QUrl &texturePath, OutputContext &output)
static std::pair< QString, QString > meshAssetName(const QSSGSceneDesc::Scene &scene, const QSSGSceneDesc::Mesh &meshNode, const QDir &outdir)
static const char * indent()
QString getAnimationSourceName(const QString &id, const QString &property, qsizetype index)
QString getMeshSourceName(const QString &name)
QString variantToQml(const QVariant &variant)
QString stripParentDirectory(const QString &filePath)
QString asString(QSSGSceneDesc::Animation::Channel::TargetProperty prop)
static void writeQmlForResourceNode(const QSSGSceneDesc::Node &node, OutputContext &output)
QString sanitizeQmlSourcePath(const QString &source, bool removeParentDirectory)
QString getTextureSourceName(const QString &name, const QString &fmt)
static void resetIdAllocator()
static QByteArrayView typeName(QMetaType mt)
void writeQml(const QSSGSceneDesc::Scene &scene, QTextStream &stream, const QDir &outdir, const QJsonObject &optionsObject)
static QString indentString(OutputContext &output)
void writeQml(const QSSGSceneDesc::Material &material, OutputContext &output)
static void writeQmlForNode(const QSSGSceneDesc::Node &node, OutputContext &output)
Combined button and popup list for selecting options.