| 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 | |