1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick 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 "qwavefrontmesh.h"
41
42#include <QtCore/qfile.h>
43#include <QtCore/qtextstream.h>
44#include <QtCore/private/qobject_p.h>
45
46#include <QtGui/qvector2d.h>
47#include <QtGui/qvector3d.h>
48
49#include <QtQml/qqmlfile.h>
50#include <QtQml/qqmlcontext.h>
51
52#include <QtQuick/qsggeometry.h>
53
54QT_BEGIN_NAMESPACE
55
56class QWavefrontMeshPrivate : public QObjectPrivate
57{
58public:
59 QWavefrontMeshPrivate()
60 : lastError(QWavefrontMesh::NoError)
61 {}
62
63 Q_DECLARE_PUBLIC(QWavefrontMesh)
64
65 static QWavefrontMeshPrivate *get(QWavefrontMesh *mesh)
66 {
67 return mesh->d_func();
68 }
69
70 static const QWavefrontMeshPrivate *get(const QWavefrontMesh *mesh)
71 {
72 return mesh->d_func();
73 }
74
75 QVector<QPair<ushort, ushort> > indexes;
76 QVector<QVector3D> vertexes;
77 QVector<QVector2D> textureCoordinates;
78
79 QUrl source;
80 QWavefrontMesh::Error lastError;
81
82 QVector3D planeV;
83 QVector3D planeW;
84};
85
86/*!
87 \qmlmodule Qt.labs.wavefrontmesh 1.\QtMinorVersion
88 \title Qt Labs WavefrontMesh QML Types
89 \ingroup qmlmodules
90 \brief The WavefrontMesh provides a mesh based on a Wavefront .obj file.
91
92 To use this module, import the module with the following line:
93
94 \qml \QtMinorVersion
95 import Qt.labs.wavefrontmesh 1.\1
96 \endqml
97*/
98
99/*!
100 \qmltype WavefrontMesh
101 \inqmlmodule Qt.labs.wavefrontmesh
102 \instantiates QWavefrontMesh
103 \ingroup qtquick-effects
104 \brief The WavefrontMesh provides a mesh based on a Wavefront .obj file.
105 \since 5.12
106
107 WavefrontMesh reads the geometry from a Wavefront .obj file and generates
108 a two-dimensional \l{QSGGeometry}{geometry} from this. If the .obj file
109 contains a three-dimensional shape, it will be orthographically projected,
110 onto a plane. If defined, this is given by \l projectionPlaneV
111 and \l projectionPlaneW. Otherwise, the first face encountered in the data
112 will be used to determine the projection plane.
113
114 If the file contains texture coordinates, these will also be used. Otherwise,
115 the vertexes of the object will be normalized and used.
116
117 The mesh can be used in a ShaderEffect to define the shaded geometry. The
118 geometry will be normalized before use, so the position and scale of the
119 input objects have no impact on the result.
120
121 \note Some Wavefront exporters will change the source scene's coordinate system
122 before exporting it. This can cause unexpected results when Qt applies the
123 projection. If the visual results are not as you expect, try checking the export
124 parameters and the documentation of the editor tool to see if this is the case.
125
126 For instance, the following example takes an .obj file containing a standard torus
127 and visualizes the automatically generated texture coordinates.
128
129 \table
130 \row
131 \li \image qtlabs-wavefrontmesh.png
132 \li \qml
133 import QtQuick 2.\1
134 import Qt.labs.wavefrontmesh 1.\1
135
136 ShaderEffect {
137 width: 200
138 height: 200
139 mesh: WavefrontMesh {
140 source: "torus.obj"
141 projectionPlaneV: Qt.vector3d(0, 1, 0)
142 projectionPlaneW: Qt.vector3d(1, 0, 0)
143 }
144 vertexShader: "
145 uniform highp mat4 qt_Matrix;
146 attribute highp vec4 qt_Vertex;
147 attribute highp vec2 qt_MultiTexCoord0;
148 varying highp vec2 coord;
149 void main() {
150 coord = qt_MultiTexCoord0;
151 gl_Position = qt_Matrix * qt_Vertex;
152 }"
153 fragmentShader: "
154 varying highp vec2 coord;
155 uniform lowp float qt_Opacity;
156 void main() {
157 gl_FragColor = vec4(coord.x, coord.y, 0.0, 1.0);
158 }"
159
160 }
161 \endqml
162 \endtable
163
164 \note Since the input is a 3D torus, we need to define the projection plane. This would not be necessary when
165 using a 2D shape as input. We use the XY plane in this case, because of the orientation of the input.
166*/
167
168QWavefrontMesh::QWavefrontMesh(QObject *parent)
169 : QQuickShaderEffectMesh(*(new QWavefrontMeshPrivate), parent)
170{
171 connect(sender: this, signal: &QWavefrontMesh::sourceChanged, receiver: this, slot: &QWavefrontMesh::readData);
172 connect(sender: this, signal: &QWavefrontMesh::projectionPlaneVChanged, receiver: this, slot: &QQuickShaderEffectMesh::geometryChanged);
173 connect(sender: this, signal: &QWavefrontMesh::projectionPlaneWChanged, receiver: this, slot: &QQuickShaderEffectMesh::geometryChanged);
174}
175
176QWavefrontMesh::~QWavefrontMesh()
177{
178}
179
180/*!
181 \qmlproperty enumeration WavefrontMesh::lastError
182
183 This property holds the last error, if any, that occurred when parsing the
184 source or building the mesh.
185
186 \list
187 \li WavefrontMesh.NoError No error has occurred.
188 \li WavefrontMesh.InvalidSourceError The source was not recognized as a valid .obj file.
189 \li WavefrontMesh.UnsupportedFaceShapeError The faces in the source is of an unsupported type.
190 WavefrontMesh only supports triangles and convex quads.
191 \li WavefrontMesh.UnsupportedIndexSizeError The source shape is too large. Only 16 bit indexes are supported.
192 \li WavefrontMesh.FileNotFoundError The source file was not found.
193 \li WavefrontMesh.MissingPositionAttributeError The 'qt_Vertex' attribute is missing from the shaders.
194 \li WavefrontMesh.MissingTextureCoordinateAttributeError The texture coordinate attribute in the shaders is wrongly named. Use 'qt_MultiTexCoord0'.
195 \li WavefrontMesh.MissingPositionAndTextureCoordinateAttributesError Both the 'qt_Vertex' and 'qt_MultiTexCoord0' attributes are missing from the shaders.
196 \li WavefrontMesh.TooManyAttributesError The shaders expect too many attributes (maximum is two: Position, 'qt_Vertex', and texture coordinate, 'qt_MultiTexCoord0').
197 \li WavefrontMesh.InvalidPlaneDefinitionError The V and W vectors in the plane cannot be null, nor parallel to each other.
198 \endlist
199*/
200
201QWavefrontMesh::Error QWavefrontMesh::lastError() const
202{
203 Q_D(const QWavefrontMesh);
204 return d->lastError;
205}
206
207void QWavefrontMesh::setLastError(Error lastError)
208{
209 Q_D(QWavefrontMesh);
210 if (d->lastError == lastError)
211 return;
212
213 d->lastError = lastError;
214 emit lastErrorChanged();
215}
216
217/*!
218 \qmlproperty url WavefrontMesh::source
219
220 This property holds the URL of the source. This must be either a local file or in qrc. The source will
221 be read as a Wavefront .obj file and the geometry will be updated.
222*/
223QUrl QWavefrontMesh::source() const
224{
225 Q_D(const QWavefrontMesh);
226 return d->source;
227}
228
229void QWavefrontMesh::setSource(const QUrl &source)
230{
231 Q_D(QWavefrontMesh);
232 if (d->source == source)
233 return;
234
235 d->source = source;
236 emit sourceChanged();
237}
238
239void QWavefrontMesh::readData()
240{
241 Q_D(QWavefrontMesh);
242 d->vertexes.clear();
243 d->textureCoordinates.clear();
244 d->indexes.clear();
245
246 QString localFile = QQmlFile::urlToLocalFileOrQrc(d->source);
247 if (!localFile.isEmpty()) {
248 QFile file(localFile);
249 if (file.open(flags: QIODevice::ReadOnly)) {
250 QTextStream stream(&file);
251
252 QString buffer;
253 buffer.reserve(asize: 256);
254
255 static QChar space(QLatin1Char(' '));
256 static QChar slash(QLatin1Char('/'));
257
258 while (!stream.atEnd()) {
259 stream.readLineInto(line: &buffer);
260 QVector<QStringRef> tokens = buffer.splitRef(sep: space, behavior: Qt::SkipEmptyParts);
261 if (tokens.size() < 2)
262 continue;
263
264 QByteArray command = tokens.at(i: 0).toLatin1();
265
266 if (command == "vt") {
267 bool ok;
268 float u = tokens.at(i: 1).toFloat(ok: &ok);
269 if (!ok) {
270 setLastError(InvalidSourceError);
271 return;
272 }
273
274 float v = tokens.size() > 2 ? tokens.at(i: 2).toFloat(ok: &ok) : 0.0;
275 if (!ok) {
276 setLastError(InvalidSourceError);
277 return;
278 }
279
280 d->textureCoordinates.append(t: QVector2D(u, v));
281 } else if (command == "v") {
282 // Format: v <x> <y> <z> [w]
283 if (tokens.length() < 4 || tokens.length() > 5) {
284 setLastError(InvalidSourceError);
285 return;
286 }
287
288 bool ok;
289
290 float x = tokens.at(i: 1).toFloat(ok: &ok);
291 if (!ok) {
292 setLastError(InvalidSourceError);
293 return;
294 }
295
296 float y = tokens.at(i: 2).toFloat(ok: &ok);
297 if (!ok) {
298 setLastError(InvalidSourceError);
299 return;
300 }
301
302 float z = tokens.at(i: 3).toFloat(ok: &ok);
303 if (!ok) {
304 setLastError(InvalidSourceError);
305 return;
306 }
307
308 d->vertexes.append(t: QVector3D(x, y, z));
309 } else if (command == "f") {
310 // The scenegraph only supports triangles, so we
311 // support triangles and quads (which we split up)
312 int p1, p2, p3;
313 int t1 = 0;
314 int t2 = 0;
315 int t3 = 0;
316 if (tokens.size() >= 4 && tokens.size() <= 5) {
317 {
318 bool ok;
319 QVector<QStringRef> faceTokens = tokens.at(i: 1).split(sep: slash, behavior: Qt::SkipEmptyParts);
320 Q_ASSERT(!faceTokens.isEmpty());
321
322 p1 = faceTokens.at(i: 0).toInt(ok: &ok) - 1;
323 if (!ok) {
324 setLastError(InvalidSourceError);
325 return;
326 }
327
328 if (faceTokens.size() > 1) {
329 t1 = faceTokens.at(i: 1).toInt(ok: &ok) - 1;
330 if (!ok) {
331 setLastError(InvalidSourceError);
332 return;
333 }
334 }
335 }
336
337 {
338 bool ok;
339 QVector<QStringRef> faceTokens = tokens.at(i: 2).split(sep: slash, behavior: Qt::SkipEmptyParts);
340 Q_ASSERT(!faceTokens.isEmpty());
341
342 p2 = faceTokens.at(i: 0).toInt(ok: &ok) - 1;
343 if (!ok) {
344 setLastError(InvalidSourceError);
345 return;
346 }
347
348 if (faceTokens.size() > 1) {
349 t2 = faceTokens.at(i: 1).toInt(ok: &ok) - 1;
350 if (!ok) {
351 setLastError(InvalidSourceError);
352 return;
353 }
354 }
355 }
356
357 {
358 bool ok;
359 QVector<QStringRef> faceTokens = tokens.at(i: 3).split(sep: slash, behavior: Qt::SkipEmptyParts);
360 Q_ASSERT(!faceTokens.isEmpty());
361
362 p3 = faceTokens.at(i: 0).toInt(ok: &ok) - 1;
363 if (!ok) {
364 setLastError(InvalidSourceError);
365 return;
366 }
367
368 if (faceTokens.size() > 1) {
369 t3 = faceTokens.at(i: 1).toInt(ok: &ok) - 1;
370 if (!ok) {
371 setLastError(InvalidSourceError);
372 return;
373 }
374 }
375 }
376
377 if (Q_UNLIKELY(p1 < 0 || p1 > UINT16_MAX
378 || p2 < 0 || p2 > UINT16_MAX
379 || p3 < 0 || p3 > UINT16_MAX
380 || t1 < 0 || t1 > UINT16_MAX
381 || t2 < 0 || t2 > UINT16_MAX
382 || t3 < 0 || t3 > UINT16_MAX)) {
383 setLastError(UnsupportedIndexSizeError);
384 return;
385 }
386
387 d->indexes.append(t: qMakePair(x: ushort(p1), y: ushort(t1)));
388 d->indexes.append(t: qMakePair(x: ushort(p2), y: ushort(t2)));
389 d->indexes.append(t: qMakePair(x: ushort(p3), y: ushort(t3)));
390 } else {
391 setLastError(UnsupportedFaceShapeError);
392 return;
393 }
394
395 if (tokens.size() == 5) {
396 bool ok;
397 QVector<QStringRef> faceTokens = tokens.at(i: 4).split(sep: slash, behavior: Qt::SkipEmptyParts);
398 Q_ASSERT(!faceTokens.isEmpty());
399
400 int p4 = faceTokens.at(i: 0).toInt(ok: &ok) - 1;
401 if (!ok) {
402 setLastError(InvalidSourceError);
403 return;
404 }
405
406 int t4 = 0;
407 if (faceTokens.size() > 1) {
408 t4 = faceTokens.at(i: 1).toInt(ok: &ok) - 1;
409 if (!ok) {
410 setLastError(InvalidSourceError);
411 return;
412 }
413 }
414
415 if (Q_UNLIKELY(p4 < 0 || p4 > UINT16_MAX || t4 < 0 || t4 > UINT16_MAX)) {
416 setLastError(UnsupportedIndexSizeError);
417 return;
418 }
419
420 // ### Assumes convex quad, correct algorithm is to find the concave corner,
421 // and if there is one, do the split on the line between this and the corner it is
422 // not connected to. Also assumes order of vertices is counter clockwise.
423 d->indexes.append(t: qMakePair(x: ushort(p3), y: ushort(t3)));
424 d->indexes.append(t: qMakePair(x: ushort(p4), y: ushort(t4)));
425 d->indexes.append(t: qMakePair(x: ushort(p1), y: ushort(t1)));
426 }
427 }
428 }
429 } else {
430 setLastError(FileNotFoundError);
431 }
432 } else {
433 setLastError(InvalidSourceError);
434 }
435
436 emit geometryChanged();
437}
438
439QString QWavefrontMesh::log() const
440{
441 Q_D(const QWavefrontMesh);
442 switch (d->lastError) {
443 case NoError: return QStringLiteral("No error");
444 case InvalidSourceError: return QStringLiteral("Error: Invalid source");
445 case UnsupportedFaceShapeError: return QStringLiteral("Error: Unsupported face shape in source");
446 case UnsupportedIndexSizeError: return QStringLiteral("Error: Unsupported index size in source");
447 case FileNotFoundError: return QStringLiteral("Error: File not found");
448 case MissingPositionAttributeError: return QStringLiteral("Error: Missing '%1' attribute").arg(a: qtPositionAttributeName());
449 case MissingTextureCoordinateAttributeError: return QStringLiteral("Error: Missing '%1' attribute").arg(a: qtTexCoordAttributeName());
450 case MissingPositionAndTextureCoordinateAttributesError: return QStringLiteral("Error: Missing '%1' and '%2' attributes").arg(a: qtPositionAttributeName()).arg(a: qtTexCoordAttributeName());
451 case TooManyAttributesError: return QStringLiteral("Error: Too many attributes");
452 case InvalidPlaneDefinitionError: return QStringLiteral("Error: Invalid plane. V and W must be non-null and cannot be parallel");
453 default: return QStringLiteral("Unknown error");
454 };
455}
456
457bool QWavefrontMesh::validateAttributes(const QVector<QByteArray> &attributes, int *posIndex)
458{
459 Q_D(QWavefrontMesh);
460 const int attrCount = attributes.count();
461 int positionIndex = attributes.indexOf(t: qtPositionAttributeName());
462 int texCoordIndex = attributes.indexOf(t: qtTexCoordAttributeName());
463
464 switch (attrCount) {
465 case 0:
466 d->lastError = NoAttributesError;
467 return false;
468 case 1:
469 if (positionIndex < 0) {
470 d->lastError = MissingPositionAttributeError;
471 return false;
472 }
473 break;
474 case 2:
475 if (positionIndex < 0 || texCoordIndex < 0) {
476 if (positionIndex < 0 && texCoordIndex < 0)
477 d->lastError = MissingPositionAndTextureCoordinateAttributesError;
478 else if (positionIndex < 0)
479 d->lastError = MissingPositionAttributeError;
480 else if (texCoordIndex < 0)
481 d->lastError = MissingTextureCoordinateAttributeError;
482 return false;
483 }
484 break;
485 default:
486 d->lastError = TooManyAttributesError;
487 return false;
488 }
489
490 if (posIndex)
491 *posIndex = positionIndex;
492
493 return true;
494
495}
496
497QSGGeometry *QWavefrontMesh::updateGeometry(QSGGeometry *geometry, int attributeCount, int positionIndex,
498 const QRectF &sourceRect, const QRectF &destinationRect)
499{
500 Q_D(QWavefrontMesh);
501
502 if (geometry == nullptr) {
503 Q_ASSERT(attributeCount == 1 || attributeCount == 2);
504 geometry = new QSGGeometry(attributeCount == 1
505 ? QSGGeometry::defaultAttributes_Point2D()
506 : QSGGeometry::defaultAttributes_TexturedPoint2D(),
507 d->indexes.size(),
508 d->indexes.size(),
509 QSGGeometry::UnsignedShortType);
510 geometry->setDrawingMode(QSGGeometry::DrawTriangles);
511
512 } else {
513 geometry->allocate(vertexCount: d->indexes.size(), indexCount: d->indexes.size());
514 }
515
516 // If there is not at least a full triangle in the data set, skip out
517 if (d->indexes.size() < 3) {
518 geometry->allocate(vertexCount: 0, indexCount: 0);
519 return geometry;
520 }
521
522 QVector3D planeV = d->planeV;
523 QVector3D planeW = d->planeW;
524
525 // Automatically detect plane based on first face if none is set
526 if (planeV.isNull() || planeW.isNull()) {
527 QVector3D p = d->vertexes.at(i: d->indexes.at(i: 0).first);
528 planeV = (d->vertexes.at(i: d->indexes.at(i: 1).first) - p);
529 planeW = (p - d->vertexes.at(i: d->indexes.at(i: 2).first)).normalized();
530 }
531
532 planeV.normalize();
533 planeW.normalize();
534
535 QVector3D planeNormal = QVector3D::crossProduct(v1: planeV, v2: planeW).normalized();
536 if (planeNormal.isNull()) { // V and W are either parallel or null
537 setLastError(InvalidPlaneDefinitionError);
538 geometry->allocate(vertexCount: 0, indexCount: 0);
539 return geometry;
540 }
541
542 QVector3D planeAxes1 = planeV;
543 QVector3D planeAxes2 = QVector3D::crossProduct(v1: planeAxes1, v2: planeNormal).normalized();
544
545 ushort *indexData = static_cast<ushort *>(geometry->indexData());
546 QSGGeometry::Point2D *vertexData = static_cast<QSGGeometry::Point2D *>(geometry->vertexData());
547
548 float minX = 0.0f;
549 float maxX = 0.0f;
550 float minY = 0.0f;
551 float maxY = 0.0f;
552 for (ushort i = 0; i < ushort(d->indexes.size()); ++i) {
553 *(indexData + i) = i;
554
555 QVector3D v = d->vertexes.at(i: d->indexes.at(i).first);
556
557 // Project onto plane
558 QVector2D w;
559 v -= QVector3D::dotProduct(v1: planeNormal, v2: v) * planeNormal;
560 w.setX(QVector3D::dotProduct(v1: v, v2: planeAxes1));
561 w.setY(QVector3D::dotProduct(v1: v, v2: planeAxes2));
562
563 QSGGeometry::Point2D *positionData = vertexData + (i * attributeCount + positionIndex);
564 positionData->x = w.x();
565 positionData->y = w.y();
566
567 if (i == 0 || minX > w.x())
568 minX = w.x();
569 if (i == 0 || maxX < w.x())
570 maxX = w.x();
571 if (i == 0 || minY > w.y())
572 minY = w.y();
573 if (i == 0 || maxY < w.y())
574 maxY = w.y();
575
576 if (attributeCount > 1 && !d->textureCoordinates.isEmpty()) {
577 Q_ASSERT(positionIndex == 0 || positionIndex == 1);
578
579 QVector2D uv = d->textureCoordinates.at(i: d->indexes.at(i).second);
580 QSGGeometry::Point2D *textureCoordinateData = vertexData + (i * attributeCount + (1 - positionIndex));
581 textureCoordinateData->x = uv.x();
582 textureCoordinateData->y = uv.y();
583 }
584 }
585
586 float width = maxX - minX;
587 float height = maxY - minY;
588
589 QVector2D center(minX + width / 2.0f, minY + height / 2.0f);
590 QVector2D scale(1.0f / width, 1.0f / height);
591
592 for (int i = 0; i < geometry->vertexCount(); ++i) {
593 float x = ((vertexData + positionIndex)->x - center.x()) * scale.x();
594 float y = ((vertexData + positionIndex)->y - center.y()) * scale.y();
595
596 for (int attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) {
597 if (attributeIndex == positionIndex) {
598 vertexData->x = float(destinationRect.left()) + x * float(destinationRect.width()) + float(destinationRect.width()) / 2.0f;
599 vertexData->y = float(destinationRect.top()) + y * float(destinationRect.height()) + float(destinationRect.height()) / 2.0f;
600 } else {
601 // If there are no texture coordinates, use the normalized vertex
602 float tx = d->textureCoordinates.isEmpty() ? x : vertexData->x;
603 float ty = d->textureCoordinates.isEmpty() ? y : vertexData->y;
604
605 vertexData->x = float(sourceRect.left()) + tx * float(sourceRect.width());
606 vertexData->y = float(sourceRect.top()) + ty * float(sourceRect.height());
607 }
608
609 ++vertexData;
610 }
611 }
612
613 return geometry;
614}
615
616/*!
617 \qmlproperty vector3d WavefrontMesh::projectionPlaneV
618
619 Since the Wavefront .obj format describes an object in 3D space, the coordinates
620 have to be projected into 2D before they can be displayed in Qt Quick.
621
622 This will be done in WavefrontMesh by an orthographic projection onto an
623 appropriate plane.
624
625 The projectionPlaneV is one of two vectors in the plane in 3D space. If
626 either this, or \l projectionPlaneW is set to (0, 0, 0) (the default),
627 then the plane will be detected based on the first encountered face in the
628 data set.
629
630 \note projectionPlaneV and \l projectionPlaneW cannot be parallel vectors.
631*/
632void QWavefrontMesh::setProjectionPlaneV(const QVector3D &v)
633{
634 Q_D(QWavefrontMesh);
635 if (d->planeV == v)
636 return;
637
638 d->planeV = v;
639 emit projectionPlaneVChanged();
640}
641
642QVector3D QWavefrontMesh::projectionPlaneV() const
643{
644 Q_D(const QWavefrontMesh);
645 return d->planeV;
646}
647
648/*!
649 \qmlproperty vector3d WavefrontMesh::projectionPlaneW
650
651 Since the Wavefront .obj format describes an object in 3D space, the coordinates
652 have to be projected into 2D before they can be displayed in Qt Quick.
653
654 This will be done in WavefrontMesh by an orthographic projection onto an
655 appropriate plane.
656
657 The projectionPlaneW is one of two vectors in the plane in 3D space. If
658 either this, or \l projectionPlaneV is set to (0, 0, 0) (the default),
659 then the plane will be detected based on the first encountered face in the
660 data set.
661
662 \note \l projectionPlaneV and projectionPlaneW cannot be parallel vectors.
663*/
664void QWavefrontMesh::setProjectionPlaneW(const QVector3D &w)
665{
666 Q_D(QWavefrontMesh);
667 if (d->planeW == w)
668 return;
669
670 d->planeW = w;
671 emit projectionPlaneWChanged();
672}
673
674QVector3D QWavefrontMesh::projectionPlaneW() const
675{
676 Q_D(const QWavefrontMesh);
677 return d->planeW;
678}
679
680
681QT_END_NAMESPACE
682

source code of qtdeclarative/src/imports/wavefrontmesh/qwavefrontmesh.cpp