1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd and/or its subsidiary(-ies).
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 "plygeometryloader.h"
41
42#include <QtCore/QDataStream>
43#include <QtCore/QLoggingCategory>
44
45QT_BEGIN_NAMESPACE
46
47namespace Qt3DRender {
48
49Q_LOGGING_CATEGORY(PlyGeometryLoaderLog, "Qt3D.PlyGeometryLoader", QtWarningMsg)
50
51namespace {
52
53class PlyDataReader
54{
55public:
56 virtual ~PlyDataReader() {}
57
58 virtual int readIntValue(PlyGeometryLoader::DataType type) = 0;
59 virtual float readFloatValue(PlyGeometryLoader::DataType type) = 0;
60};
61
62class AsciiPlyDataReader : public PlyDataReader
63{
64public:
65 AsciiPlyDataReader(QIODevice *ioDev)
66 : m_stream(ioDev)
67 { }
68
69 int readIntValue(PlyGeometryLoader::DataType) override
70 {
71 int value;
72 m_stream >> value;
73 return value;
74 }
75
76 float readFloatValue(PlyGeometryLoader::DataType) override
77 {
78 float value;
79 m_stream >> value;
80 return value;
81 }
82
83private:
84 QTextStream m_stream;
85};
86
87class BinaryPlyDataReader : public PlyDataReader
88{
89public:
90 BinaryPlyDataReader(QIODevice *ioDev, QDataStream::ByteOrder byteOrder)
91 : m_stream(ioDev)
92 {
93 ioDev->setTextModeEnabled(false);
94 m_stream.setByteOrder(byteOrder);
95 }
96
97 int readIntValue(PlyGeometryLoader::DataType type) override
98 {
99 return readValue<int>(type);
100 }
101
102 float readFloatValue(PlyGeometryLoader::DataType type) override
103 {
104 return readValue<float>(type);
105 }
106
107private:
108 template <typename T>
109 T readValue(PlyGeometryLoader::DataType type)
110 {
111 switch (type) {
112 case PlyGeometryLoader::Int8:
113 {
114 qint8 value;
115 m_stream >> value;
116 return value;
117 }
118
119 case PlyGeometryLoader::Uint8:
120 {
121 quint8 value;
122 m_stream >> value;
123 return value;
124 }
125
126 case PlyGeometryLoader::Int16:
127 {
128 qint16 value;
129 m_stream >> value;
130 return value;
131 }
132
133 case PlyGeometryLoader::Uint16:
134 {
135 quint16 value;
136 m_stream >> value;
137 return value;
138 }
139
140 case PlyGeometryLoader::Int32:
141 {
142 qint32 value;
143 m_stream >> value;
144 return value;
145 }
146
147 case PlyGeometryLoader::Uint32:
148 {
149 quint32 value;
150 m_stream >> value;
151 return value;
152 }
153
154 case PlyGeometryLoader::Float32:
155 {
156 m_stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
157 float value;
158 m_stream >> value;
159 return value;
160 }
161
162 case PlyGeometryLoader::Float64:
163 {
164 m_stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
165 double value;
166 m_stream >> value;
167 return value;
168 }
169
170 default:
171 break;
172 }
173
174 return 0;
175 }
176
177 QDataStream m_stream;
178};
179
180}
181
182static PlyGeometryLoader::DataType toPlyDataType(const QString &typeName)
183{
184 if (typeName == QStringLiteral("int8") || typeName == QStringLiteral("char")) {
185 return PlyGeometryLoader::Int8;
186 } else if (typeName == QStringLiteral("uint8") || typeName == QStringLiteral("uchar")) {
187 return PlyGeometryLoader::Uint8;
188 } else if (typeName == QStringLiteral("int16") || typeName == QStringLiteral("short")) {
189 return PlyGeometryLoader::Int16;
190 } else if (typeName == QStringLiteral("uint16") || typeName == QStringLiteral("ushort")) {
191 return PlyGeometryLoader::Uint16;
192 } else if (typeName == QStringLiteral("int32") || typeName == QStringLiteral("int")) {
193 return PlyGeometryLoader::Int32;
194 } else if (typeName == QStringLiteral("uint32") || typeName == QStringLiteral("uint")) {
195 return PlyGeometryLoader::Uint32;
196 } else if (typeName == QStringLiteral("float32") || typeName == QStringLiteral("float")) {
197 return PlyGeometryLoader::Float32;
198 } else if (typeName == QStringLiteral("float64") || typeName == QStringLiteral("double")) {
199 return PlyGeometryLoader::Float64;
200 } else if (typeName == QStringLiteral("list")) {
201 return PlyGeometryLoader::TypeList;
202 } else {
203 return PlyGeometryLoader::TypeUnknown;
204 }
205}
206
207bool PlyGeometryLoader::doLoad(QIODevice *ioDev, const QString &subMesh)
208{
209 Q_UNUSED(subMesh);
210
211 if (!parseHeader(ioDev))
212 return false;
213
214 if (!parseMesh(ioDev))
215 return false;
216
217 return true;
218}
219
220/*!
221 Read and parse the header of the PLY format file.
222 Returns \c false if one of the lines is wrongly
223 formatted.
224*/
225bool PlyGeometryLoader::parseHeader(QIODevice *ioDev)
226{
227 Format format = FormatUnknown;
228
229 m_hasNormals = m_hasTexCoords = false;
230
231 while (!ioDev->atEnd()) {
232 QByteArray lineBuffer = ioDev->readLine();
233 QTextStream textStream(lineBuffer, QIODevice::ReadOnly);
234
235 QString token;
236 textStream >> token;
237
238 if (token == QStringLiteral("end_header")) {
239 break;
240 } else if (token == QStringLiteral("format")) {
241 QString formatName;
242 textStream >> formatName;
243
244 if (formatName == QStringLiteral("ascii")) {
245 m_format = FormatAscii;
246 } else if (formatName == QStringLiteral("binary_little_endian")) {
247 m_format = FormatBinaryLittleEndian;
248 } else if (formatName == QStringLiteral("binary_big_endian")) {
249 m_format = FormatBinaryBigEndian;
250 } else {
251 qCDebug(PlyGeometryLoaderLog) << "Unrecognized PLY file format" << format;
252 return false;
253 }
254 } else if (token == QStringLiteral("element")) {
255 Element element;
256
257 QString elementName;
258 textStream >> elementName;
259
260 if (elementName == QStringLiteral("vertex")) {
261 element.type = ElementVertex;
262 } else if (elementName == QStringLiteral("face")) {
263 element.type = ElementFace;
264 } else {
265 element.type = ElementUnknown;
266 }
267
268 textStream >> element.count;
269
270 m_elements.append(t: element);
271 } else if (token == QStringLiteral("property")) {
272 if (m_elements.isEmpty()) {
273 qCDebug(PlyGeometryLoaderLog) << "Misplaced property in header";
274 return false;
275 }
276
277 Property property;
278
279 QString dataTypeName;
280 textStream >> dataTypeName;
281
282 property.dataType = toPlyDataType(typeName: dataTypeName);
283
284 if (property.dataType == TypeList) {
285 QString listSizeTypeName;
286 textStream >> listSizeTypeName;
287
288 property.listSizeType = toPlyDataType(typeName: listSizeTypeName);
289
290 QString listElementTypeName;
291 textStream >> listElementTypeName;
292
293 property.listElementType = toPlyDataType(typeName: listElementTypeName);
294 }
295
296 QString propertyName;
297 textStream >> propertyName;
298
299 if (propertyName == QStringLiteral("vertex_index")) {
300 property.type = PropertyVertexIndex;
301 } else if (propertyName == QStringLiteral("x")) {
302 property.type = PropertyX;
303 } else if (propertyName == QStringLiteral("y")) {
304 property.type = PropertyY;
305 } else if (propertyName == QStringLiteral("z")) {
306 property.type = PropertyZ;
307 } else if (propertyName == QStringLiteral("nx")) {
308 property.type = PropertyNormalX;
309 m_hasNormals = true;
310 } else if (propertyName == QStringLiteral("ny")) {
311 property.type = PropertyNormalY;
312 m_hasNormals = true;
313 } else if (propertyName == QStringLiteral("nz")) {
314 property.type = PropertyNormalZ;
315 m_hasNormals = true;
316 } else if (propertyName == QStringLiteral("s")) {
317 property.type = PropertyTextureU;
318 m_hasTexCoords = true;
319 } else if (propertyName == QStringLiteral("t")) {
320 property.type = PropertyTextureV;
321 m_hasTexCoords = true;
322 } else {
323 property.type = PropertyUnknown;
324 }
325
326 Element &element = m_elements.last();
327 element.properties.append(t: property);
328 }
329 }
330
331 if (m_format == FormatUnknown) {
332 qCDebug(PlyGeometryLoaderLog) << "Missing PLY file format";
333 return false;
334 }
335
336 return true;
337}
338
339bool PlyGeometryLoader::parseMesh(QIODevice *ioDev)
340{
341 QScopedPointer<PlyDataReader> dataReader;
342
343 switch (m_format) {
344 case FormatAscii:
345 dataReader.reset(other: new AsciiPlyDataReader(ioDev));
346 break;
347
348 case FormatBinaryLittleEndian:
349 dataReader.reset(other: new BinaryPlyDataReader(ioDev, QDataStream::LittleEndian));
350 break;
351
352 default:
353 dataReader.reset(other: new BinaryPlyDataReader(ioDev, QDataStream::BigEndian));
354 break;
355 }
356
357 for (auto &element : qAsConst(t&: m_elements)) {
358 if (element.type == ElementVertex) {
359 m_points.reserve(asize: element.count);
360
361 if (m_hasNormals)
362 m_normals.reserve(asize: element.count);
363
364 if (m_hasTexCoords)
365 m_texCoords.reserve(asize: element.count);
366 }
367
368 for (int i = 0; i < element.count; ++i) {
369 QVector3D point;
370 QVector3D normal;
371 QVector2D texCoord;
372
373 QVector<unsigned int> faceIndices;
374
375 for (auto &property : element.properties) {
376 if (property.dataType == TypeList) {
377 const int listSize = dataReader->readIntValue(type: property.listSizeType);
378
379 if (element.type == ElementFace)
380 faceIndices.reserve(asize: listSize);
381
382 for (int j = 0; j < listSize; ++j) {
383 const unsigned int value = dataReader->readIntValue(type: property.listElementType);
384
385 if (element.type == ElementFace)
386 faceIndices.append(t: value);
387 }
388 } else {
389 float value = dataReader->readFloatValue(type: property.dataType);
390
391 if (element.type == ElementVertex) {
392 switch (property.type) {
393 case PropertyX: point.setX(value); break;
394 case PropertyY: point.setY(value); break;
395 case PropertyZ: point.setZ(value); break;
396 case PropertyNormalX: normal.setX(value); break;
397 case PropertyNormalY: normal.setY(value); break;
398 case PropertyNormalZ: normal.setZ(value); break;
399 case PropertyTextureU: texCoord.setX(value); break;
400 case PropertyTextureV: texCoord.setY(value); break;
401 default: break;
402 }
403 }
404 }
405 }
406
407 if (element.type == ElementVertex) {
408 m_points.append(t: point);
409
410 if (m_hasNormals)
411 m_normals.append(t: normal);
412
413 if (m_hasTexCoords)
414 m_texCoords.append(t: texCoord);
415 } else if (element.type == ElementFace) {
416 if (faceIndices.size() >= 3) {
417 // decompose face into triangle fan
418
419 for (int j = 1; j < faceIndices.size() - 1; ++j) {
420 m_indices.append(t: faceIndices[0]);
421 m_indices.append(t: faceIndices[j]);
422 m_indices.append(t: faceIndices[j + 1]);
423 }
424 }
425 }
426 }
427 }
428
429 return true;
430}
431
432/*!
433 \enum Qt3DRender::PlyGeometryLoader::DataType
434
435 Specifies the data type specified in the parsed file.
436
437 \value Int8
438 \value Uint8
439 \value Int16
440 \value Uint16
441 \value Int32
442 \value Uint32
443 \value Float32
444 \value Float64
445 \value TypeList
446 \value TypeUnknown
447*/
448/*!
449 \enum Qt3DRender::PlyGeometryLoader::Format
450
451 Specifies the format mentioned in the header of the parsed file.
452
453 \value FormatAscii
454 \value FormatBinaryLittleEndian
455 \value FormatBinaryBigEndian
456 \value FormatUnknown
457*/
458/*!
459 \enum Qt3DRender::PlyGeometryLoader::ElementType
460
461 Specifies the element type mentioned in the header of the file.
462
463 \value ElementVertex
464 \value ElementFace
465 \value ElementUnknown
466
467*/
468/*!
469 \enum Qt3DRender::PlyGeometryLoader::PropertyType
470
471 Specifies the property type from the PLY format file that has been loaded.
472
473 \value PropertyVertexIndex Property name in header is \c vertex_index
474 \value PropertyX Property name in header is \c X
475 \value PropertyY Property name in header is \c Y
476 \value PropertyZ Property name in header is \c Z
477 \value PropertyNormalX Property name in header is \c NormalX
478 \value PropertyNormalY Property name in header is \c NormalY
479 \value PropertyNormalZ Property name in header is \c NormalZ
480 \value PropertyTextureU Property name in header is \c TextureU
481 \value PropertyTextureV Property name in header is \c TextureV
482 \value PropertyUnknown Property name in header is unknown
483
484*/
485} // namespace Qt3DRender
486
487QT_END_NAMESPACE
488

source code of qt3d/src/plugins/geometryloaders/default/plygeometryloader.cpp