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

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