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"; }
423
424static const char *getQmlElementName(const QSSGSceneDesc::Node &node)
425{
426 using RuntimeType = QSSGSceneDesc::Node::RuntimeType;
427 switch (node.runtimeType) {
428 case RuntimeType::Node:
429 return qmlElementName<RuntimeType::Node>();
430 case RuntimeType::PrincipledMaterial:
431 return qmlElementName<RuntimeType::PrincipledMaterial>();
432 case RuntimeType::SpecularGlossyMaterial:
433 return qmlElementName<RuntimeType::SpecularGlossyMaterial>();
434 case RuntimeType::CustomMaterial:
435 return qmlElementName<RuntimeType::CustomMaterial>();
436 case RuntimeType::Image2D:
437 return qmlElementName<RuntimeType::Image2D>();
438 case RuntimeType::ImageCube:
439 return qmlElementName<RuntimeType::ImageCube>();
440 case RuntimeType::TextureData:
441 return qmlElementName<RuntimeType::TextureData>();
442 case RuntimeType::Model:
443 return qmlElementName<RuntimeType::Model>();
444 case RuntimeType::OrthographicCamera:
445 return qmlElementName<RuntimeType::OrthographicCamera>();
446 case RuntimeType::PerspectiveCamera:
447 return qmlElementName<RuntimeType::PerspectiveCamera>();
448 case RuntimeType::DirectionalLight:
449 return qmlElementName<RuntimeType::DirectionalLight>();
450 case RuntimeType::PointLight:
451 return qmlElementName<RuntimeType::PointLight>();
452 case RuntimeType::SpotLight:
453 return qmlElementName<RuntimeType::SpotLight>();
454 case RuntimeType::Skeleton:
455 return qmlElementName<RuntimeType::Skeleton>();
456 case RuntimeType::Joint:
457 return qmlElementName<RuntimeType::Joint>();
458 case RuntimeType::Skin:
459 return qmlElementName<RuntimeType::Skin>();
460 case RuntimeType::MorphTarget:
461 return qmlElementName<RuntimeType::MorphTarget>();
462 default:
463 return "UNKNOWN_TYPE";
464 }
465}
466
490
491static constexpr QByteArrayView qml_basic_types[] {
492 "bool",
493 "double",
494 "int",
495 "list",
496 "real",
497 "string",
498 "url",
499 "var",
500 "color",
501 "date",
502 "font",
503 "matrix4x4",
504 "point",
505 "quaternion",
506 "rect",
507 "size",
508 "vector2d",
509 "vector3d",
510 "vector4d"
511};
512
513static_assert(std::size(qml_basic_types) == QMLBasicType::Unknown_Count, "Missing type?");
514
515static QByteArrayView typeName(QMetaType mt)
516{
517 switch (mt.id()) {
518 case QMetaType::Bool:
519 return qml_basic_types[QMLBasicType::Bool];
520 case QMetaType::Char:
521 case QMetaType::SChar:
522 case QMetaType::UChar:
523 case QMetaType::Char16:
524 case QMetaType::Char32:
525 case QMetaType::QChar:
526 case QMetaType::Short:
527 case QMetaType::UShort:
528 case QMetaType::Int:
529 case QMetaType::UInt:
530 case QMetaType::Long:
531 case QMetaType::ULong:
532 case QMetaType::LongLong:
533 case QMetaType::ULongLong:
534 return qml_basic_types[QMLBasicType::Int];
535 case QMetaType::Float:
536 case QMetaType::Double:
537 return qml_basic_types[QMLBasicType::Real];
538 case QMetaType::QByteArray:
539 case QMetaType::QString:
540 return qml_basic_types[QMLBasicType::String];
541 case QMetaType::QDate:
542 case QMetaType::QTime:
543 case QMetaType::QDateTime:
544 return qml_basic_types[QMLBasicType::Date];
545 case QMetaType::QUrl:
546 return qml_basic_types[QMLBasicType::Url];
547 case QMetaType::QRect:
548 case QMetaType::QRectF:
549 return qml_basic_types[QMLBasicType::Rect];
550 case QMetaType::QSize:
551 case QMetaType::QSizeF:
552 return qml_basic_types[QMLBasicType::Size];
553 case QMetaType::QPoint:
554 case QMetaType::QPointF:
555 return qml_basic_types[QMLBasicType::Point];
556 case QMetaType::QVariant:
557 return qml_basic_types[QMLBasicType::Var];
558 case QMetaType::QColor:
559 return qml_basic_types[QMLBasicType::Color];
560 case QMetaType::QMatrix4x4:
561 return qml_basic_types[QMLBasicType::Mat44];
562 case QMetaType::QVector2D:
563 return qml_basic_types[QMLBasicType::Vector2D];
564 case QMetaType::QVector3D:
565 return qml_basic_types[QMLBasicType::Vector3D];
566 case QMetaType::QVector4D:
567 return qml_basic_types[QMLBasicType::Vector4D];
568 case QMetaType::QQuaternion:
569 return qml_basic_types[QMLBasicType::Quaternion];
570 case QMetaType::QFont:
571 return qml_basic_types[QMLBasicType::Font];
572 default:
573 return qml_basic_types[QMLBasicType::Var];
574 }
575}
576
581// Normally g_idMap will contain all the ids but in some cases
582// (like Animation, not Node) the ids will just be stored
583// to avoid conflict.
584// Now, Animations will be processed after all the Nodes,
585// For Nodes, it is not used.
587Q_GLOBAL_STATIC(UniqueIdOthers, g_idOthers)
588
589static QString getIdForNode(const QSSGSceneDesc::Node &node)
590{
591 static constexpr const char *typeNames[] = {
592 "", // Transform
593 "_camera",
594 "", // Model
595 "_texture",
596 "_material",
597 "_light",
598 "_mesh",
599 "_skin",
600 "_skeleton",
601 "_joint",
602 "_morphtarget",
603 "_unknown"
604 };
605 constexpr uint nameCount = sizeof(typeNames)/sizeof(const char*);
606 const bool nodeHasName = (node.name.size() > 0);
607 uint nameIdx = qMin(uint(node.nodeType), nameCount);
608 QString name = nodeHasName ? QString::fromUtf8(node.name + typeNames[nameIdx]) : QString::fromLatin1(getQmlElementName(node));
609 QString sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
610
611 // Make sure we return a unique id.
612 if (const auto it = g_nodeNameMap->constFind(&node); it != g_nodeNameMap->constEnd())
613 return *it;
614
615 quint64 id = node.id;
616 int attempts = 1000;
617 QString candidate = sanitizedName;
618 do {
619 if (const auto it = g_idMap->constFind(candidate); it == g_idMap->constEnd()) {
620 g_idMap->insert(candidate, &node);
621 g_nodeNameMap->insert(&node, candidate);
622 return candidate;
623 }
624
625 candidate = QStringLiteral("%1%2").arg(sanitizedName).arg(id++);
626 } while (--attempts);
627
628 return candidate;
629}
630
631static QString getIdForAnimation(const QByteArray &inName)
632{
633 QString name = !inName.isEmpty() ? QString::fromUtf8(inName + "_timeline") : "timeline0"_L1;
634 QString sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
635
636 int attempts = 1000;
637 quint16 id = 0;
638 QString candidate = sanitizedName;
639 do {
640 if (const auto it = g_idMap->constFind(candidate); it == g_idMap->constEnd()) {
641 if (const auto oIt = g_idOthers->constFind(candidate); oIt == g_idOthers->constEnd()) {
642 g_idOthers->insert(candidate);
643 return candidate;
644 }
645 }
646
647 candidate = QStringLiteral("%1%2").arg(sanitizedName).arg(++id);
648 } while (--attempts);
649
650 return candidate;
651}
652
653QString stripParentDirectory(const QString &filePath) {
654 QString sourceCopy = filePath;
655 while (sourceCopy.startsWith(QChar::fromLatin1('.')) || sourceCopy.startsWith(QChar::fromLatin1('/')) || sourceCopy.startsWith(QChar::fromLatin1('\\')))
656 sourceCopy.remove(0, 1);
657 return sourceCopy;
658}
659
660static const char *blockBegin() { return " {\n"; }
661static const char *blockEnd() { return "}\n"; }
662static const char *comment() { return "// "; }
663static const char *indent() { return " "; }
664
666{
667 enum : quint8 { QSSG_INDENT = 4 };
668 explicit QSSGQmlScopedIndent(OutputContext &out) : output(out) { out.indent += QSSG_INDENT; };
669 ~QSSGQmlScopedIndent() { output.indent = qMax(output.indent - QSSG_INDENT, 0); }
671};
672
673static QString indentString(OutputContext &output)
674{
675 QString str;
676 for (quint8 i = 0; i < output.indent; i += QSSGQmlScopedIndent::QSSG_INDENT)
677 str += QString::fromLatin1(indent());
678 return str;
679}
680
682{
683 for (quint8 i = 0; i < output.indent; i += QSSGQmlScopedIndent::QSSG_INDENT)
684 output.stream << indent();
685 return output.stream;
686}
687
688static const char *blockBegin(OutputContext &output)
689{
690 ++output.scopeDepth;
691 return blockBegin();
692}
693
694static const char *blockEnd(OutputContext &output)
695{
696 output.scopeDepth = qMax(0, output.scopeDepth - 1);
697 return blockEnd();
698}
699
700static void writeImportHeader(OutputContext &output, bool hasAnimation = false)
701{
702 output.stream << "import QtQuick\n"
703 << "import QtQuick3D\n\n";
704 if (hasAnimation)
705 output.stream << "import QtQuick.Timeline\n\n";
706}
707
708static QString toQuotedString(const QString &text) { return QStringLiteral("\"%1\"").arg(text); }
709
710static inline QString getMeshFolder() { return QStringLiteral("meshes/"); }
711static inline QString getMeshExtension() { return QStringLiteral(".mesh"); }
712
713QString getMeshSourceName(const QString &name)
714{
715 const auto meshFolder = getMeshFolder();
716 const auto extension = getMeshExtension();
717
718 return QString(meshFolder + name + extension);
719}
720
721static inline QString getTextureFolder() { return QStringLiteral("maps/"); }
722
723static inline QString getAnimationFolder() { return QStringLiteral("animations/"); }
724static inline QString getAnimationExtension() { return QStringLiteral(".qad"); }
725QString getAnimationSourceName(const QString &id, const QString &property, qsizetype index)
726{
727 const auto animationFolder = getAnimationFolder();
728 const auto extension = getAnimationExtension();
729 return QString(animationFolder + id + QStringLiteral("_")
730 + property + QStringLiteral("_")
731 + QString::number(index) + extension);
732}
733
734QString asString(const QVariant &var)
735{
736 return var.toString();
737}
738
739QString builtinQmlType(const QVariant &var)
740{
741 switch (var.metaType().id()) {
742 case QMetaType::QVector2D: {
743 const auto vec2 = qvariant_cast<QVector2D>(var);
744 return QLatin1String("Qt.vector2d(") + QString::number(vec2.x()) + QLatin1String(", ") + QString::number(vec2.y()) + QLatin1Char(')');
745 }
746 case QMetaType::QVector3D: {
747 const auto vec3 = qvariant_cast<QVector3D>(var);
748 return QLatin1String("Qt.vector3d(") + QString::number(vec3.x()) + QLatin1String(", ")
749 + QString::number(vec3.y()) + QLatin1String(", ")
750 + QString::number(vec3.z()) + QLatin1Char(')');
751 }
752 case QMetaType::QVector4D: {
753 const auto vec4 = qvariant_cast<QVector4D>(var);
754 return QLatin1String("Qt.vector4d(") + QString::number(vec4.x()) + QLatin1String(", ")
755 + QString::number(vec4.y()) + QLatin1String(", ")
756 + QString::number(vec4.z()) + QLatin1String(", ")
757 + QString::number(vec4.w()) + QLatin1Char(')');
758 }
759 case QMetaType::QColor: {
760 const auto color = qvariant_cast<QColor>(var);
761 return colorToQml(color);
762 }
763 case QMetaType::QQuaternion: {
764 const auto &quat = qvariant_cast<QQuaternion>(var);
765 return QLatin1String("Qt.quaternion(") + QString::number(quat.scalar()) + QLatin1String(", ")
766 + QString::number(quat.x()) + QLatin1String(", ")
767 + QString::number(quat.y()) + QLatin1String(", ")
768 + QString::number(quat.z()) + QLatin1Char(')');
769 }
770 case QMetaType::QMatrix4x4: {
771 const auto mat44 = qvariant_cast<QMatrix4x4>(var);
772 return QLatin1String("Qt.matrix4x4(")
773 + QString::number(mat44(0, 0)) + u", " + QString::number(mat44(0, 1)) + u", " + QString::number(mat44(0, 2)) + u", " + QString::number(mat44(0, 3)) + u", "
774 + QString::number(mat44(1, 0)) + u", " + QString::number(mat44(1, 1)) + u", " + QString::number(mat44(1, 2)) + u", " + QString::number(mat44(1, 3)) + u", "
775 + QString::number(mat44(2, 0)) + u", " + QString::number(mat44(2, 1)) + u", " + QString::number(mat44(2, 2)) + u", " + QString::number(mat44(2, 3)) + u", "
776 + QString::number(mat44(3, 0)) + u", " + QString::number(mat44(3, 1)) + u", " + QString::number(mat44(3, 2)) + u", " + QString::number(mat44(3, 3)) + u')';
777 }
778 case QMetaType::Float:
779 case QMetaType::Double:
780 case QMetaType::Int:
781 case QMetaType::Char:
782 case QMetaType::Long:
783 case QMetaType::LongLong:
784 case QMetaType::ULong:
785 case QMetaType::ULongLong:
786 case QMetaType::Bool:
787 return var.toString();
788 case QMetaType::QUrl: // QUrl needs special handling. Return empty string to trigger that.
789 default:
790 break;
791 }
792
793 return QString();
794}
795
797{
799 return QStringLiteral("position");
801 return QStringLiteral("rotation");
803 return QStringLiteral("scale");
805 return QStringLiteral("weight");
806
807 return QStringLiteral("unknown");
808}
809
810static std::pair<QString, QString> meshAssetName(const QSSGSceneDesc::Scene &scene, const QSSGSceneDesc::Mesh &meshNode, const QDir &outdir)
811{
812 // Returns {name, notValidReason}
813
814 const auto meshFolder = getMeshFolder();
815 const auto meshId = QSSGQmlUtilities::getIdForNode(meshNode);
816 const auto meshSourceName = QSSGQmlUtilities::getMeshSourceName(meshId);
817 Q_ASSERT(scene.meshStorage.size() > meshNode.idx);
818 const auto &mesh = scene.meshStorage.at(meshNode.idx);
819
820 // If a mesh folder does not exist, then create one
821 if (!outdir.exists(meshFolder) && !outdir.mkdir(meshFolder)) {
822 qDebug() << "Failed to create meshes folder at" << outdir;
823 return {}; // Error out
824 }
825
826 const QString path = outdir.path() + QDir::separator() + meshSourceName;
827 QFile file(path);
828 if (!file.open(QIODevice::WriteOnly)) {
829 return {QString(), QStringLiteral("Failed to find mesh at ") + path};
830 }
831
832 if (mesh.save(&file) == 0) {
833 return {};
834 }
835
836 return {meshSourceName, QString()};
837};
838
839static std::pair<QString, QString> copyTextureAsset(const QUrl &texturePath, OutputContext &output)
840{
841 // Returns {path, notValidReason}
842
843 // TODO: Use QUrl::resolved() instead of manual string manipulation
844 QString assetPath = output.outdir.isAbsolutePath(texturePath.path()) ? texturePath.toString() : texturePath.path();
845 QFileInfo fi(assetPath);
846 if (fi.isRelative() && !output.sourceDir.isEmpty()) {
847 fi = QFileInfo(output.sourceDir + QChar(u'/') + assetPath);
848 }
849 if (!fi.exists()) {
850 indent(output) << comment() << "Source texture path expected: " << getTextureFolder() + texturePath.fileName() << "\n";
851 return {QString(), QStringLiteral("Failed to find texture at ") + assetPath};
852 }
853
854 const auto mapsFolder = getTextureFolder();
855 // If a maps folder does not exist, then create one
856 if (!output.outdir.exists(mapsFolder) && !output.outdir.mkdir(mapsFolder)) {
857 qDebug() << "Failed to create maps folder at" << output.outdir;
858 return {}; // Error out
859 }
860
861 const QString relpath = mapsFolder + fi.fileName();
862 const auto newfilepath = QString(output.outdir.canonicalPath() + QDir::separator() + relpath);
863 if (!QFile::exists(newfilepath) && !QFile::copy(fi.canonicalFilePath(), newfilepath)) {
864 qDebug() << "Failed to copy file from" << fi.canonicalFilePath() << "to" << newfilepath;
865 return {};
866 }
867
868 return {relpath, QString()};
869};
870
871static QStringList expandComponents(const QString &value, QMetaType mt)
872{
873 static const QRegularExpression re(QLatin1String("^Qt.[a-z0-9]*\\‍(([0-9.e\\+\\-, ]*)\\‍)"));
874 Q_ASSERT(re.isValid());
875
876 switch (mt.id()) {
877 case QMetaType::QVector2D: {
878 QRegularExpressionMatch match = re.match(value);
879 if (match.hasMatch()) {
880 const auto comp = match.captured(1).split(QLatin1Char(','));
881 if (comp.size() == 2) {
882 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
883 QLatin1String(".y: ") + comp.at(1).trimmed() };
884 }
885 }
886 break;
887 }
888 case QMetaType::QVector3D: {
889 QRegularExpressionMatch match = re.match(value);
890 if (match.hasMatch()) {
891 const auto comp = match.captured(1).split(QLatin1Char(','));
892 if (comp.size() == 3) {
893 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
894 QLatin1String(".y: ") + comp.at(1).trimmed(),
895 QLatin1String(".z: ") + comp.at(2).trimmed() };
896 }
897 }
898 break;
899 }
900 case QMetaType::QVector4D: {
901 QRegularExpressionMatch match = re.match(value);
902 if (match.hasMatch()) {
903 const auto comp = match.captured(1).split(QLatin1Char(','));
904 if (comp.size() == 4) {
905 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
906 QLatin1String(".y: ") + comp.at(1).trimmed(),
907 QLatin1String(".z: ") + comp.at(2).trimmed(),
908 QLatin1String(".w: ") + comp.at(3).trimmed() };
909 }
910 }
911 break;
912 }
913 case QMetaType::QQuaternion: {
914 QRegularExpressionMatch match = re.match(value);
915 if (match.hasMatch()) {
916 const auto comp = match.captured(1).split(QLatin1Char(','));
917 if (comp.size() == 4) {
918 return { QLatin1String(".x: ") + comp.at(0).trimmed(),
919 QLatin1String(".y: ") + comp.at(1).trimmed(),
920 QLatin1String(".z: ") + comp.at(2).trimmed(),
921 QLatin1String(".scalar: ") + comp.at(3).trimmed() };
922 }
923 }
924 break;
925 }
926 default:
927 break;
928 }
929
930 return { value };
931}
932
933static QStringList expandComponentsPartially(const QString &value, QMetaType mt)
934{
935 // Workaround for DS
936 if (mt.id() != QMetaType::QQuaternion)
937 return expandComponents(value, mt);
938
939 return { value };
940}
941
943 bool ok = false;
944 QString name;
945 QString value;
947 bool isDynamicProperty = false;
949};
950
951static ValueToQmlResult valueToQml(const QSSGSceneDesc::Node &target, const QSSGSceneDesc::Property &property, OutputContext &output)
952{
953 ValueToQmlResult result;
954 if (property.value.isNull()) {
955 result.ok = false;
956 result.notValidReason = QStringLiteral("Property value is null");
957 return result;
958 }
959
960 const QVariant &value = property.value;
961 result.name = QString::fromUtf8(property.name);
963
964 // Built-in types
965 QString valueAsString = builtinQmlType(value);
966 if (valueAsString.size() > 0) {
967 result.value = valueAsString;
968 result.ok = true;
969 } else if (value.metaType().flags() & (QMetaType::IsEnumeration | QMetaType::IsUnsignedEnumeration)) {
970 static const auto qmlEnumString = [](const QLatin1String &element, const QString &enumString) {
971 return QStringLiteral("%1.%2").arg(element).arg(enumString);
972 };
973 QLatin1String qmlElementName(getQmlElementName(target));
974 QString enumValue = asString(value);
975 if (enumValue.size() > 0) {
976 result.value = qmlEnumString(qmlElementName, enumValue);
977 result.ok = true;
978 }
979 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Flag>()) {
980 QByteArray element(getQmlElementName(target));
981 if (element.size() > 0) {
982 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(value);
983 QByteArray keysString = flag.me.valueToKeys(int(flag.value));
984 if (keysString.size() > 0) {
985 keysString.prepend(element + '.');
986 QByteArray replacement(" | " + element + '.');
987 keysString.replace('|', replacement);
988 result.value = QString::fromLatin1(keysString);
989 result.ok = true;
990 }
991 }
992 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::NodeList *>()) {
993 const auto *list = qvariant_cast<QSSGSceneDesc::NodeList *>(value);
994 if (list->count > 0) {
995 const QString indentStr = indentString(output);
996 QSSGQmlScopedIndent scopedIndent(output);
997 const QString listIndentStr = indentString(output);
998
999 QString str;
1000 str.append(u"[\n");
1001
1002 for (int i = 0, end = list->count; i != end; ++i) {
1003 if (i != 0)
1004 str.append(u",\n");
1005 str.append(listIndentStr);
1006 str.append(getIdForNode(*(list->head[i])));
1007 }
1008
1009 str.append(u'\n' + indentStr + u']');
1010
1011 result.value = str;
1012 result.ok = true;
1013 }
1014 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::ListView *>()) {
1015 const auto &list = *qvariant_cast<QSSGSceneDesc::ListView *>(value);
1016 if (list.count > 0) {
1017 const QString indentStr = indentString(output);
1018 QSSGQmlScopedIndent scopedIndent(output);
1019 const QString listIndentStr = indentString(output);
1020
1021 QString str;
1022 str.append(u"[\n");
1023
1024 char *vptr = reinterpret_cast<char *>(list.data);
1025 auto size = list.mt.sizeOf();
1026
1027 for (int i = 0, end = list.count; i != end; ++i) {
1028 if (i != 0)
1029 str.append(u",\n");
1030
1031 const QVariant var{list.mt, reinterpret_cast<void *>(vptr + (size * i))};
1032 QString valueString = builtinQmlType(var);
1033 if (valueString.isEmpty())
1034 valueString = asString(var);
1035
1036 str.append(listIndentStr);
1037 str.append(valueString);
1038 }
1039
1040 str.append(u'\n' + indentStr + u']');
1041
1042 result.value = str;
1043 result.ok = true;
1044 }
1045 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Node *>()) {
1046 if (const auto node = qvariant_cast<QSSGSceneDesc::Node *>(value)) {
1047 // If this assert is triggerd it likely means that the node never got added
1048 // to the scene tree (see: addNode()) or that it's a type not handled as a resource, see:
1049 // writeQmlForResources()
1050 Q_ASSERT(node->id != 0);
1051 // The 'TextureData' node will have its data written out and become
1052 // a source url.
1053
1054 if (node->runtimeType == QSSGSceneDesc::Node::RuntimeType::TextureData) {
1055 result.name = QStringLiteral("source");
1056 result.value = getIdForNode(*node->scene->root) + QLatin1Char('.') + getIdForNode(*node);
1057 } else {
1058 result.value = getIdForNode(*node);
1059 }
1060 result.ok = true;
1061 }
1062 } else if (value.metaType() == QMetaType::fromType<QSSGSceneDesc::Mesh *>()) {
1063 if (const auto meshNode = qvariant_cast<const QSSGSceneDesc::Mesh *>(value)) {
1064 Q_ASSERT(meshNode->nodeType == QSSGSceneDesc::Node::Type::Mesh);
1065 Q_ASSERT(meshNode->scene);
1066 const auto &scene = *meshNode->scene;
1067 const auto& [meshSourceName, notValidReason] = meshAssetName(scene, *meshNode, output.outdir);
1068 result.notValidReason = notValidReason;
1069 if (!meshSourceName.isEmpty()) {
1070 result.value = toQuotedString(meshSourceName);
1071 result.ok = true;
1072 }
1073 }
1074 } else if (value.metaType() == QMetaType::fromType<QUrl>()) {
1075 if (const auto url = qvariant_cast<QUrl>(value); !url.isEmpty()) {
1076 // We need to adjust source url(s) as those should contain the canonical path
1077 QString path;
1078 if (QSSGRenderGraphObject::isTexture(target.runtimeType)) {
1079 const auto& [relpath, notValidReason] = copyTextureAsset(url, output);
1080 result.notValidReason = notValidReason;
1081 if (!relpath.isEmpty()) {
1082 path = relpath;
1083 }
1084 } else
1085 path = url.path();
1086
1087 if (!path.isEmpty()) {
1088 result.value = toQuotedString(path);
1089 result.ok = true;
1090 }
1091 }
1092 } else if (target.runtimeType == QSSGSceneDesc::Material::RuntimeType::CustomMaterial) {
1093 // Workaround the TextureInput item that wraps textures for the Custom material.
1094 if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::Texture *>()) {
1095 if (const auto texture = qvariant_cast<QSSGSceneDesc::Texture *>(value)) {
1096 Q_ASSERT(QSSGRenderGraphObject::isTexture(texture->runtimeType));
1097 result.value = QLatin1String("TextureInput { texture: ") +
1098 getIdForNode(*texture) + QLatin1String(" }");
1099 result.ok = true;
1100 }
1101 }
1102 } else if (value.metaType() == QMetaType::fromType<QString>()) {
1103 // Plain strings in the scenedesc should map to QML string values
1104 result.value = toQuotedString(value.toString());
1105 result.ok = true;
1106 } else {
1107 result.notValidReason = QStringLiteral("Unsupported value type: ") + QString::fromUtf8(value.metaType().name());
1108 qWarning() << result.notValidReason;
1109 result.ok = false;
1110 }
1111
1112 if (result.ok && (output.options & OutputContext::Options::ExpandValueComponents)) {
1113 result.expandedProperties = ((output.options & OutputContext::Options::DesignStudioWorkarounds) == OutputContext::Options::DesignStudioWorkarounds)
1114 ? expandComponentsPartially(result.value, value.metaType())
1115 : expandComponents(result.value, value.metaType());
1116 }
1117
1118 return result;
1119}
1120
1121static void writeNodeProperties(const QSSGSceneDesc::Node &node, OutputContext &output)
1122{
1123 QSSGQmlScopedIndent scopedIndent(output);
1124
1125 indent(output) << u"id: "_s << getIdForNode(node) << u'\n';
1126
1127 // Set Object Name if one exists
1128 if (node.name.size()) {
1129 const QString objectName = QString::fromLocal8Bit(node.name);
1130 if (!objectName.startsWith(u'*'))
1131 indent(output) << u"objectName: \""_s << node.name << u"\"\n"_s;
1132 }
1133
1134 const auto &properties = node.properties;
1135 auto it = properties.begin();
1136 const auto end = properties.end();
1137 for (; it != end; ++it) {
1138 const auto &property = *it;
1139
1140 const ValueToQmlResult result = valueToQml(node, *property, output);
1141 if (result.ok) {
1142 if (result.isDynamicProperty) {
1143 indent(output) << "property " << typeName(property->value.metaType()).toByteArray() << ' ' << result.name << u": "_s << result.value << u'\n';
1144 } else if (!QSSGQmlUtilities::PropertyMap::instance()->isDefaultValue(node.runtimeType, property->name, property->value)) {
1145 if (result.expandedProperties.size() > 1) {
1146 for (const auto &va : result.expandedProperties)
1147 indent(output) << result.name << va << u'\n';
1148 } else {
1149 indent(output) << result.name << u": "_s << result.value << u'\n';
1150 }
1151 }
1152 } else if (!result.isDynamicProperty) {
1153 QString message = u"Skipped property: "_s + QString::fromUtf8(property->name);
1154 if (!result.notValidReason.isEmpty())
1155 message.append(u", reason: "_s + result.notValidReason);
1156 qDebug() << message;
1157 indent(output) << comment() << message + u'\n';
1158 }
1159 }
1160}
1161
1162static void writeQml(const QSSGSceneDesc::Node &transform, OutputContext &output)
1163{
1164 using namespace QSSGSceneDesc;
1165 Q_ASSERT(transform.nodeType == QSSGSceneDesc::Node::Type::Transform && transform.runtimeType == QSSGSceneDesc::Node::RuntimeType::Node);
1166 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::Node>() << blockBegin(output);
1167 writeNodeProperties(transform, output);
1168}
1169
1170void writeQml(const QSSGSceneDesc::Material &material, OutputContext &output)
1171{
1172 using namespace QSSGSceneDesc;
1173 Q_ASSERT(material.nodeType == QSSGSceneDesc::Model::Type::Material);
1174 if (material.runtimeType == QSSGSceneDesc::Model::RuntimeType::SpecularGlossyMaterial) {
1175 indent(output) << qmlElementName<Material::RuntimeType::SpecularGlossyMaterial>() << blockBegin(output);
1176 } else if (material.runtimeType == Model::RuntimeType::PrincipledMaterial) {
1177 indent(output) << qmlElementName<Material::RuntimeType::PrincipledMaterial>() << blockBegin(output);
1178 } else if (material.runtimeType == Material::RuntimeType::CustomMaterial) {
1179 indent(output) << qmlElementName<Material::RuntimeType::CustomMaterial>() << blockBegin(output);
1180 } else if (material.runtimeType == Material::RuntimeType::SpecularGlossyMaterial) {
1181 indent(output) << qmlElementName<Material::RuntimeType::SpecularGlossyMaterial>() << blockBegin(output);
1182 } else {
1183 Q_UNREACHABLE();
1184 }
1185
1186 writeNodeProperties(material, output);
1187}
1188
1189static void writeQml(const QSSGSceneDesc::Model &model, OutputContext &output)
1190{
1191 using namespace QSSGSceneDesc;
1192 Q_ASSERT(model.nodeType == Node::Type::Model);
1193 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::Model>() << blockBegin(output);
1194 writeNodeProperties(model, output);
1195}
1196
1197static void writeQml(const QSSGSceneDesc::Camera &camera, OutputContext &output)
1198{
1199 using namespace QSSGSceneDesc;
1200 Q_ASSERT(camera.nodeType == Node::Type::Camera);
1201 if (camera.runtimeType == Camera::RuntimeType::PerspectiveCamera)
1202 indent(output) << qmlElementName<Camera::RuntimeType::PerspectiveCamera>() << blockBegin(output);
1203 else if (camera.runtimeType == Camera::RuntimeType::OrthographicCamera)
1204 indent(output) << qmlElementName<Camera::RuntimeType::OrthographicCamera>() << blockBegin(output);
1205 else
1206 Q_UNREACHABLE();
1207 writeNodeProperties(camera, output);
1208}
1209
1210static void writeQml(const QSSGSceneDesc::Texture &texture, OutputContext &output)
1211{
1212 using namespace QSSGSceneDesc;
1213 Q_ASSERT(texture.nodeType == Node::Type::Texture && QSSGRenderGraphObject::isTexture(texture.runtimeType));
1214 if (texture.runtimeType == Texture::RuntimeType::Image2D)
1215 indent(output) << qmlElementName<Texture::RuntimeType::Image2D>() << blockBegin(output);
1216 else if (texture.runtimeType == Texture::RuntimeType::ImageCube)
1217 indent(output) << qmlElementName<Texture::RuntimeType::ImageCube>() << blockBegin(output);
1218 writeNodeProperties(texture, output);
1219}
1220
1221static void writeQml(const QSSGSceneDesc::Skin &skin, OutputContext &output)
1222{
1223 using namespace QSSGSceneDesc;
1224 Q_ASSERT(skin.nodeType == Node::Type::Skin && skin.runtimeType == Node::RuntimeType::Skin);
1225 indent(output) << qmlElementName<Node::RuntimeType::Skin>() << blockBegin(output);
1226 writeNodeProperties(skin, output);
1227}
1228
1229static void writeQml(const QSSGSceneDesc::MorphTarget &morphTarget, OutputContext &output)
1230{
1231 using namespace QSSGSceneDesc;
1232 Q_ASSERT(morphTarget.nodeType == Node::Type::MorphTarget);
1233 indent(output) << qmlElementName<QSSGSceneDesc::Node::RuntimeType::MorphTarget>() << blockBegin(output);
1234 writeNodeProperties(morphTarget, output);
1235}
1236
1237QString getTextureSourceName(const QString &name, const QString &fmt)
1238{
1239 const auto textureFolder = getTextureFolder();
1240
1241 const auto sanitizedName = QSSGQmlUtilities::sanitizeQmlId(name);
1242 const auto ext = (fmt.length() != 3) ? u".png"_s
1243 : u"."_s + fmt;
1244
1245 return QString(textureFolder + sanitizedName + ext);
1246}
1247
1248static QString outputTextureAsset(const QSSGSceneDesc::TextureData &textureData, const QDir &outdir)
1249{
1250 if (textureData.data.isEmpty())
1251 return QString();
1252
1253 const auto mapsFolder = getTextureFolder();
1254 const auto id = getIdForNode(textureData);
1255 const QString textureSourceName = getTextureSourceName(id, QString::fromUtf8(textureData.fmt));
1256
1257 const bool isCompressed = ((textureData.flgs & quint8(QSSGSceneDesc::TextureData::Flags::Compressed)) != 0);
1258
1259 // If a maps folder does not exist, then create one
1260 if (!outdir.exists(mapsFolder) && !outdir.mkdir(mapsFolder))
1261 return QString(); // Error out
1262
1263 const auto imagePath = QString(outdir.path() + QDir::separator() + textureSourceName);
1264
1265 if (isCompressed) {
1266 QFile file(imagePath);
1267 if (!file.open(QIODevice::WriteOnly)) {
1268 qWarning("Failed to open file %s: %s",
1269 qPrintable(file.fileName()), qPrintable(file.errorString()));
1270 return {};
1271 } else {
1272 file.write(textureData.data);
1273 file.close();
1274 }
1275 } else {
1276 const auto &texData = textureData.data;
1277 const auto &size = textureData.sz;
1278 QImage image;
1279 image = QImage(reinterpret_cast<const uchar *>(texData.data()), size.width(), size.height(), QImage::Format::Format_RGBA8888);
1280 if (!image.save(imagePath))
1281 return QString();
1282 }
1283
1284 return textureSourceName;
1285}
1286
1287static void writeQml(const QSSGSceneDesc::TextureData &textureData, OutputContext &output)
1288{
1289 using namespace QSSGSceneDesc;
1290 Q_ASSERT(textureData.nodeType == Node::Type::Texture && textureData.runtimeType == Node::RuntimeType::TextureData);
1291
1292 QString textureSourcePath = outputTextureAsset(textureData, output.outdir);
1293
1294 static const auto writeProperty = [](const QString &type, const QString &name, const QString &value) {
1295 return QString::fromLatin1("property %1 %2: %3").arg(type, name, value);
1296 };
1297
1298 if (!textureSourcePath.isEmpty()) {
1299 const auto type = QLatin1String("url");
1300 const auto name = getIdForNode(textureData);
1301
1302 indent(output) << writeProperty(type, name, toQuotedString(textureSourcePath)) << '\n';
1303 }
1304}
1305
1306static void writeQml(const QSSGSceneDesc::Light &light, OutputContext &output)
1307{
1308 using namespace QSSGSceneDesc;
1309 Q_ASSERT(light.nodeType == Node::Type::Light);
1310 if (light.runtimeType == Light::RuntimeType::DirectionalLight)
1311 indent(output) << qmlElementName<Light::RuntimeType::DirectionalLight>() << blockBegin(output);
1312 else if (light.runtimeType == Light::RuntimeType::SpotLight)
1313 indent(output) << qmlElementName<Light::RuntimeType::SpotLight>() << blockBegin(output);
1314 else if (light.runtimeType == Light::RuntimeType::PointLight)
1315 indent(output) << qmlElementName<Light::RuntimeType::PointLight>() << blockBegin(output);
1316 else
1317 Q_UNREACHABLE();
1318 writeNodeProperties(light, output);
1319}
1320
1321static void writeQml(const QSSGSceneDesc::Skeleton &skeleton, OutputContext &output)
1322{
1323 using namespace QSSGSceneDesc;
1324 Q_ASSERT(skeleton.nodeType == Node::Type::Skeleton && skeleton.runtimeType == Node::RuntimeType::Skeleton);
1325 indent(output) << qmlElementName<Node::RuntimeType::Skeleton>() << blockBegin(output);
1326 writeNodeProperties(skeleton, output);
1327}
1328
1329static void writeQml(const QSSGSceneDesc::Joint &joint, OutputContext &output)
1330{
1331 using namespace QSSGSceneDesc;
1332 Q_ASSERT(joint.nodeType == Node::Type::Joint && joint.runtimeType == Node::RuntimeType::Joint);
1333 indent(output) << qmlElementName<Node::RuntimeType::Joint>() << blockBegin(output);
1334 writeNodeProperties(joint, output);
1335}
1336
1337static void writeQmlForResourceNode(const QSSGSceneDesc::Node &node, OutputContext &output)
1338{
1339 using namespace QSSGSceneDesc;
1340 Q_ASSERT(output.type == OutputContext::Resource);
1341 Q_ASSERT(QSSGRenderGraphObject::isResource(node.runtimeType) || node.nodeType == Node::Type::Mesh || node.nodeType == Node::Type::Skeleton);
1342
1343 const bool processNode = !node.properties.isEmpty() || (output.type == OutputContext::Resource);
1344 if (processNode) {
1345 QSSGQmlScopedIndent scopedIndent(output);
1346 switch (node.nodeType) {
1347 case Node::Type::Skin:
1348 writeQml(static_cast<const Skin &>(node), output);
1349 break;
1350 case Node::Type::MorphTarget:
1351 writeQml(static_cast<const MorphTarget &>(node), output);
1352 break;
1353 case Node::Type::Skeleton:
1354 writeQml(static_cast<const Skeleton &>(node), output);
1355 break;
1356 case Node::Type::Texture:
1357 if (node.runtimeType == Node::RuntimeType::Image2D)
1358 writeQml(static_cast<const Texture &>(node), output);
1359 else if (node.runtimeType == Node::RuntimeType::ImageCube)
1360 writeQml(static_cast<const Texture &>(node), output);
1361 else if (node.runtimeType == Node::RuntimeType::TextureData)
1362 writeQml(static_cast<const TextureData &>(node), output);
1363 else
1364 Q_UNREACHABLE();
1365 break;
1366 case Node::Type::Material:
1367 writeQml(static_cast<const Material &>(node), output);
1368 break;
1369 case Node::Type::Mesh:
1370 // Only handled as a property (see: valueToQml())
1371 break;
1372 default:
1373 qWarning("Unhandled resource type \'%d\'?", int(node.runtimeType));
1374 break;
1375 }
1376 }
1377
1378 // Do something more convenient if this starts expending to more types...
1379 // NOTE: The TextureData type is written out as a url property...
1380 const bool skipBlockEnd = (node.runtimeType == Node::RuntimeType::TextureData || node.nodeType == Node::Type::Mesh);
1381 if (!skipBlockEnd && processNode && output.scopeDepth != 0) {
1382 QSSGQmlScopedIndent scopedIndent(output);
1383 indent(output) << blockEnd(output);
1384 }
1385}
1386
1387static void writeQmlForNode(const QSSGSceneDesc::Node &node, OutputContext &output)
1388{
1389 using namespace QSSGSceneDesc;
1390
1391 const bool processNode = !(node.properties.isEmpty() && node.children.isEmpty())
1392 || (output.type == OutputContext::Resource);
1393 if (processNode) {
1394 QSSGQmlScopedIndent scopedIndent(output);
1395 switch (node.nodeType) {
1396 case Node::Type::Skeleton:
1397 writeQml(static_cast<const Skeleton &>(node), output);
1398 break;
1399 case Node::Type::Joint:
1400 writeQml(static_cast<const Joint &>(node), output);
1401 break;
1402 case Node::Type::Light:
1403 writeQml(static_cast<const Light &>(node), output);
1404 break;
1405 case Node::Type::Transform:
1406 writeQml(node, output);
1407 break;
1408 case Node::Type::Camera:
1409 writeQml(static_cast<const Camera &>(node), output);
1410 break;
1411 case Node::Type::Model:
1412 writeQml(static_cast<const Model &>(node), output);
1413 break;
1414 default:
1415 break;
1416 }
1417 }
1418
1419 for (const auto &cld : node.children) {
1420 if (!QSSGRenderGraphObject::isResource(cld->runtimeType) && output.type == OutputContext::NodeTree) {
1421 QSSGQmlScopedIndent scopedIndent(output);
1422 writeQmlForNode(*cld, output);
1423 }
1424 }
1425
1426 // Do something more convenient if this starts expending to more types...
1427 // NOTE: The TextureData type is written out as a url property...
1428 const bool skipBlockEnd = (node.runtimeType == Node::RuntimeType::TextureData || node.nodeType == Node::Type::Mesh);
1429 if (!skipBlockEnd && processNode && output.scopeDepth != 0) {
1430 QSSGQmlScopedIndent scopedIndent(output);
1431 indent(output) << blockEnd(output);
1432 }
1433}
1434
1435void writeQmlForResources(const QSSGSceneDesc::Scene::ResourceNodes &resources, OutputContext &output)
1436{
1437 auto sortedResources = resources;
1438 std::sort(sortedResources.begin(), sortedResources.end(), [](const QSSGSceneDesc::Node *a, const QSSGSceneDesc::Node *b) {
1439 using RType = QSSGSceneDesc::Node::RuntimeType;
1440 if (a->runtimeType == RType::TextureData && b->runtimeType != RType::TextureData)
1441 return true;
1442 if (a->runtimeType == RType::ImageCube && (b->runtimeType != RType::TextureData && b->runtimeType != RType::ImageCube))
1443 return true;
1444 if (a->runtimeType == RType::Image2D && (b->runtimeType != RType::TextureData && b->runtimeType != RType::Image2D))
1445 return true;
1446
1447 return false;
1448 });
1449 for (const auto &res : std::as_const(sortedResources))
1450 writeQmlForResourceNode(*res, output);
1451}
1452
1453static void generateKeyframeData(const QSSGSceneDesc::Animation::Channel &channel, QByteArray &keyframeData)
1454{
1455#ifdef QT_QUICK3D_ENABLE_RT_ANIMATIONS
1456 QCborStreamWriter writer(&keyframeData);
1457 // Start root array
1458 writer.startArray();
1459 // header name
1460 writer.append("QTimelineKeyframes");
1461 // file version. Increase this if the format changes.
1462 const int keyframesDataVersion = 1;
1463 writer.append(keyframesDataVersion);
1464 writer.append(int(channel.keys.at(0)->getValueQMetaType()));
1465
1466 // Start Keyframes array
1467 writer.startArray();
1468 quint8 compEnd = quint8(channel.keys.at(0)->getValueType());
1469 bool isQuaternion = false;
1470 if (compEnd == quint8(QSSGSceneDesc::Animation::KeyPosition::ValueType::Quaternion)) {
1471 isQuaternion = true;
1472 compEnd = 3;
1473 } else {
1474 compEnd++;
1475 }
1476 for (const auto &key : channel.keys) {
1477 writer.append(key->time);
1478 // Easing always linear
1479 writer.append(QEasingCurve::Linear);
1480 if (isQuaternion)
1481 writer.append(key->value[3]);
1482 for (quint8 i = 0; i < compEnd; ++i)
1483 writer.append(key->value[i]);
1484 }
1485 // End Keyframes array
1486 writer.endArray();
1487 // End root array
1488 writer.endArray();
1489#else
1490 Q_UNUSED(channel)
1491 Q_UNUSED(keyframeData)
1492#endif // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1493}
1494
1495QPair<QString, QString> writeQmlForAnimation(const QSSGSceneDesc::Animation &anim, qsizetype index, OutputContext &output, bool useBinaryKeyframes = true, bool generateTimelineAnimations = true)
1496{
1497 indent(output) << "Timeline {\n";
1498
1499 QSSGQmlScopedIndent scopedIndent(output);
1500 // The duration property of the TimelineAnimation is an int...
1501 const int duration = qCeil(anim.length);
1502 // Use the same name for objectName and id
1503 const QString animationId = getIdForAnimation(anim.name);
1504 indent(output) << "id: " << animationId << "\n";
1505 QString animationName = animationId;
1506 if (!anim.name.isEmpty())
1507 animationName = QString::fromLocal8Bit(anim.name);
1508 indent(output) << "objectName: \"" << animationName << "\"\n";
1509 indent(output) << "property real framesPerSecond: " << anim.framesPerSecond << "\n";
1510 indent(output) << "startFrame: 0\n";
1511 indent(output) << "endFrame: " << duration << "\n";
1512 indent(output) << "currentFrame: 0\n";
1513 // Only generate the TimelineAnimation component here if requested
1514 // enabled is only set to true up front if we expect to autoplay
1515 // the generated TimelineAnimation
1516 if (generateTimelineAnimations) {
1517 indent(output) << "enabled: true\n";
1518 indent(output) << "animations: TimelineAnimation {\n";
1519 {
1520 QSSGQmlScopedIndent scopedIndent(output);
1521 indent(output) << "duration: " << duration << "\n";
1522 indent(output) << "from: 0\n";
1523 indent(output) << "to: " << duration << "\n";
1524 indent(output) << "running: true\n";
1525 indent(output) << "loops: Animation.Infinite\n";
1526 }
1527 indent(output) << blockEnd(output);
1528 }
1529
1530 for (const auto &channel : anim.channels) {
1531 QString id = getIdForNode(*channel->target);
1532 QString propertyName = asString(channel->targetProperty);
1533
1534 indent(output) << "KeyframeGroup {\n";
1535 {
1536 QSSGQmlScopedIndent scopedIndent(output);
1537 indent(output) << "target: " << id << "\n";
1538 indent(output) << "property: " << toQuotedString(propertyName) << "\n";
1539 if (useBinaryKeyframes && channel->keys.size() != 1) {
1540 const auto animFolder = getAnimationFolder();
1541 const auto animSourceName = getAnimationSourceName(id, propertyName, index);
1542 if (!output.outdir.exists(animFolder) && !output.outdir.mkdir(animFolder)) {
1543 // Make a warning
1544 continue;
1545 }
1546 QFile file(output.outdir.path() + QDir::separator() + animSourceName);
1547 if (!file.open(QIODevice::WriteOnly))
1548 continue;
1549 QByteArray keyframeData;
1550 // It is possible to store this keyframeData but we have to consider
1551 // all the cases including runtime only or writeQml only.
1552 // For now, we will generate it for each case.
1553 generateKeyframeData(*channel, keyframeData);
1554 file.write(keyframeData);
1555 file.close();
1556 indent(output) << "keyframeSource: " << toQuotedString(animSourceName) << "\n";
1557 } else {
1558 Q_ASSERT(!channel->keys.isEmpty());
1559 for (const auto &key : channel->keys) {
1560 indent(output) << "Keyframe {\n";
1561 {
1562 QSSGQmlScopedIndent scopedIndent(output);
1563 indent(output) << "frame: " << key->time << "\n";
1564 indent(output) << "value: " << variantToQml(key->getValue()) << "\n";
1565 }
1566 indent(output) << blockEnd(output);
1567 }
1568 }
1569 }
1570 indent(output) << blockEnd(output);
1571 }
1572 return {animationName, animationId};
1573}
1574
1575void writeQml(const QSSGSceneDesc::Scene &scene, QTextStream &stream, const QDir &outdir, const QJsonObject &optionsObject)
1576{
1577 static const auto checkBooleanOption = [](const QLatin1String &optionName, const QJsonObject &options, bool defaultValue = false) {
1578 const auto it = options.constFind(optionName);
1579 const auto end = options.constEnd();
1580 QJsonValue value;
1581 if (it != end) {
1582 if (it->isObject())
1583 value = it->toObject().value(QLatin1String("value"));
1584 else
1585 value = it.value();
1586 }
1587 return value.toBool(defaultValue);
1588 };
1589
1590 auto root = scene.root;
1591 Q_ASSERT(root);
1592
1593 QJsonObject options = optionsObject;
1594
1595 if (auto it = options.constFind(QLatin1String("options")), end = options.constEnd(); it != end)
1596 options = it->toObject();
1597
1598 quint8 outputOptions{ OutputContext::Options::None };
1599 if (checkBooleanOption(QLatin1String("expandValueComponents"), options))
1601
1602 // Workaround for design studio type components
1603 if (checkBooleanOption(QLatin1String("designStudioWorkarounds"), options))
1605
1606 const bool useBinaryKeyframes = checkBooleanOption("useBinaryKeyframes"_L1, options);
1607 const bool generateTimelineAnimations = !checkBooleanOption("manualAnimations"_L1, options);
1608
1609 OutputContext output { stream, outdir, scene.sourceDir, 0, OutputContext::Header, outputOptions };
1610
1611 writeImportHeader(output, scene.animations.count() > 0);
1612
1614 writeQml(*root, output); // Block scope will be left open!
1615 stream << "\n";
1616 stream << indent() << "// Resources\n";
1618 writeQmlForResources(scene.resources, output);
1620 stream << "\n";
1621 stream << indent() << "// Nodes:\n";
1622 for (const auto &cld : root->children)
1623 writeQmlForNode(*cld, output);
1624
1625 // animations
1626 qsizetype animId = 0;
1627 stream << "\n";
1628 stream << indent() << "// Animations:\n";
1629 QList<QPair<QString, QString>> animationMap;
1630 for (const auto &cld : scene.animations) {
1631 QSSGQmlScopedIndent scopedIndent(output);
1632 auto mapValues = writeQmlForAnimation(*cld, animId++, output, useBinaryKeyframes, generateTimelineAnimations);
1633 animationMap.append(mapValues);
1634 indent(output) << blockEnd(output);
1635 }
1636
1637 if (!generateTimelineAnimations) {
1638 // Expose a map of timelines
1639 stream << "\n";
1640 stream << indent() << "// An exported mapping of Timelines (--manualAnimations)\n";
1641 stream << indent() << "property var timelineMap: {\n";
1642 QSSGQmlScopedIndent scopedIndent(output);
1643 for (const auto &mapValues : animationMap) {
1644 QSSGQmlScopedIndent scopedIndent(output);
1645 indent(output) << "\"" << mapValues.first << "\": " << mapValues.second << ",\n";
1646 }
1647 indent(output) << blockEnd(output);
1648 stream << indent() << "// A simple list of Timelines (--manualAnimations)\n";
1649 stream << indent() << "property var timelineList: [\n";
1650 for (const auto &mapValues : animationMap) {
1651 QSSGQmlScopedIndent scopedIndent(output);
1652 indent(output) << mapValues.second << ",\n";
1653 }
1654 indent(output) << "]\n";
1655 }
1656
1657
1658 // close the root
1659 indent(output) << blockEnd(output);
1660}
1661
1662void createTimelineAnimation(const QSSGSceneDesc::Animation &anim, QObject *parent, bool isEnabled, bool useBinaryKeyframes)
1663{
1664#ifdef QT_QUICK3D_ENABLE_RT_ANIMATIONS
1665 auto timeline = new QQuickTimeline(parent);
1666 auto timelineKeyframeGroup = timeline->keyframeGroups();
1667 for (const auto &channel : anim.channels) {
1668 auto keyframeGroup = new QQuickKeyframeGroup(timeline);
1669 keyframeGroup->setTargetObject(channel->target->obj);
1670 keyframeGroup->setProperty(asString(channel->targetProperty));
1671
1672 Q_ASSERT(!channel->keys.isEmpty());
1673 if (useBinaryKeyframes) {
1674 QByteArray keyframeData;
1675 generateKeyframeData(*channel, keyframeData);
1676
1677 keyframeGroup->setKeyframeData(keyframeData);
1678 } else {
1679 auto keyframes = keyframeGroup->keyframes();
1680 for (const auto &key : channel->keys) {
1681 auto keyframe = new QQuickKeyframe(keyframeGroup);
1682 keyframe->setFrame(key->time);
1683 keyframe->setValue(key->getValue());
1684 keyframes.append(&keyframes, keyframe);
1685 }
1686 }
1687 (qobject_cast<QQmlParserStatus *>(keyframeGroup))->componentComplete();
1688 timelineKeyframeGroup.append(&timelineKeyframeGroup, keyframeGroup);
1689 }
1690 timeline->setEndFrame(anim.length);
1691 timeline->setEnabled(isEnabled);
1692
1693 auto timelineAnimation = new QQuickTimelineAnimation(timeline);
1694 timelineAnimation->setObjectName(anim.name);
1695 timelineAnimation->setDuration(int(anim.length));
1696 timelineAnimation->setFrom(0.0f);
1697 timelineAnimation->setTo(anim.length);
1698 timelineAnimation->setLoops(QQuickTimelineAnimation::Infinite);
1699 timelineAnimation->setTargetObject(timeline);
1700
1701 (qobject_cast<QQmlParserStatus *>(timeline))->componentComplete();
1702
1703 timelineAnimation->setRunning(true);
1704#else // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1705 Q_UNUSED(anim)
1706 Q_UNUSED(parent)
1707 Q_UNUSED(isEnabled)
1708 Q_UNUSED(useBinaryKeyframes)
1709#endif // QT_QUICK3D_ENABLE_RT_ANIMATIONS
1710}
1711
1712void writeQmlComponent(const QSSGSceneDesc::Node &node, QTextStream &stream, const QDir &outDir)
1713{
1714 using namespace QSSGSceneDesc;
1715
1716 QSSG_ASSERT(node.scene != nullptr, return);
1717
1718 if (node.runtimeType == Material::RuntimeType::CustomMaterial) {
1719 QString sourceDir = node.scene->sourceDir;
1720 OutputContext output { stream, outDir, sourceDir, 0, OutputContext::Resource };
1721 writeImportHeader(output);
1722 writeQml(static_cast<const Material &>(node), output);
1723 // Resources, if any, are written out as properties on the component
1724 const auto &resources = node.scene->resources;
1725 writeQmlForResources(resources, output);
1726 indent(output) << blockEnd(output);
1727 } else {
1728 Q_UNREACHABLE(); // Only implemented for Custom material at this point.
1729 }
1730}
1731
1732}
1733
1734QT_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:31
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:330
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 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 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 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)
static void writeNodeProperties(const QSSGSceneDesc::Node &node, OutputContext &output)
QPair< QString, QString > writeQmlForAnimation(const QSSGSceneDesc::Animation &anim, qsizetype index, OutputContext &output, bool useBinaryKeyframes=true, bool generateTimelineAnimations=true)
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 * getQmlElementName(const QSSGSceneDesc::Node &node)
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 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.