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