Mercurial > hg > forks > gldragon
comparison dmmodel.cpp @ 19:a329f0216491
Implement PLY file format parsing and extremely simplistic scene setup file format.
Not finished yet.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 07 Nov 2019 20:15:33 +0200 |
parents | |
children | 1404dfcee7b8 |
comparison
equal
deleted
inserted
replaced
18:b1e75c65016d | 19:a329f0216491 |
---|---|
1 // | |
2 // | |
3 // | |
4 #include "dmmodel.h" | |
5 | |
6 | |
7 static bool dmError(DMTextFileInfo &info, const std::string &msg) | |
8 { | |
9 printf("ERROR: %s on line #%d: %s\n", | |
10 msg.c_str(), info.nline, info.line.c_str()); | |
11 return false; | |
12 } | |
13 | |
14 | |
15 static bool dmSyntaxError(DMTextFileInfo &info, const std::string &msg) | |
16 { | |
17 printf("ERROR: Syntax error on line #%d: %s\n", | |
18 info.nline, msg.c_str()); | |
19 return false; | |
20 } | |
21 | |
22 | |
23 static bool dmReadLine(DMTextFileInfo &info) | |
24 { | |
25 if (!std::getline(info.file, info.line)) | |
26 return false; | |
27 | |
28 info.nline++; | |
29 | |
30 info.line = dmStrTrim(info.line); | |
31 | |
32 return true; | |
33 } | |
34 | |
35 | |
36 static DMPLYPropType dmPLYParsePropType(const std::string &name) | |
37 { | |
38 if (name == "list") | |
39 return PLY_TYPE_LIST; | |
40 else | |
41 if (name == "float" || name == "float32") | |
42 return PLY_TYPE_FLOAT; | |
43 else | |
44 if (name == "double" || name == "float64") | |
45 return PLY_TYPE_DOUBLE; | |
46 else | |
47 if (name == "uchar" || name == "uint8") | |
48 return PLY_TYPE_UINT8; | |
49 else | |
50 if (name == "int16") | |
51 return PLY_TYPE_INT16; | |
52 else | |
53 if (name == "uint16") | |
54 return PLY_TYPE_UINT16; | |
55 else | |
56 if (name == "int" || name == "int32") | |
57 return PLY_TYPE_INT32; | |
58 else | |
59 if (name == "uint" || name == "uint32") | |
60 return PLY_TYPE_UINT32; | |
61 else | |
62 return PLY_TYPE_NONE; | |
63 } | |
64 | |
65 | |
66 static bool dmPLYParsePropertyValueASCII(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval, size_t &pos) | |
67 { | |
68 size_t len = 0; | |
69 std::string val = info.line.substr(pos); | |
70 | |
71 switch (ptype) | |
72 { | |
73 case PLY_TYPE_INT8: | |
74 case PLY_TYPE_INT16: | |
75 case PLY_TYPE_INT32: | |
76 pval.v_int = std::stoi(val, &len); | |
77 break; | |
78 | |
79 case PLY_TYPE_UINT8: | |
80 case PLY_TYPE_UINT16: | |
81 case PLY_TYPE_UINT32: | |
82 pval.v_uint = std::stoi(val, &len); | |
83 break; | |
84 | |
85 case PLY_TYPE_FLOAT: | |
86 pval.v_float = std::stof(val, &len); | |
87 break; | |
88 | |
89 case PLY_TYPE_DOUBLE: | |
90 pval.v_double = std::stod(val, &len); | |
91 break; | |
92 | |
93 default: | |
94 return dmError(info, | |
95 "Internal error, unimplemented PLY property type"); | |
96 } | |
97 | |
98 pos += len; | |
99 | |
100 return true; | |
101 } | |
102 | |
103 | |
104 static bool dmPLYParsePropertyASCII(DMPLYFileInfo &info, DMPLYFileProperty &prop, size_t &pos) | |
105 { | |
106 switch (prop.type) | |
107 { | |
108 case PLY_TYPE_LIST: | |
109 prop.list_values.clear(); | |
110 | |
111 if (!dmPLYParsePropertyValueASCII(info, prop.list_num_type, prop.list_num_value, pos)) | |
112 return false; | |
113 | |
114 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) | |
115 { | |
116 DMPLYPropValue pval; | |
117 if (!dmPLYParsePropertyValueASCII(info, prop.list_values_type, pval, pos)) | |
118 return false; | |
119 | |
120 prop.list_values.push_back(pval); | |
121 } | |
122 | |
123 if (prop.list_values.size() != prop.list_num_value.v_uint) | |
124 { | |
125 return dmSyntaxError(info, | |
126 "Number of property list '"+ prop.name +" values not equal to number specified"); | |
127 } | |
128 return true; | |
129 | |
130 default: | |
131 return dmPLYParsePropertyValueASCII(info, prop.type, prop.value, pos); | |
132 } | |
133 } | |
134 | |
135 | |
136 static bool dmPLYParseElementASCII(DMPLYFileInfo &info, DMPLYFileElement &element) | |
137 { | |
138 size_t nprop = 0, pos = 0; | |
139 | |
140 // Read one line | |
141 if (!dmReadLine(info)) | |
142 return false; | |
143 | |
144 // Skip empty lines | |
145 if (info.line.empty()) | |
146 return true; | |
147 | |
148 // Parse the properties | |
149 for (auto const prop : element.properties) | |
150 { | |
151 if (!dmPLYParsePropertyASCII(info, *prop, pos)) | |
152 return false; | |
153 | |
154 nprop++; | |
155 } | |
156 | |
157 if (nprop != element.properties.size()) | |
158 { | |
159 return dmSyntaxError(info, | |
160 "Expected N properties, got different number"); | |
161 } | |
162 | |
163 return true; | |
164 } | |
165 | |
166 | |
167 bool dmPLYReadPropertyValueBIN(DMPLYFileInfo &info, const DMPLYPropType ptype, DMPLYPropValue &pval) | |
168 { | |
169 uint8_t tmp[8]; | |
170 uint16_t tmpU16; | |
171 int16_t tmpS16; | |
172 uint32_t tmpU32; | |
173 int32_t tmpS32; | |
174 | |
175 switch (ptype) | |
176 { | |
177 case PLY_TYPE_INT8: | |
178 info.file.read(reinterpret_cast<char *>(&tmp), 1); | |
179 pval.v_int = (int8_t) tmp[0]; | |
180 break; | |
181 | |
182 case PLY_TYPE_UINT8: | |
183 info.file.read(reinterpret_cast<char *>(&tmp), 1); | |
184 pval.v_uint = (uint8_t) tmp[0]; | |
185 break; | |
186 | |
187 case PLY_TYPE_INT16: | |
188 info.file.read(reinterpret_cast<char *>(&tmp), 2); | |
189 tmpS16 = | |
190 (tmp[0] << 8) | | |
191 (tmp[1]); | |
192 pval.v_int = tmpS16; | |
193 break; | |
194 | |
195 case PLY_TYPE_UINT16: | |
196 info.file.read(reinterpret_cast<char *>(&tmp), 2); | |
197 tmpU16 = | |
198 (tmp[0] << 8) | | |
199 (tmp[1]); | |
200 pval.v_uint = tmpU16; | |
201 break; | |
202 | |
203 case PLY_TYPE_INT32: | |
204 info.file.read(reinterpret_cast<char *>(&tmp), 4); | |
205 tmpS32 = | |
206 (tmp[0] << 24) | | |
207 (tmp[1] << 16) | | |
208 (tmp[2] << 8) | | |
209 (tmp[3]); | |
210 | |
211 pval.v_int = tmpS32; | |
212 break; | |
213 | |
214 case PLY_TYPE_UINT32: | |
215 info.file.read(reinterpret_cast<char *>(&tmp), 4); | |
216 tmpU32 = | |
217 (tmp[3] << 24) | | |
218 (tmp[2] << 16) | | |
219 (tmp[1] << 8) | | |
220 (tmp[0]); | |
221 | |
222 pval.v_uint = tmpU32; | |
223 break; | |
224 | |
225 case PLY_TYPE_FLOAT: | |
226 info.file.read(reinterpret_cast<char *>(&tmp), 4); | |
227 tmpU32 = | |
228 (tmp[0] << 24) | | |
229 (tmp[1] << 16) | | |
230 (tmp[2] << 8) | | |
231 (tmp[3]); | |
232 | |
233 pval.v_uint = tmpU32; | |
234 break; | |
235 | |
236 default: | |
237 return dmError(info, | |
238 "Internal error, unimplemented PLY property type"); | |
239 } | |
240 | |
241 return true; | |
242 } | |
243 | |
244 | |
245 bool dmPLYReadPropertyBIN(DMPLYFileInfo &info, DMPLYFileProperty &prop) | |
246 { | |
247 switch (prop.type) | |
248 { | |
249 case PLY_TYPE_LIST: | |
250 prop.list_values.clear(); | |
251 | |
252 if (!dmPLYReadPropertyValueBIN(info, prop.list_num_type, prop.list_num_value)) | |
253 return false; | |
254 | |
255 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) | |
256 { | |
257 DMPLYPropValue pval; | |
258 if (!dmPLYReadPropertyValueBIN(info, prop.list_values_type, pval)) | |
259 return false; | |
260 | |
261 prop.list_values.push_back(pval); | |
262 } | |
263 | |
264 if (prop.list_values.size() != prop.list_num_value.v_uint) | |
265 { | |
266 return dmSyntaxError(info, | |
267 "Number of property list '"+ prop.name +" values not equal to number specified"); | |
268 } | |
269 return true; | |
270 | |
271 default: | |
272 return dmPLYReadPropertyValueBIN(info, prop.type, prop.value); | |
273 } | |
274 } | |
275 | |
276 | |
277 bool dmPLYReadElementBIN(DMPLYFileInfo &info, DMPLYFileElement &element) | |
278 { | |
279 size_t nprop = 0; | |
280 | |
281 for (auto const prop : element.properties) | |
282 { | |
283 if (!dmPLYReadPropertyBIN(info, *prop)) | |
284 return false; | |
285 | |
286 nprop++; | |
287 } | |
288 | |
289 if (nprop != element.properties.size()) | |
290 { | |
291 return dmSyntaxError(info, | |
292 "Expected N properties, got different number"); | |
293 } | |
294 | |
295 return true; | |
296 } | |
297 | |
298 | |
299 bool DMModel::loadFromPLY(const std::string &filename) | |
300 { | |
301 DMPLYFileInfo info; | |
302 | |
303 return loadFromPLY(filename, info); | |
304 } | |
305 | |
306 | |
307 bool DMModel::loadFromPLY(const std::string &filename, DMPLYFileInfo &info) | |
308 { | |
309 info.filename = filename; | |
310 info.nline = info.state = 0; | |
311 info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary); | |
312 | |
313 printf("INFO: Trying to read mesh from '%s'.\n", | |
314 info.filename.c_str()); | |
315 | |
316 if (!info.file.is_open()) | |
317 { | |
318 printf("ERROR: Unable to open file '%s'.\n", | |
319 info.filename.c_str()); | |
320 return false; | |
321 } | |
322 | |
323 // Parse the PLY header | |
324 while (info.state >= 0) | |
325 { | |
326 // Read one line | |
327 if (!dmReadLine(info)) | |
328 return false; | |
329 | |
330 // Skip empty lines | |
331 if (info.line.empty()) | |
332 continue; | |
333 | |
334 // Split key and value | |
335 std::vector<std::string> tokens = dmStrSplit(info.line); | |
336 std::string &key = tokens[0]; | |
337 | |
338 if (info.state == 0 && key == "ply") | |
339 { | |
340 info.state = 1; | |
341 } | |
342 else | |
343 if (info.state > 0 && key == "end_header") | |
344 { | |
345 info.state = -1; | |
346 } | |
347 else | |
348 if (info.state == 1 && key == "format") | |
349 { | |
350 if (tokens.size() < 3) | |
351 { | |
352 return dmSyntaxError(info, | |
353 "Expected value for format"); | |
354 } | |
355 | |
356 info.format = PLY_FMT_UNKNOWN; | |
357 | |
358 if (tokens[1] == "ascii") | |
359 info.format = PLY_FMT_ASCII; | |
360 else | |
361 if (tokens[1] == "binary_little_endian") | |
362 info.format = PLY_FMT_BIN_LE; | |
363 else | |
364 if (tokens[1] == "binary_big_endian") | |
365 info.format = PLY_FMT_BIN_BE; | |
366 | |
367 if (info.format == PLY_FMT_UNKNOWN || | |
368 tokens[2] != "1.0") | |
369 { | |
370 printf("ERROR: Unknown or unsupported PLY file format '%s'.\n", | |
371 (tokens[1] +" "+ tokens[2]).c_str()); | |
372 return false; | |
373 } | |
374 | |
375 info.state = 2; | |
376 } | |
377 else | |
378 if ((info.state == 2 || info.state == 3) && key == "element") | |
379 { | |
380 if (tokens.size() < 3) | |
381 { | |
382 return dmSyntaxError(info, | |
383 "Expected a value for element key"); | |
384 } | |
385 | |
386 std::string &el_name = tokens[1], &el_value = tokens[2]; | |
387 | |
388 if (info.elem_map.count(el_name)) | |
389 { | |
390 return dmSyntaxError(info, | |
391 "Element '"+ el_name +"' has already been defined"); | |
392 } | |
393 | |
394 DMPLYFileElement &elem = info.elem_map[el_name]; | |
395 info.element = &elem; | |
396 info.elements.push_back(&elem); | |
397 | |
398 elem.name = el_name; | |
399 elem.value = std::stoi(el_value); | |
400 | |
401 info.state = 3; | |
402 } | |
403 else | |
404 if (info.state == 3 && key == "property") | |
405 { | |
406 if (tokens.size() < 3) | |
407 { | |
408 return dmSyntaxError(info, | |
409 "Expected value for property"); | |
410 } | |
411 | |
412 const std::string | |
413 &pr_name = tokens.back(), | |
414 &pr_typename = tokens.at(1); | |
415 | |
416 if (!info.element) | |
417 { | |
418 // Should not happen | |
419 return dmSyntaxError(info, | |
420 "No element defined for property '"+ pr_name +"'?"); | |
421 } | |
422 | |
423 // Check if this property has been already defined | |
424 if (info.element->prop_map.count(pr_name)) | |
425 { | |
426 return dmSyntaxError(info, | |
427 "Element '"+ info.element->name + | |
428 "' already has property '"+ pr_name +"' defined?"); | |
429 } | |
430 | |
431 // Parse property information | |
432 DMPLYPropType pr_type = dmPLYParsePropType(pr_typename); | |
433 if (pr_type == PLY_TYPE_NONE) | |
434 { | |
435 return dmSyntaxError(info, | |
436 "Invalid or unsupported property type '"+ pr_typename +"'"); | |
437 } | |
438 | |
439 DMPLYFileProperty &prop = info.element->prop_map[pr_name]; | |
440 info.element->properties.push_back(&prop); | |
441 | |
442 prop.name = pr_name; | |
443 prop.type = pr_type; | |
444 | |
445 if (pr_type == PLY_TYPE_LIST) | |
446 { | |
447 // List is a special case | |
448 if (tokens.size() < 5) | |
449 { | |
450 return dmSyntaxError(info, | |
451 "Expected more values for a list property (num_type, val_type, name)"); | |
452 } | |
453 | |
454 prop.list_num_type = dmPLYParsePropType(tokens.at(2)); | |
455 prop.list_values_type = dmPLYParsePropType(tokens.at(3)); | |
456 | |
457 if (prop.list_num_type == PLY_TYPE_NONE || | |
458 prop.list_values_type == PLY_TYPE_NONE) | |
459 { | |
460 return dmSyntaxError(info, | |
461 "Invalid or unsupported property type(s)"); | |
462 } | |
463 } | |
464 } | |
465 else | |
466 if (info.state > 0 // && (key == "comment" || key == "obj_info") | |
467 ) | |
468 { | |
469 // Ignore comments | |
470 // .. and unknown keys | |
471 } | |
472 else | |
473 { | |
474 return dmSyntaxError(info, | |
475 "Unexpected key '"+ key +"'"); | |
476 } | |
477 } | |
478 | |
479 // Check header data | |
480 DMPLYFileElement *elem; | |
481 DMPLYFileProperty *prop; | |
482 if (info.state != -1 || | |
483 (elem = info.checkElem(PLY_ELEM_FACE)) == 0 || | |
484 (prop = elem->checkProp(PLY_PROP_VERTEX_INDICES)) == 0 || | |
485 prop->type != PLY_TYPE_LIST || | |
486 (elem = info.checkElem(PLY_ELEM_VERTEX)) == 0 || | |
487 (prop = elem->checkProp("x")) == 0 || prop->type != PLY_TYPE_FLOAT || | |
488 (prop = elem->checkProp("y")) == 0 || prop->type != PLY_TYPE_FLOAT || | |
489 (prop = elem->checkProp("z")) == 0 || prop->type != PLY_TYPE_FLOAT | |
490 ) | |
491 { | |
492 printf("ERROR: PLY file did not contain expected information.\n"); | |
493 return false; | |
494 } | |
495 | |
496 nvertices = info.elem_map[PLY_ELEM_VERTEX].value; | |
497 nfaces = info.elem_map[PLY_ELEM_FACE].value; | |
498 | |
499 | |
500 if (nvertices < 3 || nfaces < 1) | |
501 { | |
502 printf("ERROR: Invalid nvertices (%d) and/or nfaces (%d).\n", | |
503 nvertices, nfaces); | |
504 return false; | |
505 } | |
506 | |
507 printf("INFO: Should have %d vertices, %d faces\n", | |
508 nvertices, nfaces); | |
509 | |
510 // Pre-allocate space | |
511 vertices.reserve(nvertices); | |
512 normals.reserve(nvertices); | |
513 faces.reserve(nfaces * 3); | |
514 | |
515 // Read the actual data (in order of the elements) | |
516 for (auto *element : info.elements) | |
517 for (int n = 0; n < element->value; n++) | |
518 { | |
519 switch (info.format) | |
520 { | |
521 case PLY_FMT_ASCII: | |
522 if (!dmPLYParseElementASCII(info, *element)) | |
523 return false; | |
524 break; | |
525 | |
526 default: | |
527 if (!dmPLYReadElementBIN(info, *element)) | |
528 return false; | |
529 break; | |
530 } | |
531 | |
532 // Check for specific elements | |
533 if (element->name == PLY_ELEM_FACE) | |
534 { | |
535 DMPLYFileProperty &prop = element->prop_map[PLY_PROP_VERTEX_INDICES]; | |
536 | |
537 if (prop.list_num_value.v_uint != 3) | |
538 { | |
539 return dmSyntaxError(info, | |
540 "Expected 3 vertices per face"); | |
541 } | |
542 | |
543 for (unsigned int n = 0; n < prop.list_num_value.v_uint; n++) | |
544 { | |
545 faces.push_back(prop.list_values[n].v_uint); | |
546 } | |
547 } | |
548 else | |
549 if (element->name == PLY_ELEM_VERTEX) | |
550 { | |
551 DMVertex vert; | |
552 vert.x = element->prop_map["x"].value.v_float; | |
553 vert.y = element->prop_map["y"].value.v_float; | |
554 vert.z = element->prop_map["z"].value.v_float; | |
555 | |
556 vertices.push_back(vert); | |
557 | |
558 if (element->checkProp("nx") && | |
559 element->checkProp("ny") && | |
560 element->checkProp("nz")) | |
561 { | |
562 DMVertex normal; | |
563 normal.x = element->prop_map["nx"].value.v_float; | |
564 normal.y = element->prop_map["ny"].value.v_float; | |
565 normal.z = element->prop_map["nz"].value.v_float; | |
566 | |
567 normals.push_back(normal); | |
568 } | |
569 } | |
570 } | |
571 | |
572 printf("INFO: Found %ld vertices, %ld normals, %ld faces\n", | |
573 vertices.size(), | |
574 normals.size(), | |
575 faces.size() / 3); | |
576 | |
577 return true; | |
578 } | |
579 | |
580 | |
581 bool DMSimpleScene::loadInfo(const std::string &filename) | |
582 { | |
583 DMTextFileInfo info; | |
584 | |
585 info.filename = filename; | |
586 info.nline = info.state = 0; | |
587 info.file.open(info.filename.c_str(), std::fstream::in | std::fstream::binary); | |
588 | |
589 printf("INFO: Trying to read scene data from '%s'.\n", | |
590 info.filename.c_str()); | |
591 | |
592 if (!info.file.is_open()) | |
593 { | |
594 printf("ERROR: Unable to open file '%s'.\n", | |
595 info.filename.c_str()); | |
596 return false; | |
597 } | |
598 | |
599 while (info.state >= 0) | |
600 { | |
601 // Read one line | |
602 if (!dmReadLine(info)) | |
603 { | |
604 info.state = -1; | |
605 break; | |
606 } | |
607 | |
608 // Skip empty lines and comments | |
609 if (info.line.empty() || | |
610 info.line[0] == '#' || | |
611 info.line[0] == ';') | |
612 continue; | |
613 | |
614 // Split key and values | |
615 std::vector<std::string> tokens = dmStrSplit(info.line); | |
616 std::string &key = tokens[0]; | |
617 | |
618 if (key == "color") | |
619 { | |
620 DMColor color; | |
621 color.alpha = 0xff; | |
622 | |
623 if (tokens.size() == 2) | |
624 { | |
625 color.r = color.g = color.b = std::stoi(tokens[1], 0, 0); | |
626 } | |
627 else | |
628 if (tokens.size() == 4 || tokens.size() == 5) | |
629 { | |
630 color.r = std::stoi(tokens[1], 0, 0); | |
631 color.g = std::stoi(tokens[2], 0, 0); | |
632 color.b = std::stoi(tokens[3], 0, 0); | |
633 | |
634 if (tokens.size() == 5) | |
635 color.alpha = std::stoi(tokens[4], 0, 0); | |
636 } | |
637 else | |
638 { | |
639 return dmSyntaxError(info, | |
640 "Expected color values <value> or <red> <green> <blue> [<alpha>]"); | |
641 } | |
642 | |
643 model.color = color; | |
644 } | |
645 else | |
646 if (key == "translate" || key == "rotate" || key == "scale" || | |
647 key == "camera_pos" || key == "camera_at") | |
648 { | |
649 DMVertex vec; | |
650 | |
651 if (tokens.size() == 2) | |
652 { | |
653 vec.x = vec.y = vec.z = std::stof(tokens[1]); | |
654 } | |
655 else | |
656 if (tokens.size() == 4) | |
657 { | |
658 vec.x = std::stof(tokens[1]); | |
659 vec.y = std::stof(tokens[2]); | |
660 vec.z = std::stof(tokens[3]); | |
661 } | |
662 else | |
663 { | |
664 return dmSyntaxError(info, | |
665 "Expected vector value for '"+ key +"'"); | |
666 } | |
667 | |
668 if (key == "translate") | |
669 model.translate = vec; | |
670 else | |
671 if (key == "rotate") | |
672 model.rotate = vec; | |
673 else | |
674 if (key == "scale") | |
675 model.scale = vec; | |
676 else | |
677 if (key == "camera_pos") | |
678 camera.pos = vec; | |
679 else | |
680 if (key == "camera_at") | |
681 camera.lookAt = vec; | |
682 } | |
683 else | |
684 if (key == "light") | |
685 { | |
686 if (tokens.size() == 4) | |
687 { | |
688 DMVertex pos; | |
689 pos.x = std::stof(tokens[1]); | |
690 pos.y = std::stof(tokens[2]); | |
691 pos.z = std::stof(tokens[3]); | |
692 } | |
693 else | |
694 { | |
695 return dmSyntaxError(info, | |
696 "Expected light definition as <x> <y> <z> "); | |
697 } | |
698 } | |
699 else | |
700 { | |
701 return dmSyntaxError(info, | |
702 "Unexpected key '"+ key +"'"); | |
703 } | |
704 } | |
705 | |
706 model.printInfo(); | |
707 | |
708 return true; | |
709 } |