From dea48b7e47e54236453cf83c8ef5670142fe01a2 Mon Sep 17 00:00:00 2001 From: Maximilian Goldstein Date: Thu, 21 Jan 2021 17:07:24 +0100 Subject: Qt.labs.wavefrontmesh: Make plugin optional This moves the wavefrontmesh types into a new library and is meant to make them availabe to the QML compiler at some point in the future. Task-number: QTBUG-90487 Change-Id: I9ab9dfc62ef9c205ce4649df33a6c1e2ac0ca639 Reviewed-by: Fabian Kosmale --- src/imports/wavefrontmesh/CMakeLists.txt | 5 +- src/imports/wavefrontmesh/plugin.cpp | 5 +- src/labs/CMakeLists.txt | 4 + src/labs/wavefrontmesh/CMakeLists.txt | 23 + src/labs/wavefrontmesh/qqmlwavefrontmeshglobal_p.h | 73 +++ src/labs/wavefrontmesh/qwavefrontmesh.cpp | 681 +++++++++++++++++++++ src/labs/wavefrontmesh/qwavefrontmesh.h | 115 ++++ sync.profile | 3 +- 8 files changed, 900 insertions(+), 9 deletions(-) create mode 100644 src/labs/wavefrontmesh/CMakeLists.txt create mode 100644 src/labs/wavefrontmesh/qqmlwavefrontmeshglobal_p.h create mode 100644 src/labs/wavefrontmesh/qwavefrontmesh.cpp create mode 100644 src/labs/wavefrontmesh/qwavefrontmesh.h diff --git a/src/imports/wavefrontmesh/CMakeLists.txt b/src/imports/wavefrontmesh/CMakeLists.txt index 85065589ec..7dd97c8056 100644 --- a/src/imports/wavefrontmesh/CMakeLists.txt +++ b/src/imports/wavefrontmesh/CMakeLists.txt @@ -9,15 +9,12 @@ qt_internal_add_qml_module(qmlwavefrontmeshplugin VERSION "${CMAKE_PROJECT_VERSION}" CLASSNAME QmlWavefrontMeshPlugin SKIP_TYPE_REGISTRATION - GENERATE_QMLTYPES - INSTALL_QMLTYPES SOURCES plugin.cpp - qwavefrontmesh.cpp qwavefrontmesh.h PUBLIC_LIBRARIES Qt::CorePrivate Qt::QmlPrivate - Qt::QuickPrivate + Qt::LabsWavefrontMeshPrivate ) #### Keys ignored in scope 1:.:.:wavefrontmesh.pro:: diff --git a/src/imports/wavefrontmesh/plugin.cpp b/src/imports/wavefrontmesh/plugin.cpp index eea0db19db..16c40ae8d5 100644 --- a/src/imports/wavefrontmesh/plugin.cpp +++ b/src/imports/wavefrontmesh/plugin.cpp @@ -40,10 +40,7 @@ #include #include -#include "qwavefrontmesh.h" - -extern void qml_register_types_Qt_labs_wavefrontmesh(); - +#include QT_BEGIN_NAMESPACE class QmlWavefrontMeshPlugin : public QQmlEngineExtensionPlugin diff --git a/src/labs/CMakeLists.txt b/src/labs/CMakeLists.txt index de832a1db2..4170bdf621 100644 --- a/src/labs/CMakeLists.txt +++ b/src/labs/CMakeLists.txt @@ -7,3 +7,7 @@ endif() if(TARGET Qt::Quick) add_subdirectory(animation) endif() + +if(QT_FEATURE_quick_shadereffect AND TARGET Qt::Quick) + add_subdirectory(wavefrontmesh) +endif() diff --git a/src/labs/wavefrontmesh/CMakeLists.txt b/src/labs/wavefrontmesh/CMakeLists.txt new file mode 100644 index 0000000000..88e753ab24 --- /dev/null +++ b/src/labs/wavefrontmesh/CMakeLists.txt @@ -0,0 +1,23 @@ +qt_internal_add_module(LabsWavefrontMesh + GENERATE_METATYPES + SOURCES + qwavefrontmesh.cpp qwavefrontmesh.h + qqmlwavefrontmeshglobal_p.h + DEFINES + QT_BUILD_LABSWAVEFRONTMESH_LIB + PUBLIC_LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::QuickPrivate +) + + +set_target_properties(LabsWavefrontMesh PROPERTIES + QT_QML_MODULE_INSTALL_QMLTYPES TRUE + QT_QML_MODULE_VERSION ${CMAKE_PROJECT_VERSION} + QT_QML_MODULE_URI Qt.labs.wavefrontmesh + QT_QMLTYPES_FILENAME plugins.qmltypes + QT_QML_MODULE_INSTALL_DIR "${INSTALL_QMLDIR}/Qt/labs/wavefrontmesh" +) + +qt6_qml_type_registration(LabsWavefrontMesh) diff --git a/src/labs/wavefrontmesh/qqmlwavefrontmeshglobal_p.h b/src/labs/wavefrontmesh/qqmlwavefrontmeshglobal_p.h new file mode 100644 index 0000000000..ca57c1f7f3 --- /dev/null +++ b/src/labs/wavefrontmesh/qqmlwavefrontmeshglobal_p.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQml 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$ +** +****************************************************************************/ + +#ifndef QTLABSWAVEFRONTMESHGLOBAL_P_H +#define QTLABSWAVEFRONTMESHGLOBAL_P_H + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +#if !defined(QT_STATIC) +# if defined(QT_BUILD_LABSWAVEFRONTMESH_LIB) +# define Q_LABSWAVEFRONTMESH_EXPORT Q_DECL_EXPORT +# else +# define Q_LABSWAVEFRONTMESH_EXPORT Q_DECL_IMPORT +# endif +#else +# define Q_LABSWAVEFRONTMESH_EXPORT +#endif +#define Q_LABSWAVEFRONTMESH_PRIVATE_EXPORT Q_LABSWAVEFRONTMESH_EXPORT + +QT_END_NAMESPACE + +void Q_LABSWAVEFRONTMESH_PRIVATE_EXPORT qml_register_types_Qt_labs_wavefrontmesh(); + +#endif // QTLABSWAVEFRONTMESHGLOBAL_P_H diff --git a/src/labs/wavefrontmesh/qwavefrontmesh.cpp b/src/labs/wavefrontmesh/qwavefrontmesh.cpp new file mode 100644 index 0000000000..6be5c3d73f --- /dev/null +++ b/src/labs/wavefrontmesh/qwavefrontmesh.cpp @@ -0,0 +1,681 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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 "qwavefrontmesh.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWavefrontMeshPrivate : public QObjectPrivate +{ +public: + QWavefrontMeshPrivate() + : lastError(QWavefrontMesh::NoError) + {} + + Q_DECLARE_PUBLIC(QWavefrontMesh) + + static QWavefrontMeshPrivate *get(QWavefrontMesh *mesh) + { + return mesh->d_func(); + } + + static const QWavefrontMeshPrivate *get(const QWavefrontMesh *mesh) + { + return mesh->d_func(); + } + + QVector > indexes; + QVector vertexes; + QVector textureCoordinates; + + QUrl source; + QWavefrontMesh::Error lastError; + + QVector3D planeV; + QVector3D planeW; +}; + +/*! + \qmlmodule Qt.labs.wavefrontmesh 1.\QtMinorVersion + \title Qt Labs WavefrontMesh QML Types + \ingroup qmlmodules + \brief The WavefrontMesh provides a mesh based on a Wavefront .obj file. + + To use this module, import the module with the following line: + + \qml + import Qt.labs.wavefrontmesh + \endqml +*/ + +/*! + \qmltype WavefrontMesh + \inqmlmodule Qt.labs.wavefrontmesh + \instantiates QWavefrontMesh + \ingroup qtquick-effects + \brief The WavefrontMesh provides a mesh based on a Wavefront .obj file. + \since 5.12 + + WavefrontMesh reads the geometry from a Wavefront .obj file and generates + a two-dimensional \l{QSGGeometry}{geometry} from this. If the .obj file + contains a three-dimensional shape, it will be orthographically projected, + onto a plane. If defined, this is given by \l projectionPlaneV + and \l projectionPlaneW. Otherwise, the first face encountered in the data + will be used to determine the projection plane. + + If the file contains texture coordinates, these will also be used. Otherwise, + the vertexes of the object will be normalized and used. + + The mesh can be used in a ShaderEffect to define the shaded geometry. The + geometry will be normalized before use, so the position and scale of the + input objects have no impact on the result. + + \note Some Wavefront exporters will change the source scene's coordinate system + before exporting it. This can cause unexpected results when Qt applies the + projection. If the visual results are not as you expect, try checking the export + parameters and the documentation of the editor tool to see if this is the case. + + For instance, the following example takes an .obj file containing a standard torus + and visualizes the automatically generated texture coordinates. + + \table + \row + \li \image qtlabs-wavefrontmesh.png + \li \qml + import QtQuick 2.\1 + import Qt.labs.wavefrontmesh 1.\1 + + ShaderEffect { + width: 200 + height: 200 + mesh: WavefrontMesh { + source: "torus.obj" + projectionPlaneV: Qt.vector3d(0, 1, 0) + projectionPlaneW: Qt.vector3d(1, 0, 0) + } + vertexShader: " + uniform highp mat4 qt_Matrix; + attribute highp vec4 qt_Vertex; + attribute highp vec2 qt_MultiTexCoord0; + varying highp vec2 coord; + void main() { + coord = qt_MultiTexCoord0; + gl_Position = qt_Matrix * qt_Vertex; + }" + fragmentShader: " + varying highp vec2 coord; + uniform lowp float qt_Opacity; + void main() { + gl_FragColor = vec4(coord.x, coord.y, 0.0, 1.0); + }" + + } + \endqml + \endtable + + \note Since the input is a 3D torus, we need to define the projection plane. This would not be necessary when + using a 2D shape as input. We use the XY plane in this case, because of the orientation of the input. +*/ + +QWavefrontMesh::QWavefrontMesh(QObject *parent) + : QQuickShaderEffectMesh(*(new QWavefrontMeshPrivate), parent) +{ + connect(this, &QWavefrontMesh::sourceChanged, this, &QWavefrontMesh::readData); + connect(this, &QWavefrontMesh::projectionPlaneVChanged, this, &QQuickShaderEffectMesh::geometryChanged); + connect(this, &QWavefrontMesh::projectionPlaneWChanged, this, &QQuickShaderEffectMesh::geometryChanged); +} + +QWavefrontMesh::~QWavefrontMesh() +{ +} + +/*! + \qmlproperty enumeration WavefrontMesh::lastError + + This property holds the last error, if any, that occurred when parsing the + source or building the mesh. + + \list + \li WavefrontMesh.NoError No error has occurred. + \li WavefrontMesh.InvalidSourceError The source was not recognized as a valid .obj file. + \li WavefrontMesh.UnsupportedFaceShapeError The faces in the source is of an unsupported type. + WavefrontMesh only supports triangles and convex quads. + \li WavefrontMesh.UnsupportedIndexSizeError The source shape is too large. Only 16 bit indexes are supported. + \li WavefrontMesh.FileNotFoundError The source file was not found. + \li WavefrontMesh.MissingPositionAttributeError The 'qt_Vertex' attribute is missing from the shaders. + \li WavefrontMesh.MissingTextureCoordinateAttributeError The texture coordinate attribute in the shaders is wrongly named. Use 'qt_MultiTexCoord0'. + \li WavefrontMesh.MissingPositionAndTextureCoordinateAttributesError Both the 'qt_Vertex' and 'qt_MultiTexCoord0' attributes are missing from the shaders. + \li WavefrontMesh.TooManyAttributesError The shaders expect too many attributes (maximum is two: Position, 'qt_Vertex', and texture coordinate, 'qt_MultiTexCoord0'). + \li WavefrontMesh.InvalidPlaneDefinitionError The V and W vectors in the plane cannot be null, nor parallel to each other. + \endlist +*/ + +QWavefrontMesh::Error QWavefrontMesh::lastError() const +{ + Q_D(const QWavefrontMesh); + return d->lastError; +} + +void QWavefrontMesh::setLastError(Error lastError) +{ + Q_D(QWavefrontMesh); + if (d->lastError == lastError) + return; + + d->lastError = lastError; + emit lastErrorChanged(); +} + +/*! + \qmlproperty url WavefrontMesh::source + + This property holds the URL of the source. This must be either a local file or in qrc. The source will + be read as a Wavefront .obj file and the geometry will be updated. +*/ +QUrl QWavefrontMesh::source() const +{ + Q_D(const QWavefrontMesh); + return d->source; +} + +void QWavefrontMesh::setSource(const QUrl &source) +{ + Q_D(QWavefrontMesh); + if (d->source == source) + return; + + d->source = source; + emit sourceChanged(); +} + +void QWavefrontMesh::readData() +{ + Q_D(QWavefrontMesh); + d->vertexes.clear(); + d->textureCoordinates.clear(); + d->indexes.clear(); + + QString localFile = QQmlFile::urlToLocalFileOrQrc(d->source); + if (!localFile.isEmpty()) { + QFile file(localFile); + if (file.open(QIODevice::ReadOnly)) { + QTextStream stream(&file); + + QString buffer; + buffer.reserve(256); + + static QChar space(QLatin1Char(' ')); + static QChar slash(QLatin1Char('/')); + + while (!stream.atEnd()) { + stream.readLineInto(&buffer); + auto tokens = QStringView{buffer}.split(space, Qt::SkipEmptyParts); + if (tokens.size() < 2) + continue; + + QByteArray command = tokens.at(0).toLatin1(); + + if (command == "vt") { + bool ok; + float u = tokens.at(1).toFloat(&ok); + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + float v = tokens.size() > 2 ? tokens.at(2).toFloat(&ok) : 0.0; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + d->textureCoordinates.append(QVector2D(u, v)); + } else if (command == "v") { + // Format: v [w] + if (tokens.length() < 4 || tokens.length() > 5) { + setLastError(InvalidSourceError); + return; + } + + bool ok; + + float x = tokens.at(1).toFloat(&ok); + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + float y = tokens.at(2).toFloat(&ok); + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + float z = tokens.at(3).toFloat(&ok); + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + d->vertexes.append(QVector3D(x, y, z)); + } else if (command == "f") { + // The scenegraph only supports triangles, so we + // support triangles and quads (which we split up) + int p1, p2, p3; + int t1 = 0; + int t2 = 0; + int t3 = 0; + if (tokens.size() >= 4 && tokens.size() <= 5) { + { + bool ok; + auto faceTokens = tokens.at(1).split(slash, Qt::SkipEmptyParts); + Q_ASSERT(!faceTokens.isEmpty()); + + p1 = faceTokens.at(0).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + if (faceTokens.size() > 1) { + t1 = faceTokens.at(1).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + } + } + + { + bool ok; + auto faceTokens = tokens.at(2).split(slash, Qt::SkipEmptyParts); + Q_ASSERT(!faceTokens.isEmpty()); + + p2 = faceTokens.at(0).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + if (faceTokens.size() > 1) { + t2 = faceTokens.at(1).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + } + } + + { + bool ok; + auto faceTokens = tokens.at(3).split(slash, Qt::SkipEmptyParts); + Q_ASSERT(!faceTokens.isEmpty()); + + p3 = faceTokens.at(0).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + if (faceTokens.size() > 1) { + t3 = faceTokens.at(1).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + } + } + + if (Q_UNLIKELY(p1 < 0 || p1 > UINT16_MAX + || p2 < 0 || p2 > UINT16_MAX + || p3 < 0 || p3 > UINT16_MAX + || t1 < 0 || t1 > UINT16_MAX + || t2 < 0 || t2 > UINT16_MAX + || t3 < 0 || t3 > UINT16_MAX)) { + setLastError(UnsupportedIndexSizeError); + return; + } + + d->indexes.append(qMakePair(ushort(p1), ushort(t1))); + d->indexes.append(qMakePair(ushort(p2), ushort(t2))); + d->indexes.append(qMakePair(ushort(p3), ushort(t3))); + } else { + setLastError(UnsupportedFaceShapeError); + return; + } + + if (tokens.size() == 5) { + bool ok; + auto faceTokens = tokens.at(4).split(slash, Qt::SkipEmptyParts); + Q_ASSERT(!faceTokens.isEmpty()); + + int p4 = faceTokens.at(0).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + + int t4 = 0; + if (faceTokens.size() > 1) { + t4 = faceTokens.at(1).toInt(&ok) - 1; + if (!ok) { + setLastError(InvalidSourceError); + return; + } + } + + if (Q_UNLIKELY(p4 < 0 || p4 > UINT16_MAX || t4 < 0 || t4 > UINT16_MAX)) { + setLastError(UnsupportedIndexSizeError); + return; + } + + // ### Assumes convex quad, correct algorithm is to find the concave corner, + // and if there is one, do the split on the line between this and the corner it is + // not connected to. Also assumes order of vertices is counter clockwise. + d->indexes.append(qMakePair(ushort(p3), ushort(t3))); + d->indexes.append(qMakePair(ushort(p4), ushort(t4))); + d->indexes.append(qMakePair(ushort(p1), ushort(t1))); + } + } + } + } else { + setLastError(FileNotFoundError); + } + } else { + setLastError(InvalidSourceError); + } + + emit geometryChanged(); +} + +QString QWavefrontMesh::log() const +{ + Q_D(const QWavefrontMesh); + switch (d->lastError) { + case NoError: return QStringLiteral("No error"); + case InvalidSourceError: return QStringLiteral("Error: Invalid source"); + case UnsupportedFaceShapeError: return QStringLiteral("Error: Unsupported face shape in source"); + case UnsupportedIndexSizeError: return QStringLiteral("Error: Unsupported index size in source"); + case FileNotFoundError: return QStringLiteral("Error: File not found"); + case MissingPositionAttributeError: return QStringLiteral("Error: Missing '%1' attribute").arg(qtPositionAttributeName()); + case MissingTextureCoordinateAttributeError: return QStringLiteral("Error: Missing '%1' attribute").arg(qtTexCoordAttributeName()); + case MissingPositionAndTextureCoordinateAttributesError: return QStringLiteral("Error: Missing '%1' and '%2' attributes").arg(qtPositionAttributeName()).arg(qtTexCoordAttributeName()); + case TooManyAttributesError: return QStringLiteral("Error: Too many attributes"); + case InvalidPlaneDefinitionError: return QStringLiteral("Error: Invalid plane. V and W must be non-null and cannot be parallel"); + default: return QStringLiteral("Unknown error"); + }; +} + +bool QWavefrontMesh::validateAttributes(const QList &attributes, int *posIndex) +{ + Q_D(QWavefrontMesh); + const int attrCount = attributes.count(); + int positionIndex = attributes.indexOf(qtPositionAttributeName()); + int texCoordIndex = attributes.indexOf(qtTexCoordAttributeName()); + + switch (attrCount) { + case 0: + d->lastError = NoAttributesError; + return false; + case 1: + if (positionIndex < 0) { + d->lastError = MissingPositionAttributeError; + return false; + } + break; + case 2: + if (positionIndex < 0 || texCoordIndex < 0) { + if (positionIndex < 0 && texCoordIndex < 0) + d->lastError = MissingPositionAndTextureCoordinateAttributesError; + else if (positionIndex < 0) + d->lastError = MissingPositionAttributeError; + else if (texCoordIndex < 0) + d->lastError = MissingTextureCoordinateAttributeError; + return false; + } + break; + default: + d->lastError = TooManyAttributesError; + return false; + } + + if (posIndex) + *posIndex = positionIndex; + + return true; + +} + +QSGGeometry *QWavefrontMesh::updateGeometry(QSGGeometry *geometry, int attributeCount, int positionIndex, + const QRectF &sourceRect, const QRectF &destinationRect) +{ + Q_D(QWavefrontMesh); + + if (geometry == nullptr) { + Q_ASSERT(attributeCount == 1 || attributeCount == 2); + geometry = new QSGGeometry(attributeCount == 1 + ? QSGGeometry::defaultAttributes_Point2D() + : QSGGeometry::defaultAttributes_TexturedPoint2D(), + d->indexes.size(), + d->indexes.size(), + QSGGeometry::UnsignedShortType); + geometry->setDrawingMode(QSGGeometry::DrawTriangles); + + } else { + geometry->allocate(d->indexes.size(), d->indexes.size()); + } + + // If there is not at least a full triangle in the data set, skip out + if (d->indexes.size() < 3) { + geometry->allocate(0, 0); + return geometry; + } + + QVector3D planeV = d->planeV; + QVector3D planeW = d->planeW; + + // Automatically detect plane based on first face if none is set + if (planeV.isNull() || planeW.isNull()) { + QVector3D p = d->vertexes.at(d->indexes.at(0).first); + planeV = (d->vertexes.at(d->indexes.at(1).first) - p); + planeW = (p - d->vertexes.at(d->indexes.at(2).first)).normalized(); + } + + planeV.normalize(); + planeW.normalize(); + + QVector3D planeNormal = QVector3D::crossProduct(planeV, planeW).normalized(); + if (planeNormal.isNull()) { // V and W are either parallel or null + setLastError(InvalidPlaneDefinitionError); + geometry->allocate(0, 0); + return geometry; + } + + QVector3D planeAxes1 = planeV; + QVector3D planeAxes2 = QVector3D::crossProduct(planeAxes1, planeNormal).normalized(); + + ushort *indexData = static_cast(geometry->indexData()); + QSGGeometry::Point2D *vertexData = static_cast(geometry->vertexData()); + + float minX = 0.0f; + float maxX = 0.0f; + float minY = 0.0f; + float maxY = 0.0f; + for (ushort i = 0; i < ushort(d->indexes.size()); ++i) { + *(indexData + i) = i; + + QVector3D v = d->vertexes.at(d->indexes.at(i).first); + + // Project onto plane + QVector2D w; + v -= QVector3D::dotProduct(planeNormal, v) * planeNormal; + w.setX(QVector3D::dotProduct(v, planeAxes1)); + w.setY(QVector3D::dotProduct(v, planeAxes2)); + + QSGGeometry::Point2D *positionData = vertexData + (i * attributeCount + positionIndex); + positionData->x = w.x(); + positionData->y = w.y(); + + if (i == 0 || minX > w.x()) + minX = w.x(); + if (i == 0 || maxX < w.x()) + maxX = w.x(); + if (i == 0 || minY > w.y()) + minY = w.y(); + if (i == 0 || maxY < w.y()) + maxY = w.y(); + + if (attributeCount > 1 && !d->textureCoordinates.isEmpty()) { + Q_ASSERT(positionIndex == 0 || positionIndex == 1); + + QVector2D uv = d->textureCoordinates.at(d->indexes.at(i).second); + QSGGeometry::Point2D *textureCoordinateData = vertexData + (i * attributeCount + (1 - positionIndex)); + textureCoordinateData->x = uv.x(); + textureCoordinateData->y = uv.y(); + } + } + + float width = maxX - minX; + float height = maxY - minY; + + QVector2D center(minX + width / 2.0f, minY + height / 2.0f); + QVector2D scale(1.0f / width, 1.0f / height); + + for (int i = 0; i < geometry->vertexCount(); ++i) { + float x = ((vertexData + positionIndex)->x - center.x()) * scale.x(); + float y = ((vertexData + positionIndex)->y - center.y()) * scale.y(); + + for (int attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) { + if (attributeIndex == positionIndex) { + vertexData->x = float(destinationRect.left()) + x * float(destinationRect.width()) + float(destinationRect.width()) / 2.0f; + vertexData->y = float(destinationRect.top()) + y * float(destinationRect.height()) + float(destinationRect.height()) / 2.0f; + } else { + // If there are no texture coordinates, use the normalized vertex + float tx = d->textureCoordinates.isEmpty() ? x : vertexData->x; + float ty = d->textureCoordinates.isEmpty() ? y : vertexData->y; + + vertexData->x = float(sourceRect.left()) + tx * float(sourceRect.width()); + vertexData->y = float(sourceRect.top()) + ty * float(sourceRect.height()); + } + + ++vertexData; + } + } + + return geometry; +} + +/*! + \qmlproperty vector3d WavefrontMesh::projectionPlaneV + + Since the Wavefront .obj format describes an object in 3D space, the coordinates + have to be projected into 2D before they can be displayed in Qt Quick. + + This will be done in WavefrontMesh by an orthographic projection onto an + appropriate plane. + + The projectionPlaneV is one of two vectors in the plane in 3D space. If + either this, or \l projectionPlaneW is set to (0, 0, 0) (the default), + then the plane will be detected based on the first encountered face in the + data set. + + \note projectionPlaneV and \l projectionPlaneW cannot be parallel vectors. +*/ +void QWavefrontMesh::setProjectionPlaneV(const QVector3D &v) +{ + Q_D(QWavefrontMesh); + if (d->planeV == v) + return; + + d->planeV = v; + emit projectionPlaneVChanged(); +} + +QVector3D QWavefrontMesh::projectionPlaneV() const +{ + Q_D(const QWavefrontMesh); + return d->planeV; +} + +/*! + \qmlproperty vector3d WavefrontMesh::projectionPlaneW + + Since the Wavefront .obj format describes an object in 3D space, the coordinates + have to be projected into 2D before they can be displayed in Qt Quick. + + This will be done in WavefrontMesh by an orthographic projection onto an + appropriate plane. + + The projectionPlaneW is one of two vectors in the plane in 3D space. If + either this, or \l projectionPlaneV is set to (0, 0, 0) (the default), + then the plane will be detected based on the first encountered face in the + data set. + + \note \l projectionPlaneV and projectionPlaneW cannot be parallel vectors. +*/ +void QWavefrontMesh::setProjectionPlaneW(const QVector3D &w) +{ + Q_D(QWavefrontMesh); + if (d->planeW == w) + return; + + d->planeW = w; + emit projectionPlaneWChanged(); +} + +QVector3D QWavefrontMesh::projectionPlaneW() const +{ + Q_D(const QWavefrontMesh); + return d->planeW; +} + + +QT_END_NAMESPACE diff --git a/src/labs/wavefrontmesh/qwavefrontmesh.h b/src/labs/wavefrontmesh/qwavefrontmesh.h new file mode 100644 index 0000000000..b8246f22b1 --- /dev/null +++ b/src/labs/wavefrontmesh/qwavefrontmesh.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick 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$ +** +****************************************************************************/ + +#ifndef QWAVEFRONTMESH_H +#define QWAVEFRONTMESH_H + +#include "qqmlwavefrontmeshglobal_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWavefrontMeshPrivate; +class Q_LABSWAVEFRONTMESH_PRIVATE_EXPORT QWavefrontMesh : public QQuickShaderEffectMesh +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(Error lastError READ lastError NOTIFY lastErrorChanged) + Q_PROPERTY(QVector3D projectionPlaneV READ projectionPlaneV WRITE setProjectionPlaneV NOTIFY projectionPlaneVChanged) + Q_PROPERTY(QVector3D projectionPlaneW READ projectionPlaneW WRITE setProjectionPlaneW NOTIFY projectionPlaneWChanged) + QML_NAMED_ELEMENT(WavefrontMesh) + QML_ADDED_IN_VERSION(1, 0) + +public: + enum Error { + NoError, + InvalidSourceError, + UnsupportedFaceShapeError, + UnsupportedIndexSizeError, + FileNotFoundError, + NoAttributesError, + MissingPositionAttributeError, + MissingTextureCoordinateAttributeError, + MissingPositionAndTextureCoordinateAttributesError, + TooManyAttributesError, + InvalidPlaneDefinitionError + }; + Q_ENUMS(Error) + + QWavefrontMesh(QObject *parent = nullptr); + ~QWavefrontMesh() override; + + QUrl source() const; + void setSource(const QUrl &url); + + Error lastError() const; + void setLastError(Error lastError); + + bool validateAttributes(const QList &attributes, int *posIndex) override; + QSGGeometry *updateGeometry(QSGGeometry *geometry, int attrCount, int posIndex, + const QRectF &srcRect, const QRectF &rect) override; + QString log() const override; + + QVector3D projectionPlaneV() const; + void setProjectionPlaneV(const QVector3D &projectionPlaneV); + + QVector3D projectionPlaneW() const; + void setProjectionPlaneW(const QVector3D &projectionPlaneW); + +Q_SIGNALS: + void sourceChanged(); + void lastErrorChanged(); + void projectionPlaneVChanged(); + void projectionPlaneWChanged(); + +protected Q_SLOTS: + void readData(); + +private: + Q_DISABLE_COPY(QWavefrontMesh) + Q_DECLARE_PRIVATE(QWavefrontMesh) +}; + +QT_END_NAMESPACE + +#endif // QWAVEFRONTGEOMETRYMODEL_H diff --git a/sync.profile b/sync.profile index 09ca54c454..46c8a5a285 100644 --- a/sync.profile +++ b/sync.profile @@ -15,7 +15,8 @@ "QtQmlLocalStorage" => "$basedir/src/qmllocalstorage", "QtLabsSettings" => "$basedir/src/labs/settings", "QtLabsFolderListModel" => "$basedir/src/labs/folderlistmodel", - "QtLabsAnimation" => "$basedir/src/labs/animation" + "QtLabsAnimation" => "$basedir/src/labs/animation", + "QtLabsWavefrontMesh" => "$basedir/src/labs/wavefrontmesh" ); %inject_headers = ( "$basedir/src/qml" => [ "^qqmljsgrammar_p.h", "^qqmljsparser_p.h", "^qml_compile_hash_p.h" ], -- cgit v1.2.3