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

source code of qtquick3d/src/assetutils/qssgrtutilities.cpp