comparison dmply.cpp @ 70:03aa729a9e90

Refactor PLY file parsing from dmscene.* to dmply.* and some helper functions into dmutil.h
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 16 Dec 2019 07:51:05 +0200
parents
children
comparison
equal deleted inserted replaced
69:267b3fd2c98c 70:03aa729a9e90
1 //
2 // GLDragon - OpenGL PLY model viewer / simple benchmark
3 // -- PLY file parsing
4 // Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
5 // (C) Copyright 2019 Tecnic Software productions (TNSP)
6 //
7 // See file "COPYING" for license information.
8 //
9 #include "dmply.h"
10 #include <SDL_endian.h>
11
12
13 static DMPLYPropType dmPLYParsePropType(const std::string &name)
14 {
15 if (name == "list")
16 return PLY_TYPE_LIST;
17 else
18 if (name == "float" || name == "float32")
19 return PLY_TYPE_FLOAT;
20 else
21 if (name == "double" || name == "float64")
22 return PLY_TYPE_DOUBLE;
23 else
24 if (name == "uchar" || name == "uint8")
25 return PLY_TYPE_UINT8;
26 else
27 if (name == "int16")
28 return PLY_TYPE_INT16;
29 else
30 if (name == "uint16")
31 return PLY_TYPE_UINT16;
32 else
33 if (name == "int" || name == "int32")
34 return PLY_TYPE_INT32;
35 else
36 if (name == "uint" || name == "uint32")
37 return PLY_TYPE_UINT32;
38 else
39 return PLY_TYPE_NONE;
40 }
41
42
43 static bool dmPLYParsePropertyValueASCII(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval, size_t &pos)
44 {
45 size_t len = 0;
46 std::string val = info.line.substr(pos);
47
48 switch (ptype)
49 {
50 case PLY_TYPE_INT8:
51 case PLY_TYPE_INT16:
52 case PLY_TYPE_INT32:
53 pval.v_int = std::stoi(val, &len);
54 break;
55
56 case PLY_TYPE_UINT8:
57 case PLY_TYPE_UINT16:
58 case PLY_TYPE_UINT32:
59 pval.v_uint = std::stoi(val, &len);
60 break;
61
62 case PLY_TYPE_FLOAT:
63 pval.v_float = std::stof(val, &len);
64 break;
65
66 case PLY_TYPE_DOUBLE:
67 pval.v_double = std::stod(val, &len);
68 break;
69
70 default:
71 return info.textError(
72 "Internal error, unimplemented PLY property type");
73 }
74
75 pos += len;
76
77 return true;
78 }
79
80
81 static bool dmPLYParsePropertyASCII(DMPLYFileInfo &info, DMPLYFileProperty &prop, size_t &pos)
82 {
83 switch (prop.type)
84 {
85 case PLY_TYPE_LIST:
86 prop.list_values.clear();
87
88 if (!dmPLYParsePropertyValueASCII(info, prop.list_num_type, prop.list_num_value, pos))
89 return false;
90
91 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
92 {
93 DMPLYPropValue pval;
94 if (!dmPLYParsePropertyValueASCII(info, prop.list_values_type, pval, pos))
95 return false;
96
97 prop.list_values.push_back(pval);
98 }
99
100 if (prop.list_values.size() != prop.list_num_value.v_uint)
101 {
102 return info.syntaxError(
103 "Number of property list '"+ prop.name +" values not equal to number specified");
104 }
105 return true;
106
107 default:
108 return dmPLYParsePropertyValueASCII(info, prop.type, prop.value, pos);
109 }
110 }
111
112
113 static bool dmPLYParseElementASCII(DMPLYFileInfo &info, DMPLYFileElement &element)
114 {
115 size_t nprop = 0, pos = 0;
116
117 // Read one line
118 if (!info.readLine())
119 {
120 return info.textError(
121 "Unexpected end of file");
122 }
123
124 // Skip empty lines
125 if (info.line.empty())
126 return true;
127
128 // Parse the properties
129 for (auto const prop : element.properties)
130 {
131 if (!dmPLYParsePropertyASCII(info, *prop, pos))
132 return false;
133
134 nprop++;
135 }
136
137 if (nprop != element.properties.size())
138 {
139 return info.syntaxError(
140 "Expected N properties, got different number");
141 }
142
143 return true;
144 }
145
146
147 bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval)
148 {
149 uint8_t tmpU8;
150 int8_t tmpS8;
151 uint16_t tmpU16;
152 int16_t tmpS16;
153 uint32_t tmpU32;
154 int32_t tmpS32;
155 float tmpFloat;
156
157 switch (ptype)
158 {
159 case PLY_TYPE_INT8:
160 info.file.read(reinterpret_cast<char *>(&tmpS8), 1);
161 pval.v_int = tmpS8;
162 break;
163
164 case PLY_TYPE_UINT8:
165 info.file.read(reinterpret_cast<char *>(&tmpU8), 1);
166 pval.v_uint = tmpU8;
167 break;
168
169 case PLY_TYPE_INT16:
170 info.file.read(reinterpret_cast<char *>(&tmpS16), 2);
171 tmpS16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpS16) : SDL_SwapBE16(tmpS16);
172 pval.v_int = tmpS16;
173 break;
174
175 case PLY_TYPE_UINT16:
176 info.file.read(reinterpret_cast<char *>(&tmpU16), 2);
177 tmpU16 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE16(tmpU16) : SDL_SwapBE16(tmpU16);
178 pval.v_uint = tmpU16;
179 break;
180
181 case PLY_TYPE_INT32:
182 info.file.read(reinterpret_cast<char *>(&tmpS32), 4);
183 tmpS32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpS32) : SDL_SwapBE32(tmpS32);
184 pval.v_int = tmpS32;
185 break;
186
187 case PLY_TYPE_UINT32:
188 info.file.read(reinterpret_cast<char *>(&tmpU32), 4);
189 tmpU32 = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapLE32(tmpU32) : SDL_SwapBE32(tmpU32);
190 pval.v_uint = tmpU32;
191 break;
192
193 case PLY_TYPE_FLOAT:
194 info.file.read(reinterpret_cast<char *>(&tmpFloat), 4);
195 pval.v_float = (info.format == PLY_FMT_BIN_LE) ? SDL_SwapFloatLE(tmpFloat) : SDL_SwapFloatBE(tmpFloat);
196 break;
197
198 default:
199 return info.textError(
200 "Internal error, unimplemented PLY property type");
201 }
202
203 return true;
204 }
205
206
207 bool dmPLYReadPropertyBIN(DMPLYFileInfo &info, DMPLYFileProperty &prop)
208 {
209 switch (prop.type)
210 {
211 case PLY_TYPE_LIST:
212 prop.list_values.clear();
213
214 if (!dmPLYReadPropertyValueBIN(info, prop.list_num_type, prop.list_num_value))
215 return false;
216
217 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
218 {
219 DMPLYPropValue pval;
220 if (!dmPLYReadPropertyValueBIN(info, prop.list_values_type, pval))
221 return false;
222
223 prop.list_values.push_back(pval);
224 }
225
226 if (prop.list_values.size() != prop.list_num_value.v_uint)
227 {
228 return info.syntaxError(
229 "Number of property list '"+ prop.name +" values not equal to number specified");
230 }
231 return true;
232
233 default:
234 return dmPLYReadPropertyValueBIN(info, prop.type, prop.value);
235 }
236 }
237
238
239 bool dmPLYReadElementBIN(DMPLYFileInfo &info, DMPLYFileElement &element)
240 {
241 size_t nprop = 0;
242
243 for (auto const prop : element.properties)
244 {
245 if (!dmPLYReadPropertyBIN(info, *prop))
246 return false;
247
248 nprop++;
249 }
250
251 if (nprop != element.properties.size())
252 {
253 return info.syntaxError(
254 "Expected N properties, got different number");
255 }
256
257 return true;
258 }
259
260
261 bool dmLoadFromPLY(DMModel &model, const std::string &filename)
262 {
263 DMPLYFileInfo info;
264
265 return dmLoadFromPLY(model, filename, info);
266 }
267
268
269 bool dmLoadFromPLY(DMModel &model, const std::string &filename, DMPLYFileInfo &info)
270 {
271 info.filename = filename;
272 info.nline = info.state = 0;
273 info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary);
274
275 dmMsg("Trying to read mesh from '%s'.\n",
276 info.filename.c_str());
277
278 if (!info.file.is_open())
279 {
280 dmError("Unable to open file '%s'.\n",
281 info.filename.c_str());
282 return false;
283 }
284
285 // Parse the PLY header
286 while (info.state >= 0)
287 {
288 // Read one line
289 if (!info.readLine())
290 return false;
291
292 // Skip empty lines
293 if (info.line.empty())
294 continue;
295
296 // Split key and value
297 std::vector<std::string> tokens = dmStrSplit(info.line);
298 std::string &key = tokens[0];
299
300 if (info.state == 0 && key == "ply")
301 {
302 info.state = 1;
303 }
304 else
305 if (info.state > 0 && key == "end_header")
306 {
307 info.state = -1;
308 }
309 else
310 if (info.state == 1 && key == "format")
311 {
312 if (tokens.size() < 3)
313 {
314 return info.syntaxError(
315 "Expected value for format");
316 }
317
318 info.format = PLY_FMT_UNKNOWN;
319
320 if (tokens[1] == "ascii")
321 info.format = PLY_FMT_ASCII;
322 else
323 if (tokens[1] == "binary_little_endian")
324 info.format = PLY_FMT_BIN_LE;
325 else
326 if (tokens[1] == "binary_big_endian")
327 info.format = PLY_FMT_BIN_BE;
328
329 if (info.format == PLY_FMT_UNKNOWN ||
330 tokens[2] != "1.0")
331 {
332 dmError("Unknown or unsupported PLY file format '%s'.\n",
333 (tokens[1] +" "+ tokens[2]).c_str());
334 return false;
335 }
336
337 info.state = 2;
338 }
339 else
340 if ((info.state == 2 || info.state == 3) && key == "element")
341 {
342 if (tokens.size() < 3)
343 {
344 return info.syntaxError(
345 "Expected a value for element key");
346 }
347
348 std::string &el_name = tokens[1], &el_value = tokens[2];
349
350 if (info.elem_map.count(el_name))
351 {
352 return info.syntaxError(
353 "Element '"+ el_name +"' has already been defined");
354 }
355
356 DMPLYFileElement &elem = info.elem_map[el_name];
357 info.element = &elem;
358 info.elements.push_back(&elem);
359
360 elem.name = el_name;
361 elem.value = std::stoi(el_value);
362
363 info.state = 3;
364 }
365 else
366 if (info.state == 3 && key == "property")
367 {
368 if (tokens.size() < 3)
369 {
370 return info.syntaxError(
371 "Expected value for property");
372 }
373
374 const std::string
375 &pr_name = tokens.back(),
376 &pr_typename = tokens.at(1);
377
378 if (!info.element)
379 {
380 // Should not happen
381 return info.syntaxError(
382 "No element defined for property '"+ pr_name +"'?");
383 }
384
385 // Check if this property has been already defined
386 if (info.element->prop_map.count(pr_name))
387 {
388 return info.syntaxError(
389 "Element '"+ info.element->name +
390 "' already has property '"+ pr_name +"' defined?");
391 }
392
393 // Parse property information
394 DMPLYPropType pr_type = dmPLYParsePropType(pr_typename);
395 if (pr_type == PLY_TYPE_NONE)
396 {
397 return info.syntaxError(
398 "Invalid or unsupported property type '"+ pr_typename +"'");
399 }
400
401 DMPLYFileProperty &prop = info.element->prop_map[pr_name];
402 info.element->properties.push_back(&prop);
403
404 prop.name = pr_name;
405 prop.type = pr_type;
406
407 if (pr_type == PLY_TYPE_LIST)
408 {
409 // List is a special case
410 if (tokens.size() < 5)
411 {
412 return info.syntaxError(
413 "Expected more values for a list property (num_type, val_type, name)");
414 }
415
416 prop.list_num_type = dmPLYParsePropType(tokens.at(2));
417 prop.list_values_type = dmPLYParsePropType(tokens.at(3));
418
419 if (prop.list_num_type == PLY_TYPE_NONE ||
420 prop.list_values_type == PLY_TYPE_NONE)
421 {
422 return info.syntaxError(
423 "Invalid or unsupported property type(s)");
424 }
425 }
426 }
427 else
428 if (info.state > 0 // && (key == "comment" || key == "obj_info")
429 )
430 {
431 // Ignore comments
432 // .. and unknown keys
433 }
434 else
435 {
436 return info.syntaxError(
437 "Unexpected key '"+ key +"'");
438 }
439 }
440
441 // Check header data
442 DMPLYFileElement *elem;
443 DMPLYFileProperty *prop;
444 if (info.state != -1 ||
445 (elem = info.checkElem(PLY_ELEM_FACE)) == 0 ||
446 (prop = elem->checkProp(PLY_PROP_VERTEX_INDICES)) == 0 ||
447 prop->type != PLY_TYPE_LIST ||
448 (elem = info.checkElem(PLY_ELEM_VERTEX)) == 0 ||
449 (prop = elem->checkProp("x")) == 0 || prop->type != PLY_TYPE_FLOAT ||
450 (prop = elem->checkProp("y")) == 0 || prop->type != PLY_TYPE_FLOAT ||
451 (prop = elem->checkProp("z")) == 0 || prop->type != PLY_TYPE_FLOAT
452 )
453 {
454 dmError("PLY file did not contain expected information.\n");
455 return false;
456 }
457
458 model.nvertices = info.elem_map[PLY_ELEM_VERTEX].value;
459 model.nfaces = info.elem_map[PLY_ELEM_FACE].value;
460
461
462 if (model.nvertices < 3 || model.nfaces < 1)
463 {
464 dmError("Invalid nvertices (%d) and/or nfaces (%d).\n",
465 model.nvertices, model.nfaces);
466 return false;
467 }
468
469 dmMsg("Should have %d vertices, %d faces\n",
470 model.nvertices, model.nfaces);
471
472 // Pre-allocate space
473 model.vertices.reserve(model.nvertices);
474 model.normals.reserve(model.nvertices);
475 model.faces.reserve(model.nfaces * 3);
476
477 // Read the actual data (in order of the elements)
478 for (auto *element : info.elements)
479 for (int n = 0; n < element->value; n++)
480 {
481 switch (info.format)
482 {
483 case PLY_FMT_ASCII:
484 if (!dmPLYParseElementASCII(info, *element))
485 return false;
486 break;
487
488 default:
489 if (!dmPLYReadElementBIN(info, *element))
490 return false;
491 break;
492 }
493
494 // Check for specific elements
495 if (element->name == PLY_ELEM_FACE)
496 {
497 DMPLYFileProperty &prop = element->prop_map[PLY_PROP_VERTEX_INDICES];
498
499 if (prop.list_num_value.v_uint != 3)
500 {
501 return info.syntaxError(
502 "Expected 3 vertices per face");
503 }
504
505 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++)
506 {
507 model.faces.push_back(prop.list_values[n].v_uint);
508 }
509 }
510 else
511 if (element->name == PLY_ELEM_VERTEX)
512 {
513 DMVector3 vert;
514 vert.x = element->prop_map["x"].value.v_float;
515 vert.y = element->prop_map["y"].value.v_float;
516 vert.z = element->prop_map["z"].value.v_float;
517
518 model.vertices.push_back(vert);
519
520 if (element->checkProp("nx") &&
521 element->checkProp("ny") &&
522 element->checkProp("nz"))
523 {
524 DMVector3 normal;
525 normal.x = element->prop_map["nx"].value.v_float;
526 normal.y = element->prop_map["ny"].value.v_float;
527 normal.z = element->prop_map["nz"].value.v_float;
528
529 model.normals.push_back(normal);
530 }
531 }
532 }
533
534 dmMsg("Found %ld vertices, %ld normals, %ld faces\n",
535 model.vertices.size(),
536 model.normals.size(),
537 model.faces.size() / 3);
538
539 return true;
540 }