1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3druntimeloader_p.h"
5
6#include <QtQuick3DAssetUtils/private/qssgscenedesc_p.h>
7#include <QtQuick3DAssetUtils/private/qssgqmlutilities_p.h>
8#include <QtQuick3DAssetUtils/private/qssgrtutilities_p.h>
9#include <QtQuick3DAssetImport/private/qssgassetimportmanager_p.h>
10#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
11#if QT_CONFIG(mimetype)
12#include <QtCore/qmimedatabase.h>
13#endif
14
15/*!
16 \qmltype RuntimeLoader
17 \inherits Node
18 \inqmlmodule QtQuick3D.AssetUtils
19 \since 6.2
20 \brief Imports a 3D asset at runtime.
21
22 The RuntimeLoader type provides a way to load a 3D asset directly from source at runtime,
23 without converting it to QtQuick3D's internal format first.
24
25 RuntimeLoader supports .obj and glTF version 2.0 files in both in text (.gltf) and binary
26 (.glb) formats.
27*/
28
29/*!
30 \qmlproperty url RuntimeLoader::source
31
32 This property holds the location of the source file containing the 3D asset.
33 Changing this property will unload the current asset and attempt to load an asset from
34 the given URL.
35
36 The success or failure of the load operation is indicated by \l status.
37*/
38
39/*!
40 \qmlproperty enumeration RuntimeLoader::status
41
42 This property holds the status of the latest load operation.
43
44 \value RuntimeLoader.Empty
45 No URL was specified.
46 \value RuntimeLoader.Success
47 The load operation was successful.
48 \value RuntimeLoader.Error
49 The load operation failed. A human-readable error message is provided by \l errorString.
50
51 \readonly
52*/
53
54/*!
55 \qmlproperty string RuntimeLoader::errorString
56
57 This property holds a human-readable string indicating the status of the latest load operation.
58
59 \readonly
60*/
61
62/*!
63 \qmlproperty Bounds RuntimeLoader::bounds
64
65 This property describes the extents of the bounding volume around the imported model.
66
67 \note The value may not be available before the first render
68
69 \readonly
70*/
71
72/*!
73 \qmlproperty Instancing RuntimeLoader::instancing
74
75 If this property is set, the imported model will not be rendered normally. Instead, a number of
76 instances will be rendered, as defined by the instance table.
77
78 See the \l{Instanced Rendering} overview documentation for more information.
79*/
80
81QT_BEGIN_NAMESPACE
82
83QQuick3DRuntimeLoader::QQuick3DRuntimeLoader(QQuick3DNode *parent)
84 : QQuick3DNode(parent)
85{
86
87}
88
89QUrl QQuick3DRuntimeLoader::source() const
90{
91 return m_source;
92}
93
94void QQuick3DRuntimeLoader::setSource(const QUrl &newSource)
95{
96 if (m_source == newSource)
97 return;
98
99 const QQmlContext *context = qmlContext(this);
100 auto resolvedUrl = (context ? context->resolvedUrl(newSource) : newSource);
101
102 if (m_source == resolvedUrl)
103 return;
104
105 m_source = resolvedUrl;
106 emit sourceChanged();
107
108 if (isComponentComplete())
109 loadSource();
110}
111
112void QQuick3DRuntimeLoader::componentComplete()
113{
114 QQuick3DNode::componentComplete();
115 loadSource();
116}
117
118QStringList QQuick3DRuntimeLoader::supportedExtensions()
119{
120 static QStringList extensions;
121 if (!extensions.isEmpty())
122 return extensions;
123
124 static const QStringList supportedExtensions = { QLatin1StringView("obj"),
125 QLatin1StringView("gltf"),
126 QLatin1StringView("glb")};
127
128 QSSGAssetImportManager importManager;
129 const auto types = importManager.getImporterPluginInfos();
130
131 for (const auto &t : types) {
132 for (const QString &extension : t.inputExtensions) {
133 if (supportedExtensions.contains(str: extension))
134 extensions << extension;
135 }
136 }
137 return extensions;
138}
139
140#if QT_CONFIG(mimetype)
141QList<QMimeType> QQuick3DRuntimeLoader::supportedMimeTypes()
142{
143 static QList<QMimeType> mimeTypes;
144 if (!mimeTypes.isEmpty())
145 return mimeTypes;
146
147 const QStringList &extensions = supportedExtensions();
148
149 QMimeDatabase db;
150 for (const auto &ext : extensions) {
151 // TODO: Change to db.mimeTypesForExtension(ext), once it is implemented (QTBUG-118566)
152 const QString fileName = QLatin1StringView("test.") + ext;
153 mimeTypes << db.mimeTypesForFileName(fileName);
154 }
155
156 return mimeTypes;
157}
158#endif
159
160static void boxBoundsRecursive(const QQuick3DNode *baseNode, const QQuick3DNode *node, QQuick3DBounds3 &accBounds)
161{
162 if (!node)
163 return;
164
165 if (auto *model = qobject_cast<const QQuick3DModel *>(object: node)) {
166 auto b = model->bounds();
167 for (const QVector3D point : b.bounds.toQSSGBoxPoints()) {
168 auto p = model->mapPositionToNode(node: const_cast<QQuick3DNode *>(baseNode), localPosition: point);
169 if (Q_UNLIKELY(accBounds.bounds.isEmpty()))
170 accBounds.bounds = { p, p };
171 else
172 accBounds.bounds.include(v: p);
173 }
174 }
175 for (auto *child : node->childItems())
176 boxBoundsRecursive(baseNode, node: qobject_cast<const QQuick3DNode *>(object: child), accBounds);
177}
178
179template<typename Func>
180static void applyToModels(QQuick3DObject *obj, Func &&lambda)
181{
182 if (!obj)
183 return;
184 for (auto *child : obj->childItems()) {
185 if (auto *model = qobject_cast<QQuick3DModel *>(object: child))
186 lambda(model);
187 applyToModels(child, lambda);
188 }
189}
190
191void QQuick3DRuntimeLoader::loadSource()
192{
193 delete m_root;
194 m_root.clear();
195 QSSGBufferManager::unregisterMeshData(assetId: m_assetId);
196
197 m_status = Status::Empty;
198 m_errorString = QStringLiteral("No file selected");
199 if (!m_source.isValid()) {
200 emit statusChanged();
201 emit errorStringChanged();
202 return;
203 }
204
205 QSSGAssetImportManager importManager;
206 QSSGSceneDesc::Scene scene;
207 QString error(QStringLiteral("Unknown error"));
208 auto result = importManager.importFile(url: m_source, scene, error: &error);
209
210 switch (result) {
211 case QSSGAssetImportManager::ImportState::Success:
212 m_errorString = QStringLiteral("Success!");
213 m_status = Status::Success;
214 break;
215 case QSSGAssetImportManager::ImportState::IoError:
216 m_errorString = QStringLiteral("IO Error: ") + error;
217 m_status = Status::Error;
218 break;
219 case QSSGAssetImportManager::ImportState::Unsupported:
220 m_errorString = QStringLiteral("Unsupported: ") + error;
221 m_status = Status::Error;
222 break;
223 }
224
225 if (m_status == Status::Success) {
226 // We create a dummy root node here, as it will be the parent to the first-level nodes
227 // and resources. If we use 'this' those first-level nodes/resources won't be deleted
228 // when a new scene is loaded.
229 m_root = new QQuick3DNode(this);
230 m_imported = QSSGRuntimeUtils::createScene(parent&: *m_root, scene);
231 m_assetId = scene.id;
232 m_boundsDirty = true;
233 m_instancingChanged = m_instancing != nullptr;
234 updateModels();
235 // Cleanup scene before deleting.
236 scene.cleanup();
237 } else {
238 m_source.clear();
239 emit sourceChanged();
240 }
241
242 emit statusChanged();
243 emit errorStringChanged();
244
245}
246
247void QQuick3DRuntimeLoader::updateModels()
248{
249 if (m_instancingChanged) {
250 applyToModels(obj: m_imported, lambda: [this](QQuick3DModel *model) {
251 model->setInstancing(m_instancing);
252 model->setInstanceRoot(m_imported);
253 });
254 m_instancingChanged = false;
255 }
256}
257
258QQuick3DRuntimeLoader::Status QQuick3DRuntimeLoader::status() const
259{
260 return m_status;
261}
262
263QString QQuick3DRuntimeLoader::errorString() const
264{
265 return m_errorString;
266}
267
268QSSGRenderGraphObject *QQuick3DRuntimeLoader::updateSpatialNode(QSSGRenderGraphObject *node)
269{
270 auto *result = QQuick3DNode::updateSpatialNode(node);
271 if (m_boundsDirty)
272 QMetaObject::invokeMethod(object: this, function: &QQuick3DRuntimeLoader::boundsChanged, type: Qt::QueuedConnection);
273 return result;
274}
275
276void QQuick3DRuntimeLoader::calculateBounds()
277{
278 if (!m_imported || !m_boundsDirty)
279 return;
280
281 m_bounds.bounds.setEmpty();
282 boxBoundsRecursive(baseNode: m_imported, node: m_imported, accBounds&: m_bounds);
283 m_boundsDirty = false;
284}
285
286const QQuick3DBounds3 &QQuick3DRuntimeLoader::bounds() const
287{
288 if (m_boundsDirty) {
289 auto *that = const_cast<QQuick3DRuntimeLoader *>(this);
290 that->calculateBounds();
291 return that->m_bounds;
292 }
293
294 return m_bounds;
295}
296
297QQuick3DInstancing *QQuick3DRuntimeLoader::instancing() const
298{
299 return m_instancing;
300}
301
302void QQuick3DRuntimeLoader::setInstancing(QQuick3DInstancing *newInstancing)
303{
304 if (m_instancing == newInstancing)
305 return;
306
307 QQuick3DObjectPrivate::attachWatcher(context: this, setter: &QQuick3DRuntimeLoader::setInstancing,
308 newO: newInstancing, oldO: m_instancing);
309
310 m_instancing = newInstancing;
311 m_instancingChanged = true;
312 updateModels();
313 emit instancingChanged();
314}
315
316QT_END_NAMESPACE
317

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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