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 for (auto *child : node->childItems())
178 boxBoundsRecursive(baseNode, qobject_cast<const QQuick3DNode *>(child), accBounds);
179}
180
181template<typename Func>
182static void applyToModels(QQuick3DObject *obj, Func &&lambda)
183{
184 if (!obj)
185 return;
186 for (auto *child : obj->childItems()) {
187 if (auto *model = qobject_cast<QQuick3DModel *>(child))
188 lambda(model);
189 applyToModels(child, lambda);
190 }
191}
192
193void QQuick3DRuntimeLoader::loadSource()
194{
195 delete m_root;
196 m_root.clear();
197 QSSGBufferManager::unregisterMeshData(m_assetId);
198
199 m_status = Status::Empty;
200 m_errorString = QStringLiteral("No file selected");
201 if (!m_source.isValid()) {
202 emit statusChanged();
203 emit errorStringChanged();
204 return;
205 }
206
207 QSSGAssetImportManager importManager;
208 QSSGSceneDesc::Scene scene;
209 QString error(QStringLiteral("Unknown error"));
210 auto result = importManager.importFile(m_source, scene, &error);
211
212 switch (result) {
213 case QSSGAssetImportManager::ImportState::Success:
214 m_errorString = QStringLiteral("Success!");
215 m_status = Status::Success;
216 break;
217 case QSSGAssetImportManager::ImportState::IoError:
218 m_errorString = QStringLiteral("IO Error: ") + error;
219 m_status = Status::Error;
220 break;
221 case QSSGAssetImportManager::ImportState::Unsupported:
222 m_errorString = QStringLiteral("Unsupported: ") + error;
223 m_status = Status::Error;
224 break;
225 }
226
227 if (m_status == Status::Success) {
228 // We create a dummy root node here, as it will be the parent to the first-level nodes
229 // and resources. If we use 'this' those first-level nodes/resources won't be deleted
230 // when a new scene is loaded.
231 m_root = new QQuick3DNode(this);
232 m_imported = QSSGRuntimeUtils::createScene(*m_root, scene);
233 m_assetId = scene.id;
234 m_boundsDirty = true;
235 m_instancingChanged = m_instancing != nullptr;
236 updateModels();
237 // Cleanup scene before deleting.
238 scene.cleanup();
239 } else {
240 m_source.clear();
241 emit sourceChanged();
242 }
243
244 emit statusChanged();
245 emit errorStringChanged();
246
247}
248
249void QQuick3DRuntimeLoader::updateModels()
250{
251 if (m_instancingChanged) {
252 applyToModels(m_imported, [this](QQuick3DModel *model) {
253 model->setInstancing(m_instancing);
254 model->setInstanceRoot(m_imported);
255 });
256 m_instancingChanged = false;
257 }
258}
259
260QQuick3DRuntimeLoader::Status QQuick3DRuntimeLoader::status() const
261{
262 return m_status;
263}
264
265QString QQuick3DRuntimeLoader::errorString() const
266{
267 return m_errorString;
268}
269
270QSSGRenderGraphObject *QQuick3DRuntimeLoader::updateSpatialNode(QSSGRenderGraphObject *node)
271{
272 auto *result = QQuick3DNode::updateSpatialNode(node);
273 if (m_boundsDirty)
274 QMetaObject::invokeMethod(this, &QQuick3DRuntimeLoader::boundsChanged, Qt::QueuedConnection);
275 return result;
276}
277
278void QQuick3DRuntimeLoader::calculateBounds()
279{
280 if (!m_imported || !m_boundsDirty)
281 return;
282
283 m_bounds.bounds.setEmpty();
284 boxBoundsRecursive(m_imported, m_imported, m_bounds);
285 m_boundsDirty = false;
286}
287
288const QQuick3DBounds3 &QQuick3DRuntimeLoader::bounds() const
289{
290 if (m_boundsDirty) {
291 auto *that = const_cast<QQuick3DRuntimeLoader *>(this);
292 that->calculateBounds();
293 return that->m_bounds;
294 }
295
296 return m_bounds;
297}
298
299QQuick3DInstancing *QQuick3DRuntimeLoader::instancing() const
300{
301 return m_instancing;
302}
303
304void QQuick3DRuntimeLoader::setInstancing(QQuick3DInstancing *newInstancing)
305{
306 if (m_instancing == newInstancing)
307 return;
308
309 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRuntimeLoader::setInstancing,
310 newInstancing, m_instancing);
311
312 m_instancing = newInstancing;
313 m_instancingChanged = true;
314 updateModels();
315 emit instancingChanged();
316}
317
318QT_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)