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
qssgrtutilities.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
10
11#include <QtCore/qurl.h>
12#include <QtCore/qbuffer.h>
13
14#include <QtGui/qimage.h>
15#include <QtGui/qimagereader.h>
16#include <QtGui/qimagewriter.h>
17#include <QtGui/qquaternion.h>
18
19#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
20
22
23
24// Actually set the property on node->obj, using QMetaProperty::write()
25void QSSGRuntimeUtils::applyPropertyValue(const QSSGSceneDesc::Node *node, QObject *o, QSSGSceneDesc::Property *property)
26{
27 auto *obj = qobject_cast<QQuick3DObject *>(o ? o : node->obj);
28 if (!obj)
29 return;
30
31 auto *metaObj = obj->metaObject();
32 int propertyIndex = metaObj->indexOfProperty(property->name);
33 if (propertyIndex < 0) {
34 qWarning() << "QSSGSceneDesc: could not find property" << property->name << "in" << obj;
35 return;
36 }
37 auto metaProp = metaObj->property(propertyIndex);
38 QVariant value;
39
40 auto metaId = property->value.metaType().id();
41 auto *scene = node->scene;
42
43 if (metaId == qMetaTypeId<QSSGSceneDesc::Node *>()) {
44 const auto *valueNode = qvariant_cast<QSSGSceneDesc::Node *>(property->value);
45 QObject *obj = valueNode ? valueNode->obj : nullptr;
46 value = QVariant::fromValue(obj);
47 } else if (metaId == qMetaTypeId<QSSGSceneDesc::Mesh *>()) { // Special handling for mesh nodes.
48 // Mesh nodes does not have an equivalent in the QtQuick3D scene, but is registered
49 // as a source property in the intermediate scene we therefore need to convert it to
50 // be a usable source url now.
51 const auto meshNode = qvariant_cast<const QSSGSceneDesc::Mesh *>(property->value);
52 const auto url = meshNode ? QUrl(QSSGBufferManager::runtimeMeshSourceName(node->scene->id, meshNode->idx)) : QUrl{};
53 value = QVariant::fromValue(url);
54 } else if (metaId == qMetaTypeId<QUrl>()) {
55 const auto url = qvariant_cast<QUrl>(property->value);
56 // TODO: Use QUrl::resolved() instead??
57 QString workingDir = scene->sourceDir;
58 const QUrl qurl = url.isValid() ? QUrl::fromUserInput(url.path(), workingDir) : QUrl{};
59 value = QVariant::fromValue(qurl);
60 } else if (metaId == qMetaTypeId<QSSGSceneDesc::Flag>() && property->call) {
61 // If we have a QSSGSceneDesc::Flag variant, then it came from setProperty(), and the setter function is defined.
62 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(property->value);
63 property->call->set(*obj, property->name, flag.value);
64 qDebug() << "Flag special case, probably shouldn't happen" << node->name << property->name << property->value << flag.value;
65 return;
66 } else {
67 value = property->value;
68 }
69
70 if (value.metaType().id() == qMetaTypeId<QString>()) {
71 auto str = value.toString();
72 auto propType = metaProp.metaType();
73 if (propType.id() == qMetaTypeId<QVector3D>()) {
74 QStringList l = str.split(u',');
75 if (l.length() != 3) {
76 qWarning() << "Wrong format for QVector3D:" << str;
77 } else {
78 QVector3D vec3(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat());
79 value = QVariant::fromValue(vec3);
80 }
81 } else if (propType.id() == qMetaTypeId<QVector2D>()) {
82 QStringList l = str.split(u',');
83 if (l.length() != 2) {
84 qWarning() << "Wrong format for QVector2D:" << str;
85 } else {
86 QVector2D vec(l.at(0).toFloat(), l.at(1).toFloat());
87 value = QVariant::fromValue(vec);
88 }
89 } else if (propType.id() == qMetaTypeId<QVector4D>()) {
90 QStringList l = str.split(u',');
91 if (l.length() != 2) {
92 qWarning() << "Wrong format for QVector4D:" << str;
93 } else {
94 QVector4D vec(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat(), l.at(3).toFloat());
95 value = QVariant::fromValue(vec);
96 }
97 } else if (propType.id() == qMetaTypeId<QQuaternion>()) {
98 QStringList l = str.split(u',');
99 if (l.length() != 4) {
100 qWarning() << "Wrong format for QQuaternion:" << str;
101 } else {
102 QQuaternion quat(l.at(0).toFloat(), l.at(1).toFloat(), l.at(2).toFloat(), l.at(3).toFloat());
103 value = QVariant::fromValue(quat);
104 }
105 } else {
106 // All other strings are supposed to be in QML-compatible format, so they can be written out directly
107 }
108 } else if (value.metaType().id() == qMetaTypeId<QSSGSceneDesc::NodeList*>()) {
109 auto qmlListVar = metaProp.read(obj);
110 // We have to write explicit code for each list property type, since metatype can't
111 // tell us if we have a QQmlListProperty (if we had known, we could have made a naughty
112 // hack and just static_cast to QQmlListProperty<QObject>
113 if (qmlListVar.metaType().id() == qMetaTypeId<QQmlListProperty<QQuick3DMaterial>>()) {
114 auto qmlList = qvariant_cast<QQmlListProperty<QQuick3DMaterial>>(qmlListVar);
115 auto nodeList = qvariant_cast<QSSGSceneDesc::NodeList*>(value);
116 auto head = reinterpret_cast<QSSGSceneDesc::Node **>(nodeList->head);
117
118 for (int i = 0, end = nodeList->count; i != end; ++i)
119 qmlList.append(&qmlList, qobject_cast<QQuick3DMaterial *>((*(head + i))->obj));
120
121 } else {
122 qWarning() << "Can't handle list property type" << qmlListVar.metaType();
123 }
124 return; //In any case, we can't send NodeList to QMetaProperty::write()
125 }
126
127 // qobject_cast doesn't work on nullptr, so we must convert the pointer manually
128 if ((metaProp.metaType().flags() & QMetaType::PointerToQObject) && value.isNull())
129 value.convert(metaProp.metaType()); //This will return false, but convert to the type anyway
130
131 // Q_ENUMS with '|' is explicitly handled, otherwise uses QVariant::convert. This means
132 // we get implicit qobject_cast and string to:
133 // QMetaType::Bool, QMetaType::QByteArray, QMetaType::QChar, QMetaType::QColor, QMetaType::QDate, QMetaType::QDateTime,
134 // QMetaType::Double, QMetaType::QFont, QMetaType::Int, QMetaType::QKeySequence, QMetaType::LongLong,
135 // QMetaType::QStringList, QMetaType::QTime, QMetaType::UInt, QMetaType::ULongLong, QMetaType::QUuid
136
137 bool success = metaProp.write(obj, value);
138
139 if (!success) {
140 qWarning() << "Failure when setting property" << property->name << "to" << property->value << "maps to" << value
141 << "property metatype:" << metaProp.typeName();
142 }
143}
144
145
146static void setProperties(QQuick3DObject &obj, const QSSGSceneDesc::Node &node, const QString &workingDir = {})
147{
148 using namespace QSSGSceneDesc;
149 const auto &properties = node.properties;
150 auto it = properties.begin();
151 const auto end = properties.end();
152 for (; it != end; ++it) {
153 const auto &v = *it;
154 if (!v->call) {
156 continue;
157 }
158 const auto &var = v->value;
159 if (var.metaType().id() == qMetaTypeId<Node *>()) {
160 const auto *node = qvariant_cast<Node *>(var);
161 v->call->set(obj, v->name, node ? node->obj : nullptr);
162 } else if (var.metaType() == QMetaType::fromType<Mesh *>()) { // Special handling for mesh nodes.
163 // Mesh nodes does not have an equivalent in the QtQuick3D scene, but is registered
164 // as a source property in the intermediate scene we therefore need to convert it to
165 // be a usable source url now.
166 const auto meshNode = qvariant_cast<const Mesh *>(var);
167 const auto url = meshNode ? QUrl(QSSGBufferManager::runtimeMeshSourceName(node.scene->id, meshNode->idx)) : QUrl{};
168 v->call->set(obj, v->name, &url);
169 } else if (var.metaType() == QMetaType::fromType<QUrl>()) {
170 const auto url = qvariant_cast<QUrl>(var);
171 // TODO: Use QUrl::resolved() instead
172 const QUrl qurl = url.isValid() ? QUrl::fromUserInput(url.toString(), workingDir) : QUrl{};
173 v->call->set(obj, v->name, &qurl);
174 } else if (var.metaType().id() == qMetaTypeId<QSSGSceneDesc::Flag>()) {
175 const auto flag = qvariant_cast<QSSGSceneDesc::Flag>(var);
176 v->call->set(obj, v->name, flag.value);
177 } else {
178 v->call->set(obj, v->name, var);
179 }
180 }
181}
182
183template<typename GraphObjectType, typename NodeType>
184GraphObjectType *createRuntimeObject(NodeType &node, QQuick3DObject &parent)
185{
186 GraphObjectType *obj = qobject_cast<GraphObjectType *>(node.obj);
187 if (!obj) {
188 node.obj = qobject_cast<QQuick3DObject *>(obj = new GraphObjectType);
189 QString name = QString::fromUtf8(node.name);
190 if (name.isEmpty())
191 name = QString::fromUtf8(QSSGQmlUtilities::getQmlElementName(node));
192 obj->setObjectName(name);
193 obj->setParent(&parent);
194 obj->setParentItem(&parent);
195 }
196 Q_ASSERT(obj == node.obj);
197
198 return obj;
199}
200
201template<>
202QQuick3DTextureData *createRuntimeObject<QQuick3DTextureData>(QSSGSceneDesc::TextureData &node, QQuick3DObject &parent)
203{
204 QQuick3DTextureData *obj = qobject_cast<QQuick3DTextureData *>(node.obj);
205 if (!obj) {
206 node.obj = qobject_cast<QQuick3DObject *>(obj = new QQuick3DTextureData);
207 obj->setObjectName(node.name);
208 obj->setParent(&parent);
209 obj->setParentItem(&parent);
210
211 const auto &texData = node.data;
212 const bool isCompressed = ((node.flgs & quint8(QSSGSceneDesc::TextureData::Flags::Compressed)) != 0);
213
214 if (!texData.isEmpty()) {
215 QImage image;
216 if (isCompressed) {
217 QByteArray data = texData;
218 QBuffer readBuffer(&data);
219 QImageReader imageReader(&readBuffer, node.fmt);
220 image = imageReader.read();
221 if (image.isNull())
222 qWarning() << imageReader.errorString();
223 } else {
224 const auto &size = node.sz;
225 image = QImage(reinterpret_cast<const uchar *>(texData.data()), size.width(), size.height(), QImage::Format::Format_RGBA8888);
226 }
227
228 if (!image.isNull()) {
229 const QPixelFormat pixFormat = image.pixelFormat();
230 QImage::Format targetFormat = QImage::Format_RGBA8888_Premultiplied;
231 QQuick3DTextureData::Format textureFormat = QQuick3DTextureData::Format::RGBA8;
232 if (image.colorCount()) { // a palleted image
233 targetFormat = QImage::Format_RGBA8888;
234 } else if (pixFormat.channelCount() == 1) {
235 targetFormat = QImage::Format_Grayscale8;
236 textureFormat = QQuick3DTextureData::Format::R8;
237 } else if (pixFormat.alphaUsage() == QPixelFormat::IgnoresAlpha) {
238 targetFormat = QImage::Format_RGBX8888;
239 } else if (pixFormat.premultiplied() == QPixelFormat::NotPremultiplied) {
240 targetFormat = QImage::Format_RGBA8888;
241 }
242
243 image.convertTo(targetFormat); // convert to a format mappable to QRhiTexture::Format
244 image.flip(); // Flip vertically to the conventional Y-up orientation
245
246 const auto bytes = image.sizeInBytes();
247 obj->setSize(image.size());
248 obj->setFormat(textureFormat);
249 obj->setTextureData(QByteArray(reinterpret_cast<const char *>(image.constBits()), bytes));
250 }
251 }
252 }
253
254 return obj;
255}
256
257
258// Resources may refer to other resources and/or nodes, so we first generate all the resources without setting properties,
259// then all the nodes with properties, and finally we set properties for the resources
260// TODO: split this into different functions
261
262void QSSGRuntimeUtils::createGraphObject(QSSGSceneDesc::Node &node,
263 QQuick3DObject &parent,
264 QString path,
265 QSSGRuntimeObjectNameMap *objectMap,
266 QSSGRuntimeObjectTypeMap *typeMap,
267 bool traverseChildrenAndSetProperties)
268{
269 using namespace QSSGSceneDesc;
270
271 QQuick3DObject *obj = nullptr;
272 switch (node.nodeType) {
273 case Node::Type::Skeleton:
274 // Skeleton is a resource that also needs to be in the node tree. We don't do that anymore.
275 qWarning("Skeleton runtime import not supported");
276
277 // NOTE: The skeleton is special as it's a resource and a node, the
278 // hierarchical parent is therefore important here.
279 if (!node.obj) {// 1st Phase : 'create Resources'
280 obj = createRuntimeObject<QQuick3DSkeleton>(static_cast<Skeleton &>(node), parent);
281 } else { // 2nd Phase : setParent for the Node hierarchy.
282 obj = qobject_cast<QQuick3DSkeleton *>(node.obj);
283 obj->setParent(&parent);
284 obj->setParentItem(&parent);
285 }
286 break;
287 case Node::Type::Joint:
288 obj = createRuntimeObject<QQuick3DJoint>(static_cast<Joint &>(node), parent);
289 break;
290 case Node::Type::Skin:
291 obj = createRuntimeObject<QQuick3DSkin>(static_cast<Skin &>(node), parent);
292 break;
293 case Node::Type::MorphTarget:
294 obj = createRuntimeObject<QQuick3DMorphTarget>(static_cast<MorphTarget &>(node), parent);
295 break;
296 case Node::Type::Light:
297 {
298 auto &light = static_cast<Light &>(node);
299 if (light.runtimeType == Node::RuntimeType::DirectionalLight)
300 obj = createRuntimeObject<QQuick3DDirectionalLight>(light, parent);
301 else if (light.runtimeType == Node::RuntimeType::PointLight)
302 obj = createRuntimeObject<QQuick3DPointLight>(light, parent);
303 else if (light.runtimeType == Node::RuntimeType::SpotLight)
304 obj = createRuntimeObject<QQuick3DSpotLight>(light, parent);
305 else
306 Q_UNREACHABLE();
307 }
308 break;
309 case Node::Type::Transform:
310 obj = createRuntimeObject<QQuick3DNode>(node, parent);
311 break;
312 case Node::Type::Camera:
313 {
314 auto &camera = static_cast<Camera &>(node);
315 if (camera.runtimeType == Node::RuntimeType::OrthographicCamera)
316 obj = createRuntimeObject<QQuick3DOrthographicCamera>(camera, parent);
317 else if (camera.runtimeType == Node::RuntimeType::PerspectiveCamera)
318 obj = createRuntimeObject<QQuick3DPerspectiveCamera>(camera, parent);
319 else if (camera.runtimeType == Node::RuntimeType::CustomCamera)
320 obj = createRuntimeObject<QQuick3DCustomCamera>(camera, parent);
321 else
322 Q_UNREACHABLE();
323 }
324 break;
325 case Node::Type::Model:
326 obj = createRuntimeObject<QQuick3DModel>(static_cast<Model &>(node), parent);
327 break;
328 case Node::Type::Texture:
329 if (node.runtimeType == Node::RuntimeType::TextureData)
330 obj = createRuntimeObject<QQuick3DTextureData>(static_cast<TextureData &>(node), parent);
331 else if (node.runtimeType == Node::RuntimeType::Image2D)
332 obj = createRuntimeObject<QQuick3DTexture>(static_cast<Texture &>(node), parent);
333 else if (node.runtimeType == Node::RuntimeType::ImageCube)
334 obj = createRuntimeObject<QQuick3DCubeMapTexture>(static_cast<Texture &>(node), parent);
335 else
336 Q_UNREACHABLE();
337 break;
338 case Node::Type::Material:
339 {
340 if (node.runtimeType == Node::RuntimeType::PrincipledMaterial)
341 obj = createRuntimeObject<QQuick3DPrincipledMaterial>(static_cast<Material &>(node), parent);
342 else if (node.runtimeType == Node::RuntimeType::CustomMaterial)
343 obj = createRuntimeObject<QQuick3DCustomMaterial>(static_cast<Material &>(node), parent);
344 else if (node.runtimeType == Node::RuntimeType::SpecularGlossyMaterial)
345 obj = createRuntimeObject<QQuick3DSpecularGlossyMaterial>(static_cast<Material &>(node), parent);
346 else
347 Q_UNREACHABLE();
348 }
349 break;
350 case Node::Type::Mesh:
351 // There's no runtime object for this type, but we need to register the mesh with the
352 // buffer manager (this will happen once the mesh property is processed on the model).
353 break;
354 }
355
356 if (obj) {
357 const QString keyName = obj->objectName().isEmpty()
358 ? QString::fromUtf8(QSSGQmlUtilities::getQmlElementName(node))
359 : obj->objectName();
360 path += QStringLiteral("/") + keyName;
361 if (objectMap)
362 objectMap->insert({ keyName, path }, obj);
363 if (typeMap)
364 typeMap->insert(QSSGRenderGraphObjectUtils::getBaseType(node.runtimeType), obj);
365 }
366
367 if (obj && traverseChildrenAndSetProperties) {
368 setProperties(*obj, node);
369 for (auto &chld : node.children)
370 createGraphObject(*chld, *obj, path, objectMap, typeMap);
371 }
372}
373
374/*
375
376Node {
377 name: "root"
378 children: [
379 Node {
380 name: "child1"
381 children: [
382 Node {
383 name: "grandchild1"
384 }
385 ]
386 }
387 ]
388}
389
390We'll create a map of all nodes using the path as key, so we can easily get any node by providing the path.
391Since the node names aren't unique, we'll insert each node both by name and by path. If there are multiple
392nodes with the same name, we'll just overwrite the previous one.
393
394
395*/
396
397QQuick3DNode *QSSGRuntimeUtils::createScene(QQuick3DNode &parent, const QSSGSceneDesc::Scene &scene, QSSGRuntimeObjectNameMap *objectMap, QSSGRuntimeObjectTypeMap *typeMap)
398{
399 if (!scene.root) {
400 qWarning("Incomplete scene description (missing plugin?)");
401 return nullptr;
402 }
403
404 Q_ASSERT(QQuick3DObjectPrivate::get(&parent)->sceneManager);
405
406 QSSGBufferManager::registerMeshData(scene.id, scene.meshStorage);
407
408 auto root = scene.root;
409
410 for (const auto &resource : scene.resources)
411 createGraphObject(*resource, parent, {}, objectMap, typeMap, false);
412
413 createGraphObject(*root, parent, {}, objectMap, typeMap);
414
415 // Some resources such as Skin have properties related with the node
416 // hierarchy. Therefore, resources are handled after nodes.
417 for (const auto &resource : scene.resources) {
418 if (resource->obj != nullptr) // A mesh node has no runtime object.
419 setProperties(static_cast<QQuick3DObject &>(*resource->obj), *resource, scene.sourceDir);
420 }
421
422 // Usually it makes sense to only enable 1 timeline at a time
423 // so for now we just enable the first one.
424 bool isFirstAnimation = true;
425 for (const auto &anim: scene.animations) {
426 QSSGQmlUtilities::createTimelineAnimation(*anim, root->obj, isFirstAnimation);
427 if (isFirstAnimation)
428 isFirstAnimation = false;
429 }
430
431 return qobject_cast<QQuick3DNode *>(scene.root->obj);
432}
433
434QT_END_NAMESPACE
The QVector2D class represents a vector or vertex in 2D space.
Definition qvectornd.h:36
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:180
The QVector4D class represents a vector or vertex in 4D space.
Definition qvectornd.h:343
Q_QUICK3DASSETUTILS_EXPORT void applyPropertyValue(const QSSGSceneDesc::Node *node, QObject *obj, QSSGSceneDesc::Property *property)
Combined button and popup list for selecting options.
GraphObjectType * createRuntimeObject(NodeType &node, QQuick3DObject &parent)
static void setProperties(QQuick3DObject &obj, const QSSGSceneDesc::Node &node, const QString &workingDir={})