comparison dmscene.cpp @ 61:7b138613e2fc

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