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 obj->setParent(&parent);
190 obj->setParentItem(&parent);
191 }
192 Q_ASSERT(obj == node.obj);
193
194 return obj;
195}
196
197template<>
198QQuick3DTextureData *createRuntimeObject<QQuick3DTextureData>(QSSGSceneDesc::TextureData &node, QQuick3DObject &parent)
199{
200 QQuick3DTextureData *obj = qobject_cast<QQuick3DTextureData *>(node.obj);
201 if (!obj) {
202 node.obj = qobject_cast<QQuick3DObject *>(obj = new QQuick3DTextureData);
203 obj->setParent(&parent);
204 obj->setParentItem(&parent);
205
206 const auto &texData = node.data;
207 const bool isCompressed = ((node.flgs & quint8(QSSGSceneDesc::TextureData::Flags::Compressed)) != 0);
208
209 if (!texData.isEmpty()) {
210 QImage image;
211 if (isCompressed) {
212 QByteArray data = texData;
213 QBuffer readBuffer(&data);
214 QImageReader imageReader(&readBuffer, node.fmt);
215 image = imageReader.read();
216 if (image.isNull())
217 qWarning() << imageReader.errorString();
218 } else {
219 const auto &size = node.sz;
220 image = QImage(reinterpret_cast<const uchar *>(texData.data()), size.width(), size.height(), QImage::Format::Format_RGBA8888);
221 }
222
223 if (!image.isNull()) {
224 const QPixelFormat pixFormat = image.pixelFormat();
225 QImage::Format targetFormat = QImage::Format_RGBA8888_Premultiplied;
226 QQuick3DTextureData::Format textureFormat = QQuick3DTextureData::Format::RGBA8;
227 if (image.colorCount()) { // a palleted image
228 targetFormat = QImage::Format_RGBA8888;
229 } else if (pixFormat.channelCount() == 1) {
230 targetFormat = QImage::Format_Grayscale8;
231 textureFormat = QQuick3DTextureData::Format::R8;
232 } else if (pixFormat.alphaUsage() == QPixelFormat::IgnoresAlpha) {
233 targetFormat = QImage::Format_RGBX8888;
234 } else if (pixFormat.premultiplied() == QPixelFormat::NotPremultiplied) {
235 targetFormat = QImage::Format_RGBA8888;
236 }
237
238 image.convertTo(targetFormat); // convert to a format mappable to QRhiTexture::Format
239 image.flip(); // Flip vertically to the conventional Y-up orientation
240
241 const auto bytes = image.sizeInBytes();
242 obj->setSize(image.size());
243 obj->setFormat(textureFormat);
244 obj->setTextureData(QByteArray(reinterpret_cast<const char *>(image.constBits()), bytes));
245 }
246 }
247 }
248
249 return obj;
250}
251
252
253// Resources may refer to other resources and/or nodes, so we first generate all the resources without setting properties,
254// then all the nodes with properties, and finally we set properties for the resources
255// TODO: split this into different functions
256
258 QQuick3DObject &parent, bool traverseChildrenAndSetProperties)
259{
260 using namespace QSSGSceneDesc;
261
262 QQuick3DObject *obj = nullptr;
263 switch (node.nodeType) {
264 case Node::Type::Skeleton:
265 // Skeleton is a resource that also needs to be in the node tree. We don't do that anymore.
266 qWarning("Skeleton runtime import not supported");
267
268 // NOTE: The skeleton is special as it's a resource and a node, the
269 // hierarchical parent is therefore important here.
270 if (!node.obj) {// 1st Phase : 'create Resources'
271 obj = createRuntimeObject<QQuick3DSkeleton>(static_cast<Skeleton &>(node), parent);
272 } else { // 2nd Phase : setParent for the Node hierarchy.
273 obj = qobject_cast<QQuick3DSkeleton *>(node.obj);
274 obj->setParent(&parent);
275 obj->setParentItem(&parent);
276 }
277 break;
278 case Node::Type::Joint:
279 obj = createRuntimeObject<QQuick3DJoint>(static_cast<Joint &>(node), parent);
280 break;
281 case Node::Type::Skin:
282 obj = createRuntimeObject<QQuick3DSkin>(static_cast<Skin &>(node), parent);
283 break;
284 case Node::Type::MorphTarget:
285 obj = createRuntimeObject<QQuick3DMorphTarget>(static_cast<MorphTarget &>(node), parent);
286 break;
287 case Node::Type::Light:
288 {
289 auto &light = static_cast<Light &>(node);
290 if (light.runtimeType == Node::RuntimeType::DirectionalLight)
291 obj = createRuntimeObject<QQuick3DDirectionalLight>(light, parent);
292 else if (light.runtimeType == Node::RuntimeType::PointLight)
293 obj = createRuntimeObject<QQuick3DPointLight>(light, parent);
294 else if (light.runtimeType == Node::RuntimeType::SpotLight)
295 obj = createRuntimeObject<QQuick3DSpotLight>(light, parent);
296 else
297 Q_UNREACHABLE();
298 }
299 break;
300 case Node::Type::Transform:
301 obj = createRuntimeObject<QQuick3DNode>(node, parent);
302 break;
303 case Node::Type::Camera:
304 {
305 auto &camera = static_cast<Camera &>(node);
306 if (camera.runtimeType == Node::RuntimeType::OrthographicCamera)
307 obj = createRuntimeObject<QQuick3DOrthographicCamera>(camera, parent);
308 else if (camera.runtimeType == Node::RuntimeType::PerspectiveCamera)
309 obj = createRuntimeObject<QQuick3DPerspectiveCamera>(camera, parent);
310 else if (camera.runtimeType == Node::RuntimeType::CustomCamera)
311 obj = createRuntimeObject<QQuick3DCustomCamera>(camera, parent);
312 else
313 Q_UNREACHABLE();
314 }
315 break;
316 case Node::Type::Model:
317 obj = createRuntimeObject<QQuick3DModel>(static_cast<Model &>(node), parent);
318 break;
319 case Node::Type::Texture:
320 if (node.runtimeType == Node::RuntimeType::TextureData)
321 obj = createRuntimeObject<QQuick3DTextureData>(static_cast<TextureData &>(node), parent);
322 else if (node.runtimeType == Node::RuntimeType::Image2D)
323 obj = createRuntimeObject<QQuick3DTexture>(static_cast<Texture &>(node), parent);
324 else if (node.runtimeType == Node::RuntimeType::ImageCube)
325 obj = createRuntimeObject<QQuick3DCubeMapTexture>(static_cast<Texture &>(node), parent);
326 else
327 Q_UNREACHABLE();
328 break;
329 case Node::Type::Material:
330 {
331 if (node.runtimeType == Node::RuntimeType::PrincipledMaterial)
332 obj = createRuntimeObject<QQuick3DPrincipledMaterial>(static_cast<Material &>(node), parent);
333 else if (node.runtimeType == Node::RuntimeType::CustomMaterial)
334 obj = createRuntimeObject<QQuick3DCustomMaterial>(static_cast<Material &>(node), parent);
335 else if (node.runtimeType == Node::RuntimeType::SpecularGlossyMaterial)
336 obj = createRuntimeObject<QQuick3DSpecularGlossyMaterial>(static_cast<Material &>(node), parent);
337 else
338 Q_UNREACHABLE();
339 }
340 break;
341 case Node::Type::Mesh:
342 // There's no runtime object for this type, but we need to register the mesh with the
343 // buffer manager (this will happen once the mesh property is processed on the model).
344 break;
345 }
346
347 if (obj && traverseChildrenAndSetProperties) {
348 setProperties(*obj, node);
349 for (auto &chld : node.children)
350 createGraphObject(*chld, *obj);
351 }
352}
353
354QQuick3DNode *QSSGRuntimeUtils::createScene(QQuick3DNode &parent, const QSSGSceneDesc::Scene &scene)
355{
356 if (!scene.root) {
357 qWarning("Incomplete scene description (missing plugin?)");
358 return nullptr;
359 }
360
361 Q_ASSERT(QQuick3DObjectPrivate::get(&parent)->sceneManager);
362
363 QSSGBufferManager::registerMeshData(scene.id, scene.meshStorage);
364
365 auto root = scene.root;
366 for (const auto &resource : scene.resources)
367 createGraphObject(*resource, parent, false);
368
369 createGraphObject(*root, parent);
370
371 // Some resources such as Skin have properties related with the node
372 // hierarchy. Therefore, resources are handled after nodes.
373 for (const auto &resource : scene.resources) {
374 if (resource->obj != nullptr) // A mesh node has no runtime object.
375 setProperties(static_cast<QQuick3DObject &>(*resource->obj), *resource, scene.sourceDir);
376 }
377
378 // Usually it makes sense to only enable 1 timeline at a time
379 // so for now we just enable the first one.
380 bool isFirstAnimation = true;
381 for (const auto &anim: scene.animations) {
382 QSSGQmlUtilities::createTimelineAnimation(*anim, root->obj, isFirstAnimation);
383 if (isFirstAnimation)
384 isFirstAnimation = false;
385 }
386
387 return qobject_cast<QQuick3DNode *>(scene.root->obj);
388}
389
390QT_END_NAMESPACE
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
Q_QUICK3DASSETUTILS_EXPORT void applyPropertyValue(const QSSGSceneDesc::Node *node, QObject *obj, QSSGSceneDesc::Property *property)
Q_QUICK3DASSETUTILS_EXPORT void createGraphObject(QSSGSceneDesc::Node &node, QQuick3DObject &parent, bool traverseChildrenAndSetProperties=true)
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={})