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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | |
22 | // Actually set the property on node->obj, using QMetaProperty::write() |
23 | void 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 | |
144 | static 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 | |
181 | template<typename GraphObjectType, typename NodeType> |
182 | GraphObjectType *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 | |
195 | template<> |
196 | QQuick3DTextureData *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 | |
255 | void 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 | |
352 | QQuick3DNode *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 | |
388 | QT_END_NAMESPACE |
389 | |