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 | |
45 | QT_BEGIN_NAMESPACE |
46 | |
47 | namespace Qt3DRender { |
48 | |
49 | Q_LOGGING_CATEGORY(PlyGeometryLoaderLog, "Qt3D.PlyGeometryLoader" , QtWarningMsg) |
50 | |
51 | namespace { |
52 | |
53 | class PlyDataReader |
54 | { |
55 | public: |
56 | virtual ~PlyDataReader() {} |
57 | |
58 | virtual int readIntValue(PlyGeometryLoader::DataType type) = 0; |
59 | virtual float readFloatValue(PlyGeometryLoader::DataType type) = 0; |
60 | }; |
61 | |
62 | class AsciiPlyDataReader : public PlyDataReader |
63 | { |
64 | public: |
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 | |
83 | private: |
84 | QTextStream m_stream; |
85 | }; |
86 | |
87 | class BinaryPlyDataReader : public PlyDataReader |
88 | { |
89 | public: |
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 | |
107 | private: |
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 | |
182 | static 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 | |
207 | bool 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 | */ |
225 | bool PlyGeometryLoader::(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 | |
339 | bool 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 | |
487 | QT_END_NAMESPACE |
488 | |