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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | namespace Qt3DRender { |
13 | |
14 | Q_LOGGING_CATEGORY(PlyGeometryLoaderLog, "Qt3D.PlyGeometryLoader", QtWarningMsg) |
15 | |
16 | namespace { |
17 | |
18 | class PlyDataReader |
19 | { |
20 | public: |
21 | virtual ~PlyDataReader() {} |
22 | |
23 | virtual int readIntValue(PlyGeometryLoader::DataType type) = 0; |
24 | virtual float readFloatValue(PlyGeometryLoader::DataType type) = 0; |
25 | }; |
26 | |
27 | class AsciiPlyDataReader : public PlyDataReader |
28 | { |
29 | public: |
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 | |
48 | private: |
49 | QTextStream m_stream; |
50 | }; |
51 | |
52 | class BinaryPlyDataReader : public PlyDataReader |
53 | { |
54 | public: |
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 | |
72 | private: |
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 | |
147 | static 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 | |
172 | bool 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 | */ |
190 | bool 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 | |
304 | bool 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 | |
452 | QT_END_NAMESPACE |
453 |