| 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 | |
| 31 | QT_BEGIN_NAMESPACE |
| 32 | |
| 33 | namespace Qt3DRender { |
| 34 | |
| 35 | using namespace Qt3DCore; |
| 36 | |
| 37 | Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, geometryLoader, (QGeometryLoaderFactory_iid, QLatin1String("/geometryloaders" ), Qt::CaseInsensitive)) |
| 38 | |
| 39 | QMeshPrivate::QMeshPrivate() |
| 40 | : QGeometryRendererPrivate() |
| 41 | , m_status(QMesh::None) |
| 42 | { |
| 43 | } |
| 44 | |
| 45 | QMeshPrivate *QMeshPrivate::get(QMesh *q) |
| 46 | { |
| 47 | return q->d_func(); |
| 48 | } |
| 49 | |
| 50 | void QMeshPrivate::setScene(Qt3DCore::QScene *scene) |
| 51 | { |
| 52 | QGeometryRendererPrivate::setScene(scene); |
| 53 | updateFunctor(); |
| 54 | } |
| 55 | |
| 56 | void QMeshPrivate::updateFunctor() |
| 57 | { |
| 58 | Q_Q(QMesh); |
| 59 | m_geometryFactory = QGeometryFactoryPtr(new MeshLoaderFunctor(q)); |
| 60 | update(); |
| 61 | } |
| 62 | |
| 63 | void 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 | */ |
| 170 | QMesh::QMesh(QNode *parent) |
| 171 | : QGeometryRenderer(*new QMeshPrivate, parent) |
| 172 | { |
| 173 | } |
| 174 | |
| 175 | /*! \internal */ |
| 176 | QMesh::~QMesh() |
| 177 | { |
| 178 | } |
| 179 | |
| 180 | /*! \internal */ |
| 181 | QMesh::QMesh(QMeshPrivate &dd, QNode *parent) |
| 182 | : QGeometryRenderer(dd, parent) |
| 183 | { |
| 184 | } |
| 185 | |
| 186 | void 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 | */ |
| 203 | QUrl QMesh::source() const |
| 204 | { |
| 205 | Q_D(const QMesh); |
| 206 | return d->m_source; |
| 207 | } |
| 208 | |
| 209 | void 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 | */ |
| 226 | QString 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 | */ |
| 238 | QMesh::Status QMesh::status() const |
| 239 | { |
| 240 | Q_D(const QMesh); |
| 241 | return d->m_status; |
| 242 | } |
| 243 | |
| 244 | /*! |
| 245 | * \internal |
| 246 | */ |
| 247 | MeshLoaderFunctor::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 | */ |
| 262 | Qt3DCore::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 | */ |
| 359 | bool 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 | */ |
| 374 | MeshDownloadRequest::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 |
| 383 | void 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 | |
| 412 | QT_END_NAMESPACE |
| 413 | |
| 414 | #include "moc_qmesh.cpp" |
| 415 | |