/**************************************************************************** ** ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qmesh.h" #include "qmesh_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace Qt3DRender { Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, geometryLoader, (QGeometryLoaderFactory_iid, QLatin1String("/geometryloaders"), Qt::CaseInsensitive)) QMeshPrivate::QMeshPrivate() : QGeometryRendererPrivate() , m_status(QMesh::None) { } QMeshPrivate *QMeshPrivate::get(QMesh *q) { return q->d_func(); } void QMeshPrivate::setScene(Qt3DCore::QScene *scene) { QGeometryRendererPrivate::setScene(scene); updateFunctor(); } void QMeshPrivate::updateFunctor() { Q_Q(QMesh); m_geometryFactory = QGeometryFactoryPtr(new MeshLoaderFunctor(q)); update(); } void QMeshPrivate::setStatus(QMesh::Status status) { if (m_status != status) { Q_Q(QMesh); m_status = status; const bool wasBlocked = q->blockNotifications(true); emit q->statusChanged(status); q->blockNotifications(wasBlocked); } } /*! * \qmltype Mesh * \instantiates Qt3DRender::QMesh * \inqmlmodule Qt3D.Render * \brief A custom mesh loader. * * Loads mesh data from external files in a variety of formats. * * In Qt3D 5.9, Mesh supports the following formats: * * \list * \li Wavefront OBJ * \li Stanford Triangle Format PLY * \li STL (STereoLithography) * \endlist * * QMesh will also support the following format if the SDK is installed and the fbx geometry loader plugin is built and found. * \list * \li Autodesk FBX * \endlist */ /*! * \qmlproperty url Mesh::source * * Holds the source url to the file containing the custom mesh. */ /*! * \qmlproperty string Mesh::meshName * * Filter indicating which part of the mesh should be loaded. * * If meshName is empty (the default), then the entire mesh is loaded. * * If meshName is a plain string, then only the sub-mesh matching that name, if present, will be loaded. * * If meshName is a regular expression, than all sub-meshes matching the expression will be loaded. * * \note Only Wavefront OBJ files support sub-meshes. * * \sa QRegularExpression */ /*! \qmlproperty enumeration Mesh::status Holds the status of the mesh loading. \sa Qt3DRender::QMesh::Status \readonly */ /*! * \class Qt3DRender::QMesh * \inheaderfile Qt3DRender/QMesh * \inmodule Qt3DRender * * \inherits Qt3DRender::QGeometryRenderer * * \brief A custom mesh loader. * * Loads mesh data from external files in a variety of formats. * Qt3DRender::QMesh loads data into a single mesh. * * In Qt3D 5.9, QMesh supports the following formats: * * \list * \li Wavefront OBJ * \li Stanford Triangle Format PLY * \li STL (STereoLithography) * \endlist * * QMesh will also support the following format if the SDK is installed and the fbx geometry loader plugin is built and found: * \list * \li Autodesk FBX * \endlist * * If you wish to load an entire scene made of several objects, you should rather use the Qt3DRender::QSceneLoader instead. * * \sa Qt3DRender::QSceneLoader */ /*! \enum Qt3DRender::QMesh::Status This enum identifies the status of shader used. \value None A source mesh hasn't been assigned a source yet \value Loading The mesh geometry is loading \value Ready The mesh geometry was successfully loaded \value Error An error occurred while loading the mesh */ /*! * Constructs a new QMesh with \a parent. */ QMesh::QMesh(QNode *parent) : QGeometryRenderer(*new QMeshPrivate, parent) { } /*! \internal */ QMesh::~QMesh() { } /*! \internal */ QMesh::QMesh(QMeshPrivate &dd, QNode *parent) : QGeometryRenderer(dd, parent) { } void QMesh::setSource(const QUrl& source) { Q_D(QMesh); if (d->m_source == source) return; d->m_source = source; d->updateFunctor(); const bool blocked = blockNotifications(true); emit sourceChanged(source); blockNotifications(blocked); } /*! * \property QMesh::source * * Holds the \a source url to the file containing the custom mesh. */ QUrl QMesh::source() const { Q_D(const QMesh); return d->m_source; } void QMesh::setMeshName(const QString &meshName) { Q_D(QMesh); if (d->m_meshName == meshName) return; d->m_meshName = meshName; d->updateFunctor(); const bool blocked = blockNotifications(true); emit meshNameChanged(meshName); blockNotifications(blocked); } /*! * \property QMesh::meshName * * Holds the name of the mesh. */ QString QMesh::meshName() const { Q_D(const QMesh); return d->m_meshName; } /*! \property QMesh::status Holds the status of the mesh loading. \sa Qt3DRender::QMesh::Status */ QMesh::Status QMesh::status() const { Q_D(const QMesh); return d->m_status; } /*! * \internal */ MeshLoaderFunctor::MeshLoaderFunctor(QMesh *mesh, const QByteArray &sourceData) : QGeometryFactory() , m_mesh(mesh->id()) , m_sourcePath(mesh->source()) , m_meshName(mesh->meshName()) , m_sourceData(sourceData) , m_nodeManagers(nullptr) , m_downloaderService(nullptr) , m_status(QMesh::None) { } /*! * \internal */ QGeometry *MeshLoaderFunctor::operator()() { m_status = QMesh::Loading; if (m_sourcePath.isEmpty()) { qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh is empty, nothing to load"; m_status = QMesh::Error; return nullptr; } QStringList ext; if (!Qt3DCore::QDownloadHelperService::isLocal(m_sourcePath)) { if (m_sourceData.isEmpty()) { if (m_mesh) { // Output a warning in the case a user is calling the functor directly // in the frontend if (m_nodeManagers == nullptr || m_downloaderService == nullptr) { qWarning() << "Mesh source points to a remote URL. Remotes meshes can only be loaded if the geometry is processed by the Qt3DRender backend"; m_status = QMesh::Error; return nullptr; } Qt3DCore::QDownloadRequestPtr request(new MeshDownloadRequest(m_mesh, m_sourcePath, m_nodeManagers)); m_downloaderService->submitRequest(request); } return nullptr; } QMimeDatabase db; QMimeType mtype = db.mimeTypeForData(m_sourceData); if (mtype.isValid()) { ext = mtype.suffixes(); } QFileInfo finfo(m_sourcePath.path()); ext << finfo.suffix(); ext.removeAll(QLatin1String("")); if (!ext.contains(QLatin1String("obj"))) ext << QLatin1String("obj"); } else { QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_sourcePath); QFileInfo finfo(filePath); if (finfo.suffix().isEmpty()) ext << QLatin1String("obj"); else ext << finfo.suffix(); } QScopedPointer loader; for (const QString &e: qAsConst(ext)) { loader.reset(qLoadPlugin(geometryLoader(), e)); if (loader) break; } if (!loader) { qCWarning(Render::Jobs, "unsupported format encountered (%s)", qPrintable(ext.join(QLatin1String(", ")))); m_status = QMesh::Error; return nullptr; } if (m_sourceData.isEmpty()) { QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_sourcePath); QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { qCDebug(Render::Jobs) << "Could not open file" << filePath << "for reading"; m_status = QMesh::Error; return nullptr; } if (loader->load(&file, m_meshName)) { Qt3DRender::QGeometry *geometry = loader->geometry(); m_status = geometry != nullptr ? QMesh::Ready : QMesh::Error; return geometry; } qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << filePath; } else { QT_PREPEND_NAMESPACE(QBuffer) buffer(&m_sourceData); if (!buffer.open(QIODevice::ReadOnly)) { m_status = QMesh::Error; return nullptr; } if (loader->load(&buffer, m_meshName)) { Qt3DRender::QGeometry *geometry = loader->geometry(); m_status = geometry != nullptr ? QMesh::Ready : QMesh::Error; return geometry; } qCWarning(Render::Jobs) << Q_FUNC_INFO << "Mesh loading failure for:" << m_sourcePath; } return nullptr; } /*! * \internal */ bool MeshLoaderFunctor::operator ==(const QGeometryFactory &other) const { const MeshLoaderFunctor *otherFunctor = functor_cast(&other); if (otherFunctor != nullptr) return (otherFunctor->m_sourcePath == m_sourcePath && otherFunctor->m_sourceData.isEmpty() == m_sourceData.isEmpty() && otherFunctor->m_meshName == m_meshName && otherFunctor->m_downloaderService == m_downloaderService && otherFunctor->m_nodeManagers == m_nodeManagers); return false; } /*! * \internal */ MeshDownloadRequest::MeshDownloadRequest(Qt3DCore::QNodeId mesh, QUrl source, Render::NodeManagers *managers) : Qt3DCore::QDownloadRequest(source) , m_mesh(mesh) , m_nodeManagers(managers) { } // Called in Aspect Thread context (not a Qt3D AspectJob) // We are sure that when this is called, no AspectJob are running void MeshDownloadRequest::onCompleted() { if (cancelled() || !succeeded()) return; if (!m_nodeManagers) return; Render::GeometryRenderer *renderer = m_nodeManagers->geometryRendererManager()->lookupResource(m_mesh); if (!renderer) return; QGeometryFactoryPtr geometryFactory = renderer->geometryFactory(); if (!geometryFactory.isNull() && geometryFactory->id() == Qt3DRender::functorTypeId()) { QSharedPointer functor = qSharedPointerCast(geometryFactory); // We make sure we are setting the result for the right request // (the functor for the mesh could have changed in the meantime) if (m_url == functor->sourcePath()) { functor->setSourceData(m_data); // mark the component as dirty so that the functor runs again in the correct job m_nodeManagers->geometryRendererManager()->addDirtyGeometryRenderer(m_mesh); } } } } // namespace Qt3DRender QT_END_NAMESPACE