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

source code of qt3d/src/render/geometry/qmesh.cpp