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
qquick3druntimeloader.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
8#include <QtQuick3DAssetUtils/private/qssgscenedesc_p.h>
9#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
10#include <QtQuick3DAssetUtils/private/qssgrtutilities_p.h>
11#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
13#if QT_CONFIG(mimetype)
14#include <QtCore/qmimedatabase.h>
15#endif
16
17/*!
18 \qmltype RuntimeLoader
19 \inherits Node
20 \inqmlmodule QtQuick3D.AssetUtils
21 \since 6.2
22 \brief Imports a 3D asset at runtime.
23
24 The RuntimeLoader type provides a way to load a 3D asset directly from source at runtime,
25 without converting it to QtQuick3D's internal format first.
26
27 RuntimeLoader supports .obj and glTF version 2.0 files in both in text (.gltf) and binary
28 (.glb) formats.
29*/
30
31/*!
32 \qmlproperty url RuntimeLoader::source
33
34 This property holds the location of the source file containing the 3D asset.
35 Changing this property will unload the current asset and attempt to load an asset from
36 the given URL.
37
38 The success or failure of the load operation is indicated by \l status.
39*/
40
41/*!
42 \qmlproperty enumeration RuntimeLoader::status
43
44 This property holds the status of the latest load operation.
45
46 \value RuntimeLoader.Empty
47 No URL was specified.
48 \value RuntimeLoader.Success
49 The load operation was successful.
50 \value RuntimeLoader.Error
51 The load operation failed. A human-readable error message is provided by \l errorString.
52
53 \readonly
54*/
55
56/*!
57 \qmlproperty string RuntimeLoader::errorString
58
59 This property holds a human-readable string indicating the status of the latest load operation.
60
61 \readonly
62*/
63
64/*!
65 \qmlproperty Bounds RuntimeLoader::bounds
66
67 This property describes the extents of the bounding volume around the imported model.
68
69 \note The value may not be available before the first render
70
71 \readonly
72*/
73
74/*!
75 \qmlproperty Instancing RuntimeLoader::instancing
76
77 If this property is set, the imported model will not be rendered normally. Instead, a number of
78 instances will be rendered, as defined by the instance table.
79
80 See the \l{Instanced Rendering} overview documentation for more information.
81*/
82
84
85QQuick3DRuntimeLoader::QQuick3DRuntimeLoader(QQuick3DNode *parent)
86 : QQuick3DNode(parent)
87{
88
89}
90
91QUrl QQuick3DRuntimeLoader::source() const
92{
93 return m_source;
94}
95
96void QQuick3DRuntimeLoader::setSource(const QUrl &newSource)
97{
98 if (m_source == newSource)
99 return;
100
101 const QQmlContext *context = qmlContext(this);
102 auto resolvedUrl = (context ? context->resolvedUrl(newSource) : newSource);
103
104 if (m_source == resolvedUrl)
105 return;
106
107 m_source = resolvedUrl;
108 emit sourceChanged();
109
110 if (isComponentComplete())
111 loadSource();
112}
113
114void QQuick3DRuntimeLoader::componentComplete()
115{
116 QQuick3DNode::componentComplete();
117 loadSource();
118}
119
120QStringList QQuick3DRuntimeLoader::supportedExtensions()
121{
122 static QStringList extensions;
123 if (!extensions.isEmpty())
124 return extensions;
125
126 static const QStringList supportedExtensions = { QLatin1StringView("obj"),
127 QLatin1StringView("gltf"),
128 QLatin1StringView("glb")};
129
130 QSSGAssetImportManager importManager;
131 const auto types = importManager.getImporterPluginInfos();
132
133 for (const auto &t : types) {
134 for (const QString &extension : t.inputExtensions) {
135 if (supportedExtensions.contains(extension))
136 extensions << extension;
137 }
138 }
139 return extensions;
140}
141
142#if QT_CONFIG(mimetype)
143QList<QMimeType> QQuick3DRuntimeLoader::supportedMimeTypes()
144{
145 static QList<QMimeType> mimeTypes;
146 if (!mimeTypes.isEmpty())
147 return mimeTypes;
148
149 const QStringList &extensions = supportedExtensions();
150
151 QMimeDatabase db;
152 for (const auto &ext : extensions) {
153 // TODO: Change to db.mimeTypesForExtension(ext), once it is implemented (QTBUG-118566)
154 const QString fileName = QLatin1StringView("test.") + ext;
155 mimeTypes << db.mimeTypesForFileName(fileName);
156 }
157
158 return mimeTypes;
159}
160#endif
161
162static void boxBoundsRecursive(const QQuick3DNode *baseNode, const QQuick3DNode *node, QQuick3DBounds3 &accBounds)
163{
164 if (!node)
165 return;
166
167 if (auto *model = qobject_cast<const QQuick3DModel *>(node)) {
168 auto b = model->bounds();
169 for (const QVector3D point : b.bounds.toQSSGBoxPoints()) {
170 auto p = model->mapPositionToNode(const_cast<QQuick3DNode *>(baseNode), point);
171 if (Q_UNLIKELY(accBounds.bounds.isEmpty()))
172 accBounds.bounds = { p, p };
173 else
174 accBounds.bounds.include(p);
175 }
176 }
177 const auto childItems1 = node->childItems();
178 for (auto *child : childItems1)
179 boxBoundsRecursive(baseNode, qobject_cast<const QQuick3DNode *>(child), accBounds);
180}
181
182template<typename Func>
183static void applyToModels(QQuick3DObject *obj, Func &&lambda)
184{
185 if (!obj)
186 return;
187 const auto childItems2 = obj->childItems();
188 for (auto *child : childItems2) {
189 if (auto *model = qobject_cast<QQuick3DModel *>(child))
190 lambda(model);
191 applyToModels(child, lambda);
192 }
193}
194
195void QQuick3DRuntimeLoader::loadSource()
196{
197 delete m_root;
198 m_objects.clear();
199 m_objectsByType.clear();
200 QSSGBufferManager::unregisterMeshData(m_assetId);
201
202 m_status = Status::Empty;
203 m_errorString = QStringLiteral("No file selected");
204 if (!m_source.isValid()) {
205 emit statusChanged();
206 emit errorStringChanged();
207 return;
208 }
209
210 QSSGAssetImportManager importManager;
211 QSSGSceneDesc::Scene scene;
212 QString error(QStringLiteral("Unknown error"));
213 auto result = importManager.importFile(m_source, scene, &error);
214
215 switch (result) {
216 case QSSGAssetImportManager::ImportState::Success:
217 m_errorString = QStringLiteral("Success!");
218 m_status = Status::Success;
219 break;
220 case QSSGAssetImportManager::ImportState::IoError:
221 m_errorString = QStringLiteral("IO Error: ") + error;
222 m_status = Status::Error;
223 break;
224 case QSSGAssetImportManager::ImportState::Unsupported:
225 m_errorString = QStringLiteral("Unsupported: ") + error;
226 m_status = Status::Error;
227 break;
228 }
229
230 if (m_status == Status::Success) {
231 // We create a dummy root node here, as it will be the parent to the first-level nodes
232 // and resources. If we use 'this' those first-level nodes/resources won't be deleted
233 // when a new scene is loaded.
234 m_root = new QQuick3DNode(this);
235 m_root->setObjectName("RuntimeLoaderRoot");
236 m_imported = QSSGRuntimeUtils::createScene(*m_root, scene, &m_objects, &m_objectsByType);
237 m_assetId = scene.id;
238 m_boundsDirty = true;
239 m_instancingChanged = m_instancing != nullptr;
240 updateModels();
241 // Cleanup scene before deleting.
242 scene.cleanup();
243 } else {
244 m_source.clear();
245 emit sourceChanged();
246 }
247
248 emit statusChanged();
249 emit errorStringChanged();
250
251}
252
253void QQuick3DRuntimeLoader::updateModels()
254{
255 if (m_instancingChanged) {
256 applyToModels(m_imported, [this](QQuick3DModel *model) {
257 model->setInstancing(m_instancing);
258 model->setInstanceRoot(m_imported);
259 });
260 m_instancingChanged = false;
261 }
262}
263
264QQuick3DRuntimeLoader::Status QQuick3DRuntimeLoader::status() const
265{
266 return m_status;
267}
268
269QString QQuick3DRuntimeLoader::errorString() const
270{
271 return m_errorString;
272}
273
274QSSGRenderGraphObject *QQuick3DRuntimeLoader::updateSpatialNode(QSSGRenderGraphObject *node)
275{
276 auto *result = QQuick3DNode::updateSpatialNode(node);
277 if (m_boundsDirty)
278 QMetaObject::invokeMethod(this, &QQuick3DRuntimeLoader::boundsChanged, Qt::QueuedConnection);
279 return result;
280}
281
282void QQuick3DRuntimeLoader::calculateBounds()
283{
284 if (!m_imported || !m_boundsDirty)
285 return;
286
287 m_bounds.bounds.setEmpty();
288 boxBoundsRecursive(m_imported, m_imported, m_bounds);
289 m_boundsDirty = false;
290}
291
292const QQuick3DBounds3 &QQuick3DRuntimeLoader::bounds() const
293{
294 if (m_boundsDirty) {
295 auto *that = const_cast<QQuick3DRuntimeLoader *>(this);
296 that->calculateBounds();
297 return that->m_bounds;
298 }
299
300 return m_bounds;
301}
302
303QQuick3DInstancing *QQuick3DRuntimeLoader::instancing() const
304{
305 return m_instancing;
306}
307
308void QQuick3DRuntimeLoader::setInstancing(QQuick3DInstancing *newInstancing)
309{
310 if (m_instancing == newInstancing)
311 return;
312
313 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRuntimeLoader::setInstancing,
314 newInstancing, m_instancing);
315
316 m_instancing = newInstancing;
317 m_instancingChanged = true;
318 updateModels();
319 emit instancingChanged();
320}
321
322/*!
323 \qmlmethod Object3D RuntimeLoader::query(string arg)
324 \since 6.12
325
326 Returns the object with the given name, or \c null if no object with that name exists.
327
328 The \a arg parameter is the name of the object to query or a query string.
329 For example, to query the object named "PaintMaterialX", use the following code:
330
331 \badcode
332 var object = runtimeLoader.query("PaintMaterial")
333 \endcode
334
335 The above code works as expected assuming the objects are sensibly named in the source asset file.
336 However, if there are multiple objects with the same name, the query will return the first object
337 found matching the given name. To query a specific object, and avoid ambiguity, use the full object path.
338
339 \badcode
340 var object = runtimeLoader.query("/House2/Wall001/Mirror/Material")
341 \endcode
342
343 \note Even with paths it's possible for a improperly structured asset file to have multiple objects with the same path,
344 as the paths are built up from the object names in the source asset file.
345
346 \note The object names are defined as in the source asset file.
347*/
348
349QQuick3DObject *QQuick3DRuntimeLoader::query(const QString &name) const
350{
351 const auto index = name.lastIndexOf(QChar(u'/'));
352
353 if (index != -1) {
354 const QString shortName = name.mid(index + 1);
355 const auto range = m_objects.equal_range({shortName, QString()});
356 for (auto it = range.first; it != range.second; ++it) {
357 if (it.key().path == name)
358 return it.value();
359 }
360 }
361
362 return m_objects.value(QSSGRuntimeObjectNameKey{name, QString()});
363}
364
365static inline QSSGRenderGraphObject::BaseType queryFilterToBaseType(QQuick3DRuntimeLoader::QueryFilter filter)
366{
367 using Type = QSSGRenderGraphObject::Type;
368 switch (filter) {
369 case QQuick3DRuntimeLoader::QueryFilter::Textures:
370 return QSSGRenderGraphObjectUtils::getBaseType(Type::Image2D);
371 case QQuick3DRuntimeLoader::QueryFilter::Materials:
372 return QSSGRenderGraphObjectUtils::getBaseType(Type::PrincipledMaterial);
373 case QQuick3DRuntimeLoader::QueryFilter::Nodes:
374 return QSSGRenderGraphObjectUtils::getBaseType(Type::Node);
375 case QQuick3DRuntimeLoader::QueryFilter::Cameras:
376 return QSSGRenderGraphObjectUtils::getBaseType(Type::OrthographicCamera);
377 case QQuick3DRuntimeLoader::QueryFilter::Lights:
378 return QSSGRenderGraphObjectUtils::getBaseType(Type::DirectionalLight);
379 case QQuick3DRuntimeLoader::QueryFilter::Models:
380 return QSSGRenderGraphObjectUtils::getBaseType(Type::Model);
381 }
382
383 Q_UNREACHABLE_RETURN(QSSGRenderGraphObject::BaseType(0));
384}
385
386/*!
387 \qmlmethod List<Object3D> RuntimeLoader::queryAll(QueryFilter filter)
388 \since 6.12
389
390 Returns a list of all objects matching the given filter.
391
392 The \a filter parameter specifies the type of objects to query for.
393 For example, to query for all materials, use the following code:
394
395 \badcode
396 var materials = runtimeLoader.queryAll(RuntimeLoader.Materials)
397 \endcode
398
399 The above code returns a list of all materials in the source asset file.
400*/
401
402QList<QQuick3DObject *> QQuick3DRuntimeLoader::queryAll(QueryFilter filter) const
403{
404 QList<QQuick3DObject *> results;
405 const auto range = m_objectsByType.equal_range(queryFilterToBaseType(filter));
406 for (auto it = range.first; it != range.second; ++it) {
407 if (auto *obj = it.value().data())
408 results << obj;
409 }
410 return results;
411}
412
413QT_END_NAMESPACE
Combined button and popup list for selecting options.
static void boxBoundsRecursive(const QQuick3DNode *baseNode, const QQuick3DNode *node, QQuick3DBounds3 &accBounds)
static void applyToModels(QQuick3DObject *obj, Func &&lambda)
static QSSGRenderGraphObject::BaseType queryFilterToBaseType(QQuick3DRuntimeLoader::QueryFilter filter)