aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2018-04-22 18:25:24 +0200
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2018-10-25 05:07:45 +0000
commit4207940684b578469f7ec6b428403af85e579b2c (patch)
tree7e9abf2dcc70faf5ce3694f56294d1f7e3e258da
parent6734ee9a6ddb6a3870556514a70d904836258559 (diff)
Add WavefrontMesh
Add a mesh type for use with ShaderEffect which loads the geometry from an .obj file. Change-Id: I53c9149fc56cdab4a36fccc087abf54a6b50f42f Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r--src/imports/imports.pro3
-rw-r--r--src/imports/wavefrontmesh/plugin.cpp59
-rw-r--r--src/imports/wavefrontmesh/plugins.qmltypes37
-rw-r--r--src/imports/wavefrontmesh/qmldir4
-rw-r--r--src/imports/wavefrontmesh/qwavefrontmesh.cpp684
-rw-r--r--src/imports/wavefrontmesh/qwavefrontmesh.h100
-rw-r--r--src/imports/wavefrontmesh/wavefrontmesh.pro15
-rw-r--r--src/quick/doc/images/qtlabs-wavefrontmesh.pngbin0 -> 2881 bytes
8 files changed, 901 insertions, 1 deletions
diff --git a/src/imports/imports.pro b/src/imports/imports.pro
index eb3e336c7f..ca613dac66 100644
--- a/src/imports/imports.pro
+++ b/src/imports/imports.pro
@@ -4,7 +4,8 @@ SUBDIRS += \
builtins \
qtqml \
models \
- labsmodels
+ labsmodels \
+ wavefrontmesh
qtConfig(thread): SUBDIRS += folderlistmodel
qtHaveModule(sql): SUBDIRS += localstorage
diff --git a/src/imports/wavefrontmesh/plugin.cpp b/src/imports/wavefrontmesh/plugin.cpp
new file mode 100644
index 0000000000..c974db1161
--- /dev/null
+++ b/src/imports/wavefrontmesh/plugin.cpp
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** 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:GPL$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtQml/qqmlextensionplugin.h>
+#include <QtQml/qqml.h>
+
+#include "qwavefrontmesh.h"
+
+QT_BEGIN_NAMESPACE
+
+class QmlWavefrontMeshPlugin : public QQmlExtensionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid)
+public:
+ QmlWavefrontMeshPlugin(QObject *parent = nullptr)
+ : QQmlExtensionPlugin(parent)
+ {
+ }
+
+ void registerTypes(const char *uri) override
+ {
+ Q_ASSERT(QLatin1String(uri) == QLatin1String("Qt.labs.wavefrontmesh"));
+ qmlRegisterType<QWavefrontMesh>(uri, 1, 0, "WavefrontMesh");
+
+ // Auto-increment the import to stay in sync with ALL future QtQuick minor versions from 5.12 onward
+ qmlRegisterModule(uri, 1, QT_VERSION_MINOR);
+ }
+};
+
+QT_END_NAMESPACE
+
+#include "plugin.moc"
diff --git a/src/imports/wavefrontmesh/plugins.qmltypes b/src/imports/wavefrontmesh/plugins.qmltypes
new file mode 100644
index 0000000000..a0c4e22271
--- /dev/null
+++ b/src/imports/wavefrontmesh/plugins.qmltypes
@@ -0,0 +1,37 @@
+import QtQuick.tooling 1.2
+
+// This file describes the plugin-supplied types contained in the library.
+// It is used for QML tooling purposes only.
+//
+// This file was auto-generated by:
+// 'qmlplugindump -nonrelocatable Qt.labs.wavefrontmesh 1.12'
+
+Module {
+ dependencies: ["QtQuick 2.12"]
+ Component {
+ name: "QWavefrontMesh"
+ prototype: "QQuickShaderEffectMesh"
+ exports: ["Qt.labs.wavefrontmesh/WavefrontMesh 1.0"]
+ exportMetaObjectRevisions: [0]
+ Enum {
+ name: "Error"
+ values: {
+ "NoError": 0,
+ "InvalidSourceError": 1,
+ "UnsupportedFaceShapeError": 2,
+ "UnsupportedIndexSizeError": 3,
+ "FileNotFoundError": 4,
+ "NoAttributesError": 5,
+ "MissingPositionAttributeError": 6,
+ "MissingTextureCoordinateAttributeError": 7,
+ "MissingPositionAndTextureCoordinateAttributesError": 8,
+ "TooManyAttributesError": 9,
+ "InvalidPlaneDefinitionError": 10
+ }
+ }
+ Property { name: "source"; type: "QUrl" }
+ Property { name: "lastError"; type: "Error"; isReadonly: true }
+ Property { name: "projectionPlaneV"; type: "QVector3D" }
+ Property { name: "projectionPlaneW"; type: "QVector3D" }
+ }
+}
diff --git a/src/imports/wavefrontmesh/qmldir b/src/imports/wavefrontmesh/qmldir
new file mode 100644
index 0000000000..fed15dd06f
--- /dev/null
+++ b/src/imports/wavefrontmesh/qmldir
@@ -0,0 +1,4 @@
+module Qt.labs.wavefrontmesh
+plugin qmlwavefrontmeshplugin
+classname QmlWavefrontMeshPlugin
+typeinfo plugins.qmltypes
diff --git a/src/imports/wavefrontmesh/qwavefrontmesh.cpp b/src/imports/wavefrontmesh/qwavefrontmesh.cpp
new file mode 100644
index 0000000000..3cf1211aae
--- /dev/null
+++ b/src/imports/wavefrontmesh/qwavefrontmesh.cpp
@@ -0,0 +1,684 @@
+/****************************************************************************
+**
+** 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:GPL$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qwavefrontmesh.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qtextstream.h>
+#include <QtCore/private/qobject_p.h>
+
+#include <QtGui/qvector2d.h>
+#include <QtGui/qvector3d.h>
+
+#include <QtQml/qqmlfile.h>
+#include <QtQml/qqmlcontext.h>
+
+#include <QtQuick/qsggeometry.h>
+
+QT_BEGIN_NAMESPACE
+
+static const char qt_position_attribute_name[] = "qt_Vertex";
+static const char qt_texcoord_attribute_name[] = "qt_MultiTexCoord0";
+
+const char *qtPositionAttributeName()
+{
+ return qt_position_attribute_name;
+}
+
+const char *qtTexCoordAttributeName()
+{
+ return qt_texcoord_attribute_name;
+}
+
+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<QPair<ushort, ushort> > indexes;
+ QVector<QVector3D> vertexes;
+ QVector<QVector2D> 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 \QtMinorVersion
+ import Qt.labs.wavefrontmesh 1.\1
+ \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);
+ QVector<QStringRef> tokens = buffer.splitRef(space, QString::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 <x> <y> <z> [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;
+ QVector<QStringRef> faceTokens = tokens.at(1).split(slash, QString::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;
+ QVector<QStringRef> faceTokens = tokens.at(2).split(slash, QString::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;
+ QVector<QStringRef> faceTokens = tokens.at(3).split(slash, QString::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;
+ QVector<QStringRef> faceTokens = tokens.at(4).split(slash, QString::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 QVector<QByteArray> &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<ushort *>(geometry->indexData());
+ QSGGeometry::Point2D *vertexData = static_cast<QSGGeometry::Point2D *>(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/imports/wavefrontmesh/qwavefrontmesh.h b/src/imports/wavefrontmesh/qwavefrontmesh.h
new file mode 100644
index 0000000000..a13dc780fa
--- /dev/null
+++ b/src/imports/wavefrontmesh/qwavefrontmesh.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** 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:GPL$
+** 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 General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) 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.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-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QWAVEFRONTMESH_H
+#define QWAVEFRONTMESH_H
+
+#include <QtQuick/private/qquickshadereffectmesh_p.h>
+
+#include <QtCore/qurl.h>
+#include <QtGui/qvector3d.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWavefrontMeshPrivate;
+class 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)
+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 QVector<QByteArray> &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/src/imports/wavefrontmesh/wavefrontmesh.pro b/src/imports/wavefrontmesh/wavefrontmesh.pro
new file mode 100644
index 0000000000..85bd500089
--- /dev/null
+++ b/src/imports/wavefrontmesh/wavefrontmesh.pro
@@ -0,0 +1,15 @@
+CXX_MODULE = qml
+TARGET = qmlwavefrontmeshplugin
+TARGETPATH = Qt/labs/wavefrontmesh
+IMPORT_VERSION = 1.$$QT_MINOR_VERSION
+
+QT = core-private qml-private quick-private
+
+SOURCES += \
+ plugin.cpp \
+ qwavefrontmesh.cpp
+
+HEADERS += \
+ qwavefrontmesh.h
+
+load(qml_plugin)
diff --git a/src/quick/doc/images/qtlabs-wavefrontmesh.png b/src/quick/doc/images/qtlabs-wavefrontmesh.png
new file mode 100644
index 0000000000..e8ec3642fa
--- /dev/null
+++ b/src/quick/doc/images/qtlabs-wavefrontmesh.png
Binary files differ