1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qmesh.h" |
41 | #include "qmesh_p.h" |
42 | |
43 | #include <QtCore/private/qfactoryloader_p.h> |
44 | #include <QDebug> |
45 | #include <QFile> |
46 | #include <QFileInfo> |
47 | #include <QScopedPointer> |
48 | #include <QMimeDatabase> |
49 | #include <QMimeType> |
50 | #include <QtCore/QBuffer> |
51 | #include <Qt3DRender/QRenderAspect> |
52 | #include <Qt3DCore/QAspectEngine> |
53 | #include <Qt3DCore/qpropertyupdatedchange.h> |
54 | #include <Qt3DCore/private/qscene_p.h> |
55 | #include <Qt3DCore/private/qdownloadhelperservice_p.h> |
56 | #include <Qt3DRender/private/qrenderaspect_p.h> |
57 | #include <Qt3DRender/private/nodemanagers_p.h> |
58 | #include <Qt3DRender/private/qgeometryloaderinterface_p.h> |
59 | #include <Qt3DRender/private/renderlogging_p.h> |
60 | #include <Qt3DRender/private/qurlhelper_p.h> |
61 | #include <Qt3DRender/private/qgeometryloaderfactory_p.h> |
62 | #include <Qt3DRender/private/geometryrenderermanager_p.h> |
63 | |
64 | #include <algorithm> |
65 | |
66 | QT_BEGIN_NAMESPACE |
67 | |
68 | namespace Qt3DRender { |
69 | |
70 | Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, geometryLoader, (QGeometryLoaderFactory_iid, QLatin1String("/geometryloaders" ), Qt::CaseInsensitive)) |
71 | |
72 | QMeshPrivate::QMeshPrivate() |
73 | : QGeometryRendererPrivate() |
74 | , m_status(QMesh::None) |
75 | { |
76 | } |
77 | |
78 | QMeshPrivate *QMeshPrivate::get(QMesh *q) |
79 | { |
80 | return q->d_func(); |
81 | } |
82 | |
83 | void QMeshPrivate::setScene(Qt3DCore::QScene *scene) |
84 | { |
85 | QGeometryRendererPrivate::setScene(scene); |
86 | updateFunctor(); |
87 | } |
88 | |
89 | void QMeshPrivate::updateFunctor() |
90 | { |
91 | Q_Q(QMesh); |
92 | q->setGeometryFactory(QGeometryFactoryPtr(new MeshLoaderFunctor(q))); |
93 | } |
94 | |
95 | void QMeshPrivate::setStatus(QMesh::Status status) |
96 | { |
97 | if (m_status != status) { |
98 | Q_Q(QMesh); |
99 | m_status = status; |
100 | const bool wasBlocked = q->blockNotifications(block: true); |
101 | emit q->statusChanged(status); |
102 | q->blockNotifications(block: wasBlocked); |
103 | } |
104 | } |
105 | |
106 | /*! |
107 | * \qmltype Mesh |
108 | * \instantiates Qt3DRender::QMesh |
109 | * \inqmlmodule Qt3D.Render |
110 | * \brief A custom mesh loader. |
111 | * |
112 | * Loads mesh data from external files in a variety of formats. |
113 | * |
114 | * In Qt3D 5.9, Mesh supports the following formats: |
115 | * |
116 | * \list |
117 | * \li Wavefront OBJ |
118 | * \li Stanford Triangle Format PLY |
119 | * \li STL (STereoLithography) |
120 | * \endlist |
121 | * |
122 | * QMesh will also support the following format if the SDK is installed and the fbx geometry loader plugin is built and found. |
123 | * \list |
124 | * \li Autodesk FBX |
125 | * \endlist |
126 | */ |
127 | |
128 | /*! |
129 | * \qmlproperty url Mesh::source |
130 | * |
131 | * Holds the source url to the file containing the custom mesh. |
132 | */ |
133 | |
134 | /*! |
135 | * \qmlproperty string Mesh::meshName |
136 | * |
137 | * Filter indicating which part of the mesh should be loaded. |
138 | * |
139 | * If meshName is empty (the default), then the entire mesh is loaded. |
140 | * |
141 | * If meshName is a plain string, then only the sub-mesh matching that name, if present, will be loaded. |
142 | * |
143 | * If meshName is a regular expression, than all sub-meshes matching the expression will be loaded. |
144 | * |
145 | * \note Only Wavefront OBJ files support sub-meshes. |
146 | * |
147 | * \sa QRegularExpression |
148 | */ |
149 | |
150 | /*! |
151 | \qmlproperty enumeration Mesh::status |
152 | |
153 | Holds the status of the mesh loading. |
154 | \sa Qt3DRender::QMesh::Status |
155 | \readonly |
156 | */ |
157 | |
158 | /*! |
159 | * \class Qt3DRender::QMesh |
160 | * \inheaderfile Qt3DRender/QMesh |
161 | * \inmodule Qt3DRender |
162 | * |
163 | * \inherits Qt3DRender::QGeometryRenderer |
164 | * |
165 | * \brief A custom mesh loader. |
166 | * |
167 | * Loads mesh data from external files in a variety of formats. |
168 | * Qt3DRender::QMesh loads data into a single mesh. |
169 | * |
170 | * In Qt3D 5.9, QMesh supports the following formats: |
171 | * |
172 | * \list |
173 | * \li Wavefront OBJ |
174 | * \li Stanford Triangle Format PLY |
175 | * \li STL (STereoLithography) |
176 | * \endlist |
177 | * |
178 | * QMesh will also support the following format if the SDK is installed and the fbx geometry loader plugin is built and found: |
179 | * \list |
180 | * \li Autodesk FBX |
181 | * \endlist |
182 | * |
183 | * If you wish to load an entire scene made of several objects, you should rather use the Qt3DRender::QSceneLoader instead. |
184 | * |
185 | * \sa Qt3DRender::QSceneLoader |
186 | */ |
187 | |
188 | /*! |
189 | \enum Qt3DRender::QMesh::Status |
190 | |
191 | This enum identifies the status of shader used. |
192 | |
193 | \value None A source mesh hasn't been assigned a source yet |
194 | \value Loading The mesh geometry is loading |
195 | \value Ready The mesh geometry was successfully loaded |
196 | \value Error An error occurred while loading the mesh |
197 | */ |
198 | |
199 | /*! |
200 | * Constructs a new QMesh with \a parent. |
201 | */ |
202 | QMesh::QMesh(QNode *parent) |
203 | : QGeometryRenderer(*new QMeshPrivate, parent) |
204 | { |
205 | } |
206 | |
207 | /*! \internal */ |
208 | QMesh::~QMesh() |
209 | { |
210 | } |
211 | |
212 | /*! \internal */ |
213 | QMesh::QMesh(QMeshPrivate &dd, QNode *parent) |
214 | : QGeometryRenderer(dd, parent) |
215 | { |
216 | } |
217 | |
218 | // TODO Unused remove in Qt6 |
219 | void QMesh::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &) |
220 | { |
221 | } |
222 | |
223 | void QMesh::setSource(const QUrl& source) |
224 | { |
225 | Q_D(QMesh); |
226 | if (d->m_source == source) |
227 | return; |
228 | d->m_source = source; |
229 | d->updateFunctor(); |
230 | const bool blocked = blockNotifications(block: true); |
231 | emit sourceChanged(source); |
232 | blockNotifications(block: blocked); |
233 | } |
234 | |
235 | /*! |
236 | * \property QMesh::source |
237 | * |
238 | * Holds the \a source url to the file containing the custom mesh. |
239 | */ |
240 | QUrl QMesh::source() const |
241 | { |
242 | Q_D(const QMesh); |
243 | return d->m_source; |
244 | } |
245 | |
246 | void QMesh::setMeshName(const QString &meshName) |
247 | { |
248 | Q_D(QMesh); |
249 | if (d->m_meshName == meshName) |
250 | return; |
251 | d->m_meshName = meshName; |
252 | d->updateFunctor(); |
253 | const bool blocked = blockNotifications(block: true); |
254 | emit meshNameChanged(meshName); |
255 | blockNotifications(block: blocked); |
256 | } |
257 | |
258 | /*! |
259 | * \property QMesh::meshName |
260 | * |
261 | * Holds the name of the mesh. |
262 | */ |
263 | QString QMesh::meshName() const |
264 | { |
265 | Q_D(const QMesh); |
266 | return d->m_meshName; |
267 | } |
268 | |
269 | /*! |
270 | \property QMesh::status |
271 | |
272 | Holds the status of the mesh loading. |
273 | \sa Qt3DRender::QMesh::Status |
274 | */ |
275 | QMesh::Status QMesh::status() const |
276 | { |
277 | Q_D(const QMesh); |
278 | return d->m_status; |
279 | } |
280 | |
281 | /*! |
282 | * \internal |
283 | */ |
284 | MeshLoaderFunctor::MeshLoaderFunctor(QMesh *mesh, const QByteArray &sourceData) |
285 | : QGeometryFactory() |
286 | , m_mesh(mesh->id()) |
287 | , m_sourcePath(mesh->source()) |
288 | , m_meshName(mesh->meshName()) |
289 | , m_sourceData(sourceData) |
290 | , m_nodeManagers(nullptr) |
291 | , m_downloaderService(nullptr) |
292 | , m_status(QMesh::None) |
293 | { |
294 | } |
295 | |
296 | /*! |
297 | * \internal |
298 | */ |
299 | QGeometry *MeshLoaderFunctor::operator()() |
300 | { |
301 | m_status = QMesh::Loading; |
302 | |
303 | if (m_sourcePath.isEmpty()) { |
304 | qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh is empty, nothing to load" ; |
305 | m_status = QMesh::Error; |
306 | return nullptr; |
307 | } |
308 | |
309 | QStringList ext; |
310 | if (!Qt3DCore::QDownloadHelperService::isLocal(url: m_sourcePath)) { |
311 | if (m_sourceData.isEmpty()) { |
312 | if (m_mesh) { |
313 | // Output a warning in the case a user is calling the functor directly |
314 | // in the frontend |
315 | if (m_nodeManagers == nullptr || m_downloaderService == nullptr) { |
316 | qWarning() << "Mesh source points to a remote URL. Remotes meshes can only be loaded if the geometry is processed by the Qt3DRender backend" ; |
317 | m_status = QMesh::Error; |
318 | return nullptr; |
319 | } |
320 | Qt3DCore::QDownloadRequestPtr request(new MeshDownloadRequest(m_mesh, m_sourcePath, m_nodeManagers)); |
321 | m_downloaderService->submitRequest(request); |
322 | } |
323 | return nullptr; |
324 | } |
325 | |
326 | QMimeDatabase db; |
327 | QMimeType mtype = db.mimeTypeForData(data: m_sourceData); |
328 | if (mtype.isValid()) { |
329 | ext = mtype.suffixes(); |
330 | } |
331 | QFileInfo finfo(m_sourcePath.path()); |
332 | ext << finfo.suffix(); |
333 | ext.removeAll(t: QLatin1String("" )); |
334 | if (!ext.contains(str: QLatin1String("obj" ))) |
335 | ext << QLatin1String("obj" ); |
336 | } else { |
337 | QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(url: m_sourcePath); |
338 | QFileInfo finfo(filePath); |
339 | if (finfo.suffix().isEmpty()) |
340 | ext << QLatin1String("obj" ); |
341 | else |
342 | ext << finfo.suffix(); |
343 | } |
344 | |
345 | QScopedPointer<QGeometryLoaderInterface> loader; |
346 | for (const QString &e: qAsConst(t&: ext)) { |
347 | loader.reset(other: qLoadPlugin<QGeometryLoaderInterface, QGeometryLoaderFactory>(loader: geometryLoader(), key: e)); |
348 | if (loader) |
349 | break; |
350 | } |
351 | if (!loader) { |
352 | qCWarning(Render::Jobs, "unsupported format encountered (%s)" , qPrintable(ext.join(QLatin1String(", " )))); |
353 | m_status = QMesh::Error; |
354 | return nullptr; |
355 | } |
356 | |
357 | if (m_sourceData.isEmpty()) { |
358 | QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(url: m_sourcePath); |
359 | QFile file(filePath); |
360 | if (!file.open(flags: QIODevice::ReadOnly)) { |
361 | qCDebug(Render::Jobs) << "Could not open file" << filePath << "for reading" ; |
362 | m_status = QMesh::Error; |
363 | return nullptr; |
364 | } |
365 | |
366 | if (loader->load(ioDev: &file, subMesh: m_meshName)) { |
367 | Qt3DRender::QGeometry *geometry = loader->geometry(); |
368 | m_status = geometry != nullptr ? QMesh::Ready : QMesh::Error; |
369 | return geometry; |
370 | } |
371 | qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << filePath; |
372 | } else { |
373 | QT_PREPEND_NAMESPACE(QBuffer) buffer(&m_sourceData); |
374 | if (!buffer.open(openMode: QIODevice::ReadOnly)) { |
375 | m_status = QMesh::Error; |
376 | return nullptr; |
377 | } |
378 | |
379 | if (loader->load(ioDev: &buffer, subMesh: m_meshName)) { |
380 | Qt3DRender::QGeometry *geometry = loader->geometry(); |
381 | m_status = geometry != nullptr ? QMesh::Ready : QMesh::Error; |
382 | return geometry; |
383 | } |
384 | |
385 | qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << m_sourcePath; |
386 | } |
387 | |
388 | return nullptr; |
389 | } |
390 | |
391 | /*! |
392 | * \internal |
393 | */ |
394 | bool MeshLoaderFunctor::equals(const QGeometryFactory &other) const |
395 | { |
396 | const MeshLoaderFunctor *otherFunctor = functor_cast<MeshLoaderFunctor>(other: &other); |
397 | if (otherFunctor != nullptr) |
398 | return (otherFunctor->m_sourcePath == m_sourcePath && |
399 | otherFunctor->m_sourceData.isEmpty() == m_sourceData.isEmpty() && |
400 | otherFunctor->m_meshName == m_meshName && |
401 | otherFunctor->m_downloaderService == m_downloaderService && |
402 | otherFunctor->m_nodeManagers == m_nodeManagers); |
403 | return false; |
404 | } |
405 | |
406 | /*! |
407 | * \internal |
408 | */ |
409 | MeshDownloadRequest::MeshDownloadRequest(Qt3DCore::QNodeId mesh, QUrl source, Render::NodeManagers *managers) |
410 | : Qt3DCore::QDownloadRequest(source) |
411 | , m_mesh(mesh) |
412 | , m_nodeManagers(managers) |
413 | { |
414 | } |
415 | |
416 | // Called in Aspect Thread context (not a Qt3D AspectJob) |
417 | // We are sure that when this is called, no AspectJob are running |
418 | void MeshDownloadRequest::onCompleted() |
419 | { |
420 | if (cancelled() || !succeeded()) |
421 | return; |
422 | |
423 | if (!m_nodeManagers) |
424 | return; |
425 | |
426 | Render::GeometryRenderer *renderer = m_nodeManagers->geometryRendererManager()->lookupResource(id: m_mesh); |
427 | if (!renderer) |
428 | return; |
429 | |
430 | QGeometryFactoryPtr geometryFactory = renderer->geometryFactory(); |
431 | if (!geometryFactory.isNull() && geometryFactory->id() == Qt3DRender::functorTypeId<MeshLoaderFunctor>()) { |
432 | QSharedPointer<MeshLoaderFunctor> functor = qSharedPointerCast<MeshLoaderFunctor>(src: geometryFactory); |
433 | |
434 | // We make sure we are setting the result for the right request |
435 | // (the functor for the mesh could have changed in the meantime) |
436 | if (m_url == functor->sourcePath()) { |
437 | functor->setSourceData(m_data); |
438 | |
439 | // mark the component as dirty so that the functor runs again in the correct job |
440 | m_nodeManagers->geometryRendererManager()->addDirtyGeometryRenderer(bufferId: m_mesh); |
441 | } |
442 | } |
443 | } |
444 | |
445 | } // namespace Qt3DRender |
446 | |
447 | QT_END_NAMESPACE |
448 | |