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
qssgsceneedit.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 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
7
8#include <QtGui/QGuiApplication>
9
10#include <QtCore/QVariant>
11#include <QtCore/QHash>
12#include <QtCore/QMetaProperty>
13#include <QtCore/QUrl>
14
15#include <QtCore/QJsonObject>
16#include <QtCore/QJsonDocument>
17#include <QtCore/QJsonArray>
18
19#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
20#include <QtQuick3DAssetUtils/private/qssgscenedesc_p.h>
21#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
22#include <QtQuick3DAssetUtils/private/qssgrtutilities_p.h>
23
25using namespace Qt::Literals::StringLiterals;
26namespace QSSGQmlUtilities {
27
28static const char* typeNames[] =
29{
30 "Transform",
31 "Camera",
32 "Model",
33 "Texture",
34 "Material",
35 "Light",
36 "Mesh",
37 "Skin",
38 "Skeleton",
39 "Joint",
40 "MorphTarget",
41 "ERROR"
42};
43
44static constexpr qsizetype nNodeTypes = std::size(typeNames) - 1;
45
46static QSSGSceneDesc::Node::Type nodeTypeFromName(const QByteArrayView &typeName)
47{
48 int i = 0;
49 while (i < nNodeTypes) {
50 if (typeName == typeNames[i])
51 break;
52 ++i;
53 }
54 return QSSGSceneDesc::Node::Type(i);
55}
56
57static void replaceReferencesToResource(QSSGSceneDesc::Node *node, QSSGSceneDesc::Node *resource, QSSGSceneDesc::Node *replacement)
58{
59 for (auto *prop : node->properties) {
60 auto &val = prop->value;
61 if (qvariant_cast<QSSGSceneDesc::Node *>(val) == resource) {
62 if (replacement)
63 val = QVariant::fromValue(replacement);
64 }
65 if (val.metaType().id() == qMetaTypeId<QSSGSceneDesc::NodeList *>()) {
66 const auto &list = *qvariant_cast<QSSGSceneDesc::NodeList *>(val);
67 for (int i = 0, end = list.count; i != end; ++i) {
68 if (list.head[i] == resource) {
69 list.head[i] = replacement;
70 }
71 }
72 }
73 }
74 for (auto *child : node->children)
75 replaceReferencesToResource(child, resource, replacement);
76}
77
78// TODO: optimize this by using a hashmap or similar
79static QSSGSceneDesc::Node *findNode(QSSGSceneDesc::Node *root, const QByteArrayView name,
80 QSSGSceneDesc::Node::Type type, QSSGSceneDesc::Node **parent = nullptr)
81{
82 if (!root || name.isEmpty())
83 return nullptr;
84
85 if (root->name == name && root->nodeType == type)
86 return root;
87
88 for (auto *child : root->children) {
89 if (auto *ret = findNode(child, name, type, parent)) {
90 if (parent && !*parent)
91 *parent = root;
92 return ret;
93 }
94 }
95 return nullptr;
96}
97
98static QSSGSceneDesc::Node *findResource(const QSSGSceneDesc::Scene *scene, const QByteArrayView &name, QSSGSceneDesc::Node::Type nodeType)
99{
100 if (name.isEmpty())
101 return nullptr; // Empty strings by definition means nothing
102 for (auto *resource : scene->resources) {
103 if (resource->name == name && resource->nodeType == nodeType)
104 return resource;
105 }
106
107 return nullptr;
108}
109
111typedef bool NodeFilter(QSSGSceneDesc::Node *);
112
113static NodeSet flattenTree(QSSGSceneDesc::Node *node, NodeFilter *excludeFunction = nullptr)
114{
115 NodeSet ret = { node };
116 for (auto *child : node->children)
117 if (!excludeFunction || !excludeFunction(child))
118 ret.unite(flattenTree(child));
119 return ret;
120}
121
122static void unlinkChild(QSSGSceneDesc::Node *child, QSSGSceneDesc::Node *parent)
123{
124 parent->children.removeOne(child);
125}
126
127static void removeFromAnimation(QSSGSceneDesc::Animation *animation, const NodeSet &nodes)
128{
129 auto isTargeted = [nodes](QSSGSceneDesc::Animation::Channel *channel) { return nodes.contains(channel->target); };
130 const auto end_it = animation->channels.end();
131 auto remove_it = std::remove_if(animation->channels.begin(), end_it, isTargeted);
132 for (auto it = remove_it; it != end_it; ++it)
133 delete *it;
134 animation->channels.erase(remove_it, end_it);
135}
136
137static void deleteTree(QSSGSceneDesc::Node *node)
138{
139 const auto children = flattenTree(node);
140 for (auto *animation : node->scene->animations)
141 removeFromAnimation(animation, children);
142 for (auto *child : children)
143 delete child;
144}
145
146static void removeProperty(QSSGSceneDesc::Node *node, const QByteArrayView &name)
147{
148 auto *propList = &node->properties;
149
150 auto findName = [name](QSSGSceneDesc::Property *p) { return p->name == name; };
151 auto it = std::find_if(propList->begin(), propList->end(), findName);
152 if (it != propList->end()) {
153 QSSGSceneDesc::Property *p = *it;
154 propList->erase(it);
155 delete p;
156 }
157}
158
159static QSSGSceneDesc::Node *nodeFromJson(const QSSGSceneDesc::Scene *scene, const QJsonObject &nodeRef)
160{
161 auto it = nodeRef.constBegin();
162 if (it == nodeRef.constEnd())
163 return nullptr;
164 auto nodeType = nodeTypeFromName(it.key().toUtf8());
165 auto nodeName = it.value().toString().toUtf8();
166 auto *node = findResource(scene, nodeName, nodeType);
167 if (!node)
168 node = findNode(scene->root, nodeName, nodeType);
169 return node;
170}
171
172static QSSGSceneDesc::NodeList *nodeListFromJson(const QSSGSceneDesc::Scene *scene, const QJsonArray &array)
173{
174 QVarLengthArray<QSSGSceneDesc::Node *> nodes;
175
176 for (auto json : array) {
177 auto *node = nodeFromJson(scene, json.toObject());
178 if (!node) {
179 qWarning() << "Could not find node for" << json;
180 continue;
181 }
182 nodes.append(node);
183 }
184 auto *nodeList = new QSSGSceneDesc::NodeList(reinterpret_cast<void **>(nodes.data()), nodes.count());
185 return nodeList;
186}
187
188/*
189 JSON format
190
191 Node reference: {"<nodeTypeName>": "<name>"}
192 URL: {"url": "<filepath>"}
193 List: [ {"<nodeTypeName>": "<name>"}, ... ]
194 */
195
196void setProperty(QSSGSceneDesc::Node *node, const QStringView propertyName, const QJsonValue &value)
197{
198 QVariant var;
199
200 if (value.isArray()) {
201 var = QVariant::fromValue(nodeListFromJson(node->scene, value.toArray()));
202 } else if (value.isObject()) {
203 auto obj = value.toObject();
204 if (obj.contains(u"url")) {
205 auto path = obj.value(u"url").toString();
206 var = QVariant::fromValue(QUrl(path));
207 } else {
208 QSSGSceneDesc::Node *n = nodeFromJson(node->scene, obj);
209 var = QVariant::fromValue(n);
210 }
211 } else {
212 var = value.toVariant(); // The rest of the special handling happens in QSSGRuntimeUtils::applyPropertyValue
213 }
214
215 const auto name = propertyName.toUtf8();
216 removeProperty(node, name); // TODO: change property if it exists, instead of deleting and adding
217 auto *property = QSSGSceneDesc::setProperty(*node, name, std::move(var));
218
219 if (node->obj)
220 QSSGRuntimeUtils::applyPropertyValue(node, node->obj, property);
221}
222
223
224QSSGSceneDesc::Node *addResource(QSSGSceneDesc::Scene *scene, const QJsonObject &addition)
225{
226 auto name = addition.value(u"name").toString().toUtf8();
227 auto typeName = addition.value(u"type").toString().toUtf8();
228 if (name.isEmpty() || typeName.isEmpty()) {
229 qWarning("Can't create node without name or type");
230 return nullptr;
231 }
232
233 QSSGSceneDesc::Node *node = nullptr;
234 QSSGSceneDesc::Node *prevResource = findResource(scene, name, nodeTypeFromName(typeName));
235
236 if (typeName == "Material") {
237 bool isSpecGlossy = addition.contains(u"albedoColor") || addition.contains(u"albedoMap")
238 || addition.contains(u"glossinessMap") || addition.contains(u"glossiness");
239 typeName = isSpecGlossy ? "SpecularGlossyMaterial" : "PrincipledMaterial";
240 }
241
242 if (typeName == "PrincipledMaterial") {
243 node = new QSSGSceneDesc::Node(name, QSSGSceneDesc::Node::Type::Material,
244 QSSGRenderGraphObject::Type::PrincipledMaterial);
245 } else if (typeName == "SpecularGlossyMaterial") {
246 node = new QSSGSceneDesc::Node(name, QSSGSceneDesc::Node::Type::Material,
247 QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
248 } else if (typeName == "Texture") {
249 node = new QSSGSceneDesc::Node(name, QSSGSceneDesc::Node::Type::Texture,
250 QSSGRenderGraphObject::Type::Image2D);
251 } else {
252 qWarning() << "Not supported. Don't know how to create" << typeName;
253 return nullptr;
254 }
255 Q_ASSERT(node);
256 node->scene = scene;
257 for (auto it = addition.constBegin(); it != addition.constEnd(); ++it) {
258 const auto &propertyName = it.key();
259 if (propertyName == u"name" || propertyName == u"type" || propertyName == u"comment" || propertyName == u"command")
260 continue;
261 setProperty(node, it.key(), it.value());
262 }
263
264 if (prevResource) {
265 replaceReferencesToResource(scene->root, prevResource, node);
266 scene->resources.removeOne(prevResource);
267 delete prevResource;
268 }
269
270 QSSGSceneDesc::addNode(*scene, *node);
271 return node;
272}
273
274void applyEdit(QSSGSceneDesc::Scene *scene, const QJsonObject &changes)
275{
276 auto doApply = [scene](const QJsonObject &obj) {
277 QByteArray name = obj.value(u"name").toString().toUtf8();
278 QByteArray typeName = obj.value(u"type").toString().toUtf8();
279 auto command = obj.value(u"command").toString(u"edit"_s);
280 auto nodeType = nodeTypeFromName(typeName);
281 if (command == u"edit") {
282 auto *node = findNode(scene->root, name, nodeType);
283 if (!node)
284 node = findResource(scene, name, nodeType);
285 if (node) {
286 for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) {
287 const auto &propertyName = it.key();
288 if (propertyName == u"name" || propertyName == u"type" || propertyName == u"comment" || propertyName == u"command")
289 continue;
290 setProperty(node, it.key(), it.value());
291 }
292 }
293 } else if (command == u"add") {
294 addResource(scene, obj);
295 } else if (command == u"delete") {
296 QSSGSceneDesc::Node *parent = nullptr;
297 auto *node = findNode(scene->root, name, nodeType, &parent);
298 if (node) {
299 deleteTree(node);
300 if (parent)
301 unlinkChild(node, parent);
302 else
303 qWarning("Delete: could not find parent for node");
304 }
305 }
306 };
307
308 const auto editList = changes.value(u"editList").toArray();
309
310 // Do all the adds first, since the edits may depend on them
311 // If adds depend on each other, they need to be in dependency order
312 for (auto edit : editList) {
313 auto obj = edit.toObject();
314 if (obj.value(u"command") == u"add"_s)
315 doApply(obj);
316 }
317
318 for (auto edit : editList) {
319 auto obj = edit.toObject();
320 if (obj.value(u"command") != u"add"_s)
321 doApply(obj);
322 }
323
324}
325
326}
327
328QT_END_NAMESPACE
static void removeFromAnimation(QSSGSceneDesc::Animation *animation, const NodeSet &nodes)
bool NodeFilter(QSSGSceneDesc::Node *)
static QSSGSceneDesc::Node::Type nodeTypeFromName(const QByteArrayView &typeName)
static void removeProperty(QSSGSceneDesc::Node *node, const QByteArrayView &name)
static constexpr qsizetype nNodeTypes
void applyEdit(QSSGSceneDesc::Scene *scene, const QJsonObject &changes)
void setProperty(QSSGSceneDesc::Node *node, const QStringView propertyName, const QJsonValue &value)
static NodeSet flattenTree(QSSGSceneDesc::Node *node, NodeFilter *excludeFunction=nullptr)
static QSSGSceneDesc::NodeList * nodeListFromJson(const QSSGSceneDesc::Scene *scene, const QJsonArray &array)
static QSSGSceneDesc::Node * nodeFromJson(const QSSGSceneDesc::Scene *scene, const QJsonObject &nodeRef)
static void deleteTree(QSSGSceneDesc::Node *node)
static void replaceReferencesToResource(QSSGSceneDesc::Node *node, QSSGSceneDesc::Node *resource, QSSGSceneDesc::Node *replacement)
static const char * typeNames[]
QSSGSceneDesc::Node * addResource(QSSGSceneDesc::Scene *scene, const QJsonObject &addition)
static void unlinkChild(QSSGSceneDesc::Node *child, QSSGSceneDesc::Node *parent)
static QSSGSceneDesc::Node * findResource(const QSSGSceneDesc::Scene *scene, const QByteArrayView &name, QSSGSceneDesc::Node::Type nodeType)
static QSSGSceneDesc::Node * findNode(QSSGSceneDesc::Node *root, const QByteArrayView name, QSSGSceneDesc::Node::Type type, QSSGSceneDesc::Node **parent=nullptr)
Combined button and popup list for selecting options.