Mercurial > hg > forks > gldragon
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 } |