1.3.49
 
Loading...
Searching...
No Matches
Context_fileIO.cpp
Go to the documentation of this file.
1
16#include "Context.h"
17
18using namespace helios;
19
20// Geometric tolerance for triangle area validation
21// Triangles with area below this threshold are considered degenerate and skipped
22static constexpr float MIN_TRIANGLE_AREA_THRESHOLD = 1e-8f;
23
24int XMLparser::parse_data_float(const pugi::xml_node &node_data, std::vector<float> &data) {
25 std::string data_str = node_data.child_value();
26 data.resize(0);
27 if (!data_str.empty()) {
28 std::istringstream data_stream(data_str);
29 std::string tmp_s;
30 float tmp_f;
31 while (data_stream >> tmp_s) {
32 if (parse_float(tmp_s, tmp_f)) {
33 data.push_back(tmp_f);
34 } else {
35 return 2;
36 }
37 }
38 } else {
39 return 1;
40 }
41
42 return 0;
43}
44
45int XMLparser::parse_data_double(const pugi::xml_node &node_data, std::vector<double> &data) {
46 std::string data_str = node_data.child_value();
47 data.resize(0);
48 if (!data_str.empty()) {
49 std::istringstream data_stream(data_str);
50 std::string tmp_s;
51 double tmp_f;
52 while (data_stream >> tmp_s) {
53 if (parse_double(tmp_s, tmp_f)) {
54 data.push_back(tmp_f);
55 } else {
56 return 2;
57 }
58 }
59 } else {
60 return 1;
61 }
62
63 return 0;
64}
65
66int XMLparser::parse_data_int(const pugi::xml_node &node_data, std::vector<int> &data) {
67 std::string data_str = node_data.child_value();
68 data.resize(0);
69 if (!data_str.empty()) {
70 std::istringstream data_stream(data_str);
71 std::string tmp_s;
72 int tmp_f;
73 while (data_stream >> tmp_s) {
74 if (parse_int(tmp_s, tmp_f)) {
75 data.push_back(tmp_f);
76 } else {
77 return 2;
78 }
79 }
80 } else {
81 return 1;
82 }
83
84 return 0;
85}
86
87int XMLparser::parse_data_uint(const pugi::xml_node &node_data, std::vector<uint> &data) {
88 std::string data_str = node_data.child_value();
89 data.resize(0);
90 if (!data_str.empty()) {
91 std::istringstream data_stream(data_str);
92 std::string tmp_s;
93 uint tmp_f;
94 while (data_stream >> tmp_s) {
95 if (parse_uint(tmp_s, tmp_f)) {
96 data.push_back(tmp_f);
97 } else {
98 return 2;
99 }
100 }
101 } else {
102 return 1;
103 }
104
105 return 0;
106}
107
108int XMLparser::parse_data_string(const pugi::xml_node &node_data, std::vector<std::string> &data) {
109 std::string data_str = node_data.child_value();
110 data.resize(0);
111 if (!data_str.empty()) {
112 std::istringstream data_stream(data_str);
113 std::string tmp_s;
114 while (data_stream >> tmp_s) {
115 data.push_back(tmp_s);
116 }
117 } else {
118 return 1;
119 }
120
121 return 0;
122}
123
124int XMLparser::parse_data_vec2(const pugi::xml_node &node_data, std::vector<vec2> &data) {
125 std::string data_str = node_data.child_value();
126 data.resize(0);
127 if (!data_str.empty()) {
128 std::istringstream data_stream(data_str);
129 std::vector<std::string> tmp_s(2);
130 vec2 tmp;
131 while (data_stream >> tmp_s[0]) {
132 data_stream >> tmp_s[1];
133 if (!parse_float(tmp_s[0], tmp.x) || !parse_float(tmp_s[1], tmp.y)) {
134 return 2;
135 } else {
136 data.push_back(tmp);
137 }
138 }
139 } else {
140 return 1;
141 }
142
143 return 0;
144}
145
146int XMLparser::parse_data_vec3(const pugi::xml_node &node_data, std::vector<vec3> &data) {
147 std::string data_str = node_data.child_value();
148 data.resize(0);
149 if (!data_str.empty()) {
150 std::istringstream data_stream(data_str);
151 std::vector<std::string> tmp_s(3);
152 vec3 tmp;
153 while (data_stream >> tmp_s[0]) {
154 data_stream >> tmp_s[1];
155 data_stream >> tmp_s[2];
156 if (!parse_float(tmp_s[0], tmp.x) || !parse_float(tmp_s[1], tmp.y) || !parse_float(tmp_s[2], tmp.z)) {
157 return 2;
158 } else {
159 data.push_back(tmp);
160 }
161 }
162 } else {
163 return 1;
164 }
165
166 return 0;
167}
168
169int XMLparser::parse_data_vec4(const pugi::xml_node &node_data, std::vector<vec4> &data) {
170 std::string data_str = node_data.child_value();
171 data.resize(0);
172 if (!data_str.empty()) {
173 std::istringstream data_stream(data_str);
174 std::vector<std::string> tmp_s(4);
175 vec4 tmp;
176 while (data_stream >> tmp_s[0]) {
177 data_stream >> tmp_s[1];
178 data_stream >> tmp_s[2];
179 data_stream >> tmp_s[3];
180 if (!parse_float(tmp_s[0], tmp.x) || !parse_float(tmp_s[1], tmp.y) || !parse_float(tmp_s[2], tmp.z) || !parse_float(tmp_s[3], tmp.w)) {
181 return 2;
182 } else {
183 data.push_back(tmp);
184 }
185 }
186 } else {
187 return 1;
188 }
189
190 return 0;
191}
192
193int XMLparser::parse_data_int2(const pugi::xml_node &node_data, std::vector<int2> &data) {
194 std::string data_str = node_data.child_value();
195 data.resize(0);
196 if (!data_str.empty()) {
197 std::istringstream data_stream(data_str);
198 std::vector<std::string> tmp_s(2);
199 int2 tmp;
200 while (data_stream >> tmp_s[0]) {
201 data_stream >> tmp_s[1];
202 if (!parse_int(tmp_s[0], tmp.x) || !parse_int(tmp_s[1], tmp.y)) {
203 return 2;
204 } else {
205 data.push_back(tmp);
206 }
207 }
208 } else {
209 return 1;
210 }
211
212 return 0;
213}
214
215int XMLparser::parse_data_int3(const pugi::xml_node &node_data, std::vector<int3> &data) {
216 std::string data_str = node_data.child_value();
217 data.resize(0);
218 if (!data_str.empty()) {
219 std::istringstream data_stream(data_str);
220 std::vector<std::string> tmp_s(3);
221 int3 tmp;
222 while (data_stream >> tmp_s[0]) {
223 data_stream >> tmp_s[1];
224 data_stream >> tmp_s[2];
225 if (!parse_int(tmp_s[0], tmp.x) || !parse_int(tmp_s[1], tmp.y) || !parse_int(tmp_s[2], tmp.z)) {
226 return 2;
227 } else {
228 data.push_back(tmp);
229 }
230 }
231 } else {
232 return 1;
233 }
234
235 return 0;
236}
237
238int XMLparser::parse_data_int4(const pugi::xml_node &node_data, std::vector<int4> &data) {
239 std::string data_str = node_data.child_value();
240 data.resize(0);
241 if (!data_str.empty()) {
242 std::istringstream data_stream(data_str);
243 std::vector<std::string> tmp_s(4);
244 int4 tmp;
245 while (data_stream >> tmp_s[0]) {
246 data_stream >> tmp_s[1];
247 data_stream >> tmp_s[2];
248 data_stream >> tmp_s[3];
249 if (!parse_int(tmp_s[0], tmp.x) || !parse_int(tmp_s[1], tmp.y) || !parse_int(tmp_s[2], tmp.z) || !parse_int(tmp_s[3], tmp.w)) {
250 return 2;
251 } else {
252 data.push_back(tmp);
253 }
254 }
255 } else {
256 return 1;
257 }
258
259 return 0;
260}
261
262int XMLparser::parse_objID(const pugi::xml_node &node, uint &objID) {
263 pugi::xml_node objID_node = node.child("objID");
264 std::string oid = trim_whitespace(objID_node.child_value());
265 objID = 0;
266 if (!oid.empty()) {
267 if (!parse_uint(oid, objID)) {
268 return 2;
269 }
270 } else {
271 return 1;
272 }
273
274 return 0;
275}
276
277int XMLparser::parse_transform(const pugi::xml_node &node, float (&transform)[16]) {
278 pugi::xml_node transform_node = node.child("transform");
279
280 std::string transform_str = transform_node.child_value();
281 if (transform_str.empty()) {
282 makeIdentityMatrix(transform);
283 return 1;
284 } else {
285 std::istringstream stream(transform_str);
286 std::string tmp_s;
287 float tmp;
288 int i = 0;
289 while (stream >> tmp_s) {
290 if (parse_float(tmp_s, tmp)) {
291 transform[i] = tmp;
292 i++;
293 } else {
294 return 2;
295 }
296 }
297 if (i != 16) {
298 return 3;
299 }
300 }
301 return 0;
302}
303
304int XMLparser::parse_texture(const pugi::xml_node &node, std::string &texture) {
305 pugi::xml_node texture_node = node.child("texture");
306 std::string texfile = trim_whitespace(texture_node.child_value());
307 if (texfile.empty()) {
308 texture = "none";
309 return 1;
310 } else {
311 texture = texfile;
312 return 0;
313 }
314}
315
316int XMLparser::parse_textureUV(const pugi::xml_node &node, std::vector<vec2> &uvs) {
317 pugi::xml_node uv_node = node.child("textureUV");
318 std::string texUV = uv_node.child_value();
319 if (!texUV.empty()) {
320 std::istringstream uv_stream(texUV);
321 std::vector<std::string> tmp_s(2);
322 vec2 tmp;
323 while (uv_stream >> tmp_s[0]) {
324 uv_stream >> tmp_s[1];
325 if (!parse_float(tmp_s[0], tmp.x) || !parse_float(tmp_s[1], tmp.y)) {
326 return 2;
327 } else {
328 uvs.push_back(tmp);
329 }
330 }
331 } else {
332 return 1;
333 }
334
335 return 0;
336}
337
338int XMLparser::parse_solid_fraction(const pugi::xml_node &node, float &solid_fraction) {
339 pugi::xml_node sfrac_node = node.child("solid_fraction");
340 std::string sfrac = trim_whitespace(sfrac_node.child_value());
341 if (!sfrac.empty()) {
342 if (!parse_float(sfrac, solid_fraction)) {
343 return 2;
344 }
345 } else {
346 return 1;
347 }
348 return 0;
349}
350
351int XMLparser::parse_vertices(const pugi::xml_node &node, std::vector<float> &vertices) {
352 vertices.resize(12);
353
354 pugi::xml_node vertices_node = node.child("vertices");
355
356 std::string vertices_str = vertices_node.child_value();
357 if (!vertices_str.empty()) {
358 std::istringstream stream(vertices_str);
359 std::string tmp_s;
360 float tmp;
361 int i = 0;
362 while (stream >> tmp_s) {
363 if (i > 11) {
364 return 3;
365 } else if (parse_float(tmp_s, tmp)) {
366 vertices.at(i) = tmp;
367 i++;
368 } else {
369 return 2;
370 }
371 }
372 vertices.resize(i);
373 } else {
374 return 1;
375 }
376
377 return 0;
378}
379
380int XMLparser::parse_subdivisions(const pugi::xml_node &node, uint &subdivisions) {
381 pugi::xml_node subdiv_node = node.child("subdivisions");
382 std::string subdiv = trim_whitespace(subdiv_node.child_value());
383 if (!subdiv.empty()) {
384 if (!parse_uint(subdiv, subdivisions)) {
385 return 2;
386 }
387 } else {
388 return 1;
389 }
390 return 0;
391}
392
393int XMLparser::parse_subdivisions(const pugi::xml_node &node, int2 &subdivisions) {
394 pugi::xml_node subdiv_node = node.child("subdivisions");
395 std::string subdiv = trim_whitespace(subdiv_node.child_value());
396 if (!subdiv.empty()) {
397 std::istringstream data_stream(subdiv);
398 std::vector<std::string> tmp_s(2);
399 data_stream >> tmp_s[0];
400 data_stream >> tmp_s[1];
401 if (!parse_int(tmp_s[0], subdivisions.x) || !parse_int(tmp_s[1], subdivisions.y)) {
402 return 2;
403 }
404 } else {
405 return 1;
406 }
407 return 0;
408}
409
410int XMLparser::parse_subdivisions(const pugi::xml_node &node, int3 &subdivisions) {
411 pugi::xml_node subdiv_node = node.child("subdivisions");
412 std::string subdiv = trim_whitespace(subdiv_node.child_value());
413 if (!subdiv.empty()) {
414 std::istringstream data_stream(subdiv);
415 std::vector<std::string> tmp_s(3);
416 data_stream >> tmp_s[0];
417 data_stream >> tmp_s[1];
418 data_stream >> tmp_s[2];
419 if (!parse_int(tmp_s[0], subdivisions.x) || !parse_int(tmp_s[1], subdivisions.y) || !parse_int(tmp_s[2], subdivisions.z)) {
420 return 2;
421 }
422 } else {
423 return 1;
424 }
425 return 0;
426}
427
428int XMLparser::parse_nodes(const pugi::xml_node &node, std::vector<vec3> &nodes) {
429 pugi::xml_node node_data = node.child("nodes");
430 std::string data_str = node_data.child_value();
431 nodes.resize(0);
432 if (!data_str.empty()) {
433 std::istringstream data_stream(data_str);
434 std::vector<std::string> tmp_s(3);
435 vec3 tmp;
436 while (data_stream >> tmp_s[0]) {
437 data_stream >> tmp_s[1];
438 data_stream >> tmp_s[2];
439 if (!parse_float(tmp_s[0], tmp.x) || !parse_float(tmp_s[1], tmp.y) || !parse_float(tmp_s[2], tmp.z)) {
440 return 2;
441 } else {
442 nodes.push_back(tmp);
443 }
444 }
445 } else {
446 return 1;
447 }
448
449 return 0;
450}
451
452int XMLparser::parse_radius(const pugi::xml_node &node, std::vector<float> &radius) {
453 pugi::xml_node node_data = node.child("radius");
454 std::string data_str = node_data.child_value();
455 radius.resize(0);
456 if (!data_str.empty()) {
457 std::istringstream data_stream(data_str);
458 std::string tmp_s;
459 float tmp_f;
460 while (data_stream >> tmp_s) {
461 if (parse_float(tmp_s, tmp_f)) {
462 radius.push_back(tmp_f);
463 } else {
464 return 2;
465 }
466 }
467 } else {
468 return 1;
469 }
470
471 return 0;
472}
473
474void Context::loadPData(pugi::xml_node p, uint UUID) {
475 for (pugi::xml_node data = p.child("data_int"); data; data = data.next_sibling("data_int")) {
476 const char *label = data.attribute("label").value();
477
478 std::vector<int> datav;
479 if (XMLparser::parse_data_int(data, datav) != 0 || datav.empty()) {
480 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_int> with label " + std::string(label) + " contained invalid data.");
481 }
482
483 if (datav.size() == 1) {
484 setPrimitiveData(UUID, label, datav.front());
485 } else if (datav.size() > 1) {
486 setPrimitiveData(UUID, label, datav);
487 }
488 }
489
490 for (pugi::xml_node data = p.child("data_uint"); data; data = data.next_sibling("data_uint")) {
491 const char *label = data.attribute("label").value();
492
493 std::vector<uint> datav;
494 if (XMLparser::parse_data_uint(data, datav) != 0 || datav.empty()) {
495 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_uint> with label " + std::string(label) + " contained invalid data.");
496 }
497
498 if (datav.size() == 1) {
499 setPrimitiveData(UUID, label, datav.front());
500 } else if (datav.size() > 1) {
501 setPrimitiveData(UUID, label, datav);
502 }
503 }
504
505 for (pugi::xml_node data = p.child("data_float"); data; data = data.next_sibling("data_float")) {
506 const char *label = data.attribute("label").value();
507
508 std::vector<float> datav;
509 if (XMLparser::parse_data_float(data, datav) != 0 || datav.empty()) {
510 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_float> with label " + std::string(label) + " contained invalid data.");
511 }
512
513 if (datav.size() == 1) {
514 setPrimitiveData(UUID, label, datav.front());
515 } else if (datav.size() > 1) {
516 setPrimitiveData(UUID, label, datav);
517 }
518 }
519
520 for (pugi::xml_node data = p.child("data_double"); data; data = data.next_sibling("data_double")) {
521 const char *label = data.attribute("label").value();
522
523 std::vector<double> datav;
524 if (XMLparser::parse_data_double(data, datav) != 0 || datav.empty()) {
525 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_double> with label " + std::string(label) + " contained invalid data.");
526 }
527
528 if (datav.size() == 1) {
529 setPrimitiveData(UUID, label, datav.front());
530 } else if (datav.size() > 1) {
531 setPrimitiveData(UUID, label, datav);
532 }
533 }
534
535 for (pugi::xml_node data = p.child("data_vec2"); data; data = data.next_sibling("data_vec2")) {
536 const char *label = data.attribute("label").value();
537
538 std::vector<vec2> datav;
539 if (XMLparser::parse_data_vec2(data, datav) != 0 || datav.empty()) {
540 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_vec2> with label " + std::string(label) + " contained invalid data.");
541 }
542
543 if (datav.size() == 1) {
544 setPrimitiveData(UUID, label, datav.front());
545 } else if (datav.size() > 1) {
546 setPrimitiveData(UUID, label, datav);
547 }
548 }
549
550 for (pugi::xml_node data = p.child("data_vec3"); data; data = data.next_sibling("data_vec3")) {
551 const char *label = data.attribute("label").value();
552
553 std::vector<vec3> datav;
554 if (XMLparser::parse_data_vec3(data, datav) != 0 || datav.empty()) {
555 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_vec3> with label " + std::string(label) + " contained invalid data.");
556 }
557
558 if (datav.size() == 1) {
559 setPrimitiveData(UUID, label, datav.front());
560 } else if (datav.size() > 1) {
561 setPrimitiveData(UUID, label, datav);
562 }
563 }
564
565 for (pugi::xml_node data = p.child("data_vec4"); data; data = data.next_sibling("data_vec4")) {
566 const char *label = data.attribute("label").value();
567
568 std::vector<vec4> datav;
569 if (XMLparser::parse_data_vec4(data, datav) != 0 || datav.empty()) {
570 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_vec4> with label " + std::string(label) + " contained invalid data.");
571 }
572
573 if (datav.size() == 1) {
574 setPrimitiveData(UUID, label, datav.front());
575 } else if (datav.size() > 1) {
576 setPrimitiveData(UUID, label, datav);
577 }
578 }
579
580 for (pugi::xml_node data = p.child("data_int2"); data; data = data.next_sibling("data_int2")) {
581 const char *label = data.attribute("label").value();
582
583 std::vector<int2> datav;
584 if (XMLparser::parse_data_int2(data, datav) != 0 || datav.empty()) {
585 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_int2> with label " + std::string(label) + " contained invalid data.");
586 }
587
588 if (datav.size() == 1) {
589 setPrimitiveData(UUID, label, datav.front());
590 } else if (datav.size() > 1) {
591 setPrimitiveData(UUID, label, datav);
592 }
593 }
594
595 for (pugi::xml_node data = p.child("data_int3"); data; data = data.next_sibling("data_int3")) {
596 const char *label = data.attribute("label").value();
597
598 std::vector<int3> datav;
599 if (XMLparser::parse_data_int3(data, datav) != 0 || datav.empty()) {
600 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_int3> with label " + std::string(label) + " contained invalid data.");
601 }
602
603 if (datav.size() == 1) {
604 setPrimitiveData(UUID, label, datav.front());
605 } else if (datav.size() > 1) {
606 setPrimitiveData(UUID, label, datav);
607 }
608 }
609
610 for (pugi::xml_node data = p.child("data_int4"); data; data = data.next_sibling("data_int4")) {
611 const char *label = data.attribute("label").value();
612
613 std::vector<int4> datav;
614 if (XMLparser::parse_data_int4(data, datav) != 0 || datav.empty()) {
615 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_int4> with label " + std::string(label) + " contained invalid data.");
616 }
617
618 if (datav.size() == 1) {
619 setPrimitiveData(UUID, label, datav.front());
620 } else if (datav.size() > 1) {
621 setPrimitiveData(UUID, label, datav);
622 }
623 }
624
625 for (pugi::xml_node data = p.child("data_string"); data; data = data.next_sibling("data_string")) {
626 const char *label = data.attribute("label").value();
627
628 std::vector<std::string> datav;
629 if (XMLparser::parse_data_string(data, datav) != 0 || datav.empty()) {
630 helios_runtime_error("ERROR (Context::loadXML): Primitive data tag <data_string> with label " + std::string(label) + " contained invalid data.");
631 }
632
633 if (datav.size() == 1) {
634 setPrimitiveData(UUID, label, datav.front());
635 } else if (datav.size() > 1) {
636 setPrimitiveData(UUID, label, datav);
637 }
638 }
639}
640
641void Context::loadOData(pugi::xml_node p, uint ID) {
642 assert(doesObjectExist(ID));
643
644 for (pugi::xml_node data = p.child("data_int"); data; data = data.next_sibling("data_int")) {
645 const char *label = data.attribute("label").value();
646
647 std::vector<int> datav;
648 if (XMLparser::parse_data_int(data, datav) != 0 || datav.empty()) {
649 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_int> with label " + std::string(label) + " contained invalid data.");
650 }
651
652 if (datav.size() == 1) {
653 setObjectData(ID, label, datav.front());
654 } else if (datav.size() > 1) {
655 setObjectData(ID, label, datav);
656 }
657 }
658
659 for (pugi::xml_node data = p.child("data_uint"); data; data = data.next_sibling("data_uint")) {
660 const char *label = data.attribute("label").value();
661
662 std::vector<uint> datav;
663 if (XMLparser::parse_data_uint(data, datav) != 0 || datav.empty()) {
664 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_uint> with label " + std::string(label) + " contained invalid data.");
665 }
666
667 if (datav.size() == 1) {
668 setObjectData(ID, label, datav.front());
669 } else if (datav.size() > 1) {
670 setObjectData(ID, label, datav);
671 }
672 }
673
674 for (pugi::xml_node data = p.child("data_float"); data; data = data.next_sibling("data_float")) {
675 const char *label = data.attribute("label").value();
676
677 std::vector<float> datav;
678 if (XMLparser::parse_data_float(data, datav) != 0 || datav.empty()) {
679 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_float> with label " + std::string(label) + " contained invalid data.");
680 }
681
682 if (datav.size() == 1) {
683 setObjectData(ID, label, datav.front());
684 } else if (datav.size() > 1) {
685 setObjectData(ID, label, datav);
686 }
687 }
688
689 for (pugi::xml_node data = p.child("data_double"); data; data = data.next_sibling("data_double")) {
690 const char *label = data.attribute("label").value();
691
692 std::vector<double> datav;
693 if (XMLparser::parse_data_double(data, datav) != 0 || datav.empty()) {
694 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_double> with label " + std::string(label) + " contained invalid data.");
695 }
696
697 if (datav.size() == 1) {
698 setObjectData(ID, label, datav.front());
699 } else if (datav.size() > 1) {
700 setObjectData(ID, label, datav);
701 }
702 }
703
704 for (pugi::xml_node data = p.child("data_vec2"); data; data = data.next_sibling("data_vec2")) {
705 const char *label = data.attribute("label").value();
706
707 std::vector<vec2> datav;
708 if (XMLparser::parse_data_vec2(data, datav) != 0 || datav.empty()) {
709 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_vec2> with label " + std::string(label) + " contained invalid data.");
710 }
711
712 if (datav.size() == 1) {
713 setObjectData(ID, label, datav.front());
714 } else if (datav.size() > 1) {
715 setObjectData(ID, label, datav);
716 }
717 }
718
719 for (pugi::xml_node data = p.child("data_vec3"); data; data = data.next_sibling("data_vec3")) {
720 const char *label = data.attribute("label").value();
721
722 std::vector<vec3> datav;
723 if (XMLparser::parse_data_vec3(data, datav) != 0 || datav.empty()) {
724 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_vec3> with label " + std::string(label) + " contained invalid data.");
725 }
726
727 if (datav.size() == 1) {
728 setObjectData(ID, label, datav.front());
729 } else if (datav.size() > 1) {
730 setObjectData(ID, label, datav);
731 }
732 }
733
734 for (pugi::xml_node data = p.child("data_vec4"); data; data = data.next_sibling("data_vec4")) {
735 const char *label = data.attribute("label").value();
736
737 std::vector<vec4> datav;
738 if (XMLparser::parse_data_vec4(data, datav) != 0 || datav.empty()) {
739 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_vec4> with label " + std::string(label) + " contained invalid data.");
740 }
741
742 if (datav.size() == 1) {
743 setObjectData(ID, label, datav.front());
744 } else if (datav.size() > 1) {
745 setObjectData(ID, label, datav);
746 }
747 }
748
749 for (pugi::xml_node data = p.child("data_int2"); data; data = data.next_sibling("data_int2")) {
750 const char *label = data.attribute("label").value();
751
752 std::vector<int2> datav;
753 if (XMLparser::parse_data_int2(data, datav) != 0 || datav.empty()) {
754 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_int2> with label " + std::string(label) + " contained invalid data.");
755 }
756
757 if (datav.size() == 1) {
758 setObjectData(ID, label, datav.front());
759 } else if (datav.size() > 1) {
760 setObjectData(ID, label, datav);
761 }
762 }
763
764 for (pugi::xml_node data = p.child("data_int3"); data; data = data.next_sibling("data_int3")) {
765 const char *label = data.attribute("label").value();
766
767 std::vector<int3> datav;
768 if (XMLparser::parse_data_int3(data, datav) != 0 || datav.empty()) {
769 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_int3> with label " + std::string(label) + " contained invalid data.");
770 }
771
772 if (datav.size() == 1) {
773 setObjectData(ID, label, datav.front());
774 } else if (datav.size() > 1) {
775 setObjectData(ID, label, datav);
776 }
777 }
778
779 for (pugi::xml_node data = p.child("data_int4"); data; data = data.next_sibling("data_int4")) {
780 const char *label = data.attribute("label").value();
781
782 std::vector<int4> datav;
783 if (XMLparser::parse_data_int4(data, datav) != 0 || datav.empty()) {
784 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_int4> with label " + std::string(label) + " contained invalid data.");
785 }
786
787 if (datav.size() == 1) {
788 setObjectData(ID, label, datav.front());
789 } else if (datav.size() > 1) {
790 setObjectData(ID, label, datav);
791 }
792 }
793
794 for (pugi::xml_node data = p.child("data_string"); data; data = data.next_sibling("data_string")) {
795 const char *label = data.attribute("label").value();
796
797 std::vector<std::string> datav;
798 if (XMLparser::parse_data_string(data, datav) != 0 || datav.empty()) {
799 helios_runtime_error("ERROR (Context::loadXML): Object data tag <data_string> with label " + std::string(label) + " contained invalid data.");
800 }
801
802 if (datav.size() == 1) {
803 setObjectData(ID, label, datav.front());
804 } else if (datav.size() > 1) {
805 setObjectData(ID, label, datav);
806 }
807 }
808}
809
810void Context::loadOsubPData(pugi::xml_node p, uint ID) {
811 assert(doesObjectExist(ID));
812
813 std::vector<uint> prim_UUIDs = getObjectPointer(ID)->getPrimitiveUUIDs();
814
815 int u;
816
817 for (pugi::xml_node prim_data = p.child("primitive_data_int"); prim_data; prim_data = prim_data.next_sibling("primitive_data_int")) {
818 const char *label = prim_data.attribute("label").value();
819
820 u = 0;
821 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
822 if (u >= prim_UUIDs.size()) {
823 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
824 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
825 break;
826 }
827
828 std::vector<int> datav;
829 if (XMLparser::parse_data_int(data, datav) != 0 || datav.empty()) {
830 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int> with label " + std::string(label) + " contained invalid data.");
831 }
832
833 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
834 if (datav.size() == 1) {
835 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
836 } else if (datav.size() > 1) {
837 setPrimitiveData(prim_UUIDs.at(u), label, datav);
838 }
839 }
840 u++;
841 }
842 }
843
844 for (pugi::xml_node prim_data = p.child("primitive_data_uint"); prim_data; prim_data = prim_data.next_sibling("primitive_data_uint")) {
845 const char *label = prim_data.attribute("label").value();
846
847 u = 0;
848 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
849 if (u >= prim_UUIDs.size()) {
850 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
851 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
852 break;
853 }
854
855 std::vector<uint> datav;
856 if (XMLparser::parse_data_uint(data, datav) != 0 || datav.empty()) {
857 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_uint> with label " + std::string(label) + " contained invalid data.");
858 }
859
860 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
861 if (datav.size() == 1) {
862 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
863 } else if (datav.size() > 1) {
864 setPrimitiveData(prim_UUIDs.at(u), label, datav);
865 }
866 }
867 u++;
868 }
869 }
870
871 for (pugi::xml_node prim_data = p.child("primitive_data_float"); prim_data; prim_data = prim_data.next_sibling("primitive_data_float")) {
872 const char *label = prim_data.attribute("label").value();
873
874 u = 0;
875 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
876 if (u >= prim_UUIDs.size()) {
877 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
878 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
879 break;
880 }
881
882 std::vector<float> datav;
883 if (XMLparser::parse_data_float(data, datav) != 0 || datav.empty()) {
884 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_float> with label " + std::string(label) + " contained invalid data.");
885 }
886
887 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
888 if (datav.size() == 1) {
889 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
890 } else if (datav.size() > 1) {
891 setPrimitiveData(prim_UUIDs.at(u), label, datav);
892 }
893 }
894 u++;
895 }
896 }
897
898 for (pugi::xml_node prim_data = p.child("primitive_data_double"); prim_data; prim_data = prim_data.next_sibling("primitive_data_double")) {
899 const char *label = prim_data.attribute("label").value();
900
901 u = 0;
902 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
903 if (u >= prim_UUIDs.size()) {
904 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
905 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
906 break;
907 }
908
909 std::vector<double> datav;
910 if (XMLparser::parse_data_double(data, datav) != 0 || datav.empty()) {
911 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_double> with label " + std::string(label) + " contained invalid data.");
912 }
913
914 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
915 if (datav.size() == 1) {
916 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
917 } else if (datav.size() > 1) {
918 setPrimitiveData(prim_UUIDs.at(u), label, datav);
919 }
920 }
921 u++;
922 }
923 }
924
925 for (pugi::xml_node prim_data = p.child("primitive_data_vec2"); prim_data; prim_data = prim_data.next_sibling("primitive_data_vec2")) {
926 const char *label = prim_data.attribute("label").value();
927
928 u = 0;
929 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
930 if (u >= prim_UUIDs.size()) {
931 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
932 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
933 break;
934 }
935
936 std::vector<vec2> datav;
937 if (XMLparser::parse_data_vec2(data, datav) != 0 || datav.empty()) {
938 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec2> with label " + std::string(label) + " contained invalid data.");
939 }
940
941 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
942 if (datav.size() == 1) {
943 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
944 } else if (datav.size() > 1) {
945 setPrimitiveData(prim_UUIDs.at(u), label, datav);
946 }
947 }
948 u++;
949 }
950 }
951
952 for (pugi::xml_node prim_data = p.child("primitive_data_vec3"); prim_data; prim_data = prim_data.next_sibling("primitive_data_vec3")) {
953 const char *label = prim_data.attribute("label").value();
954
955 u = 0;
956 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
957 if (u >= prim_UUIDs.size()) {
958 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
959 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
960 break;
961 }
962
963 std::vector<vec3> datav;
964 if (XMLparser::parse_data_vec3(data, datav) != 0 || datav.empty()) {
965 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec3> with label " + std::string(label) + " contained invalid data.");
966 }
967
968 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
969 if (datav.size() == 1) {
970 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
971 } else if (datav.size() > 1) {
972 setPrimitiveData(prim_UUIDs.at(u), label, datav);
973 }
974 }
975 u++;
976 }
977 }
978
979 for (pugi::xml_node prim_data = p.child("primitive_data_vec4"); prim_data; prim_data = prim_data.next_sibling("primitive_data_vec4")) {
980 const char *label = prim_data.attribute("label").value();
981
982 u = 0;
983 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
984 if (u >= prim_UUIDs.size()) {
985 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
986 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
987 break;
988 }
989
990 std::vector<vec4> datav;
991 if (XMLparser::parse_data_vec4(data, datav) != 0 || datav.empty()) {
992 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec4> with label " + std::string(label) + " contained invalid data.");
993 }
994
995 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
996 if (datav.size() == 1) {
997 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
998 } else if (datav.size() > 1) {
999 setPrimitiveData(prim_UUIDs.at(u), label, datav);
1000 }
1001 }
1002 u++;
1003 }
1004 }
1005
1006 for (pugi::xml_node prim_data = p.child("primitive_data_int2"); prim_data; prim_data = prim_data.next_sibling("primitive_data_int2")) {
1007 const char *label = prim_data.attribute("label").value();
1008
1009 u = 0;
1010 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
1011 if (u >= prim_UUIDs.size()) {
1012 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1013 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1014 break;
1015 }
1016
1017 std::vector<int2> datav;
1018 if (XMLparser::parse_data_int2(data, datav) != 0 || datav.empty()) {
1019 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int2> with label " + std::string(label) + " contained invalid data.");
1020 }
1021
1022 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
1023 if (datav.size() == 1) {
1024 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
1025 } else if (datav.size() > 1) {
1026 setPrimitiveData(prim_UUIDs.at(u), label, datav);
1027 }
1028 }
1029 u++;
1030 }
1031 }
1032
1033 for (pugi::xml_node prim_data = p.child("primitive_data_int3"); prim_data; prim_data = prim_data.next_sibling("primitive_data_int3")) {
1034 const char *label = prim_data.attribute("label").value();
1035
1036 u = 0;
1037 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
1038 if (u >= prim_UUIDs.size()) {
1039 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1040 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1041 break;
1042 }
1043
1044 std::vector<int3> datav;
1045 if (XMLparser::parse_data_int3(data, datav) != 0 || datav.empty()) {
1046 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int3> with label " + std::string(label) + " contained invalid data.");
1047 }
1048
1049 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
1050 if (datav.size() == 1) {
1051 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
1052 } else if (datav.size() > 1) {
1053 setPrimitiveData(prim_UUIDs.at(u), label, datav);
1054 }
1055 }
1056 u++;
1057 }
1058 }
1059
1060 for (pugi::xml_node prim_data = p.child("primitive_data_int4"); prim_data; prim_data = prim_data.next_sibling("primitive_data_int4")) {
1061 const char *label = prim_data.attribute("label").value();
1062
1063 u = 0;
1064 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
1065 if (u >= prim_UUIDs.size()) {
1066 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1067 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1068 break;
1069 }
1070
1071 std::vector<int4> datav;
1072 if (XMLparser::parse_data_int4(data, datav) != 0 || datav.empty()) {
1073 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int4> with label " + std::string(label) + " contained invalid data.");
1074 }
1075
1076 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
1077 if (datav.size() == 1) {
1078 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
1079 } else if (datav.size() > 1) {
1080 setPrimitiveData(prim_UUIDs.at(u), label, datav);
1081 }
1082 }
1083 u++;
1084 }
1085 }
1086
1087 for (pugi::xml_node prim_data = p.child("primitive_data_string"); prim_data; prim_data = prim_data.next_sibling("primitive_data_string")) {
1088 const char *label = prim_data.attribute("label").value();
1089
1090 u = 0;
1091 for (pugi::xml_node data = prim_data.child("data"); data; data = data.next_sibling("data")) {
1092 if (u >= prim_UUIDs.size()) {
1093 std::cerr << "WARNING (Context::loadXML): There was a problem with reading object primitive data \"" << label
1094 << "\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values." << std::endl;
1095 break;
1096 }
1097
1098 std::vector<std::string> datav;
1099 if (XMLparser::parse_data_string(data, datav) != 0 || datav.empty()) {
1100 helios_runtime_error("ERROR (Context::loadXML): Object member primitive data tag <primitive_data_string> with label " + std::string(label) + " contained invalid data.");
1101 }
1102
1103 if (doesPrimitiveExist(prim_UUIDs.at(u))) {
1104 if (datav.size() == 1) {
1105 setPrimitiveData(prim_UUIDs.at(u), label, datav.front());
1106 } else if (datav.size() > 1) {
1107 setPrimitiveData(prim_UUIDs.at(u), label, datav);
1108 }
1109 }
1110 u++;
1111 }
1112 }
1113}
1114
1115std::vector<uint> Context::loadXML(const char *filename, bool quiet) {
1116 if (!quiet) {
1117 std::cout << "Loading XML file: " << filename << "..." << std::flush;
1118 }
1119
1120 std::string fn = filename;
1121 std::string ext = getFileExtension(filename);
1122 if (ext != ".xml" && ext != ".XML") {
1123 helios_runtime_error("failed.\n File " + fn + " is not XML format.");
1124 }
1125
1126 // Resolve file path using unified resolution
1127 std::filesystem::path resolved_path = resolveFilePath(filename);
1128 std::string resolved_filename = resolved_path.string();
1129
1130 XMLfiles.emplace_back(resolved_filename);
1131
1132 uint ID;
1133 std::vector<uint> UUID;
1134
1135 // Using "pugixml" parser. See pugixml.org
1136 pugi::xml_document xmldoc;
1137
1138 // load file
1139 pugi::xml_parse_result load_result = xmldoc.load_file(resolved_filename.c_str());
1140
1141 // error checking
1142 if (!load_result) {
1143 helios_runtime_error("failed.\n XML [" + std::string(filename) + "] parsed with errors, attr value: [" + xmldoc.child("node").attribute("attr").value() + "]\nError description: " + load_result.description() +
1144 "\nError offset: " + std::to_string(load_result.offset) + " (error at [..." + (filename + load_result.offset) + "]\n");
1145 }
1146
1147 pugi::xml_node helios = xmldoc.child("helios");
1148
1149 if (helios.empty()) {
1150 if (!quiet) {
1151 std::cout << "failed." << std::endl;
1152 }
1153 helios_runtime_error("ERROR (Context::loadXML): XML file must have tag '<helios> ... </helios>' bounding all other tags.");
1154 }
1155
1156 // if primitives are added that belong to an object, store their UUIDs here so that we can make sure their UUIDs are consistent
1157 std::map<uint, std::vector<uint>> object_prim_UUIDs;
1158
1159 //-------------- TIME/DATE ---------------//
1160
1161 for (pugi::xml_node p = helios.child("date"); p; p = p.next_sibling("date")) {
1162 pugi::xml_node year_node = p.child("year");
1163 const char *year_str = year_node.child_value();
1164 int year;
1165 if (!parse_int(year_str, year)) {
1166 helios_runtime_error("ERROR (Context::loadXML): Year given in 'date' block must be an integer value.");
1167 }
1168
1169 pugi::xml_node month_node = p.child("month");
1170 const char *month_str = month_node.child_value();
1171 int month;
1172 if (!parse_int(month_str, month)) {
1173 helios_runtime_error("ERROR (Context::loadXML): Month given in 'date' block must be an integer value.");
1174 }
1175
1176 pugi::xml_node day_node = p.child("day");
1177 const char *day_str = day_node.child_value();
1178 int day;
1179 if (!parse_int(day_str, day)) {
1180 helios_runtime_error("ERROR (Context::loadXML): Day given in 'date' block must be an integer value.");
1181 }
1182
1183 setDate(day, month, year);
1184 }
1185
1186 for (pugi::xml_node p = helios.child("time"); p; p = p.next_sibling("time")) {
1187 pugi::xml_node hour_node = p.child("hour");
1188 const char *hour_str = hour_node.child_value();
1189 int hour;
1190 if (!parse_int(hour_str, hour)) {
1191 helios_runtime_error("ERROR (Context::loadXML): Hour given in 'time' block must be an integer value.");
1192 }
1193
1194 pugi::xml_node minute_node = p.child("minute");
1195 const char *minute_str = minute_node.child_value();
1196 int minute;
1197 if (!parse_int(minute_str, minute)) {
1198 helios_runtime_error("ERROR (Context::loadXML): Minute given in 'time' block must be an integer value.");
1199 }
1200
1201 pugi::xml_node second_node = p.child("second");
1202 const char *second_str = second_node.child_value();
1203 int second;
1204 if (!parse_int(second_str, second)) {
1205 helios_runtime_error("ERROR (Context::loadXML): Second given in 'time' block must be an integer value.");
1206 }
1207
1208 setTime(second, minute, hour);
1209 }
1210
1211 //-------------- PATCHES ---------------//
1212 for (pugi::xml_node p = helios.child("patch"); p; p = p.next_sibling("patch")) {
1213 // * Patch Object ID * //
1214 uint objID = 0;
1215 if (XMLparser::parse_objID(p, objID) > 1) {
1216 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'patch' block must be a non-negative integer value.");
1217 }
1218
1219 // * Patch Transformation Matrix * //
1220 float transform[16];
1221 int result = XMLparser::parse_transform(p, transform);
1222 if (result == 3) {
1223 helios_runtime_error("ERROR (Context::loadXML): Patch <transform> node contains less than 16 data values.");
1224 } else if (result == 2) {
1225 helios_runtime_error("ERROR (Context::loadXML): Patch <transform> node contains invalid data.");
1226 }
1227
1228 // * Patch Texture * //
1229 std::string texture_file;
1230 XMLparser::parse_texture(p, texture_file);
1231
1232 // * Patch Texture (u,v) Coordinates * //
1233 std::vector<vec2> uv;
1234 if (XMLparser::parse_textureUV(p, uv) == 2) {
1235 helios_runtime_error("ERROR (Context::loadXML): (u,v) coordinates given in 'patch' block contain invalid data.");
1236 }
1237
1238 // * Patch Solid Fraction * //
1239 float solid_fraction = -1;
1240 if (XMLparser::parse_solid_fraction(p, solid_fraction) == 2) {
1241 helios_runtime_error("ERROR (Context::loadXML): Solid fraction given in 'patch' block contains invalid data.");
1242 }
1243
1244 // * Patch Diffuse Colors * //
1245 RGBAcolor color;
1246 pugi::xml_node color_node = p.child("color");
1247
1248 const char *color_str = color_node.child_value();
1249 if (strlen(color_str) == 0) {
1250 color = make_RGBAcolor(0, 0, 0, 1); // assume default color of black
1251 } else {
1252 color = string2RGBcolor(color_str);
1253 }
1254
1255 // * Add the Patch * //
1256 if (strcmp(texture_file.c_str(), "none") == 0) { // no texture file was given
1257 ID = addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), make_SphericalCoord(0, 0), color);
1258 } else { // has a texture file
1259 std::string texture_file_copy;
1260 if (solid_fraction < 1.f && solid_fraction >= 0.f) { // solid fraction was given in the XML, and is not equal to 1.0
1261 texture_file_copy = texture_file;
1262 texture_file = "lib/images/solid.jpg"; // load dummy solid texture to avoid re-calculating the solid fraction
1263 }
1264 if (uv.empty()) { // custom (u,v) coordinates were not given
1265 ID = addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), make_SphericalCoord(0, 0), texture_file.c_str());
1266 } else {
1267 ID = addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), make_SphericalCoord(0, 0), texture_file.c_str(), 0.5 * (uv.at(2) + uv.at(0)), uv.at(2) - uv.at(0));
1268 }
1269 if (solid_fraction < 1.f && solid_fraction >= 0.f) { // replace dummy texture and set the solid fraction
1270 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1271 addTexture(texture_file_copy.c_str());
1272 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1273 }
1274 }
1275 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1276
1277 if (objID > 0) {
1278 object_prim_UUIDs[objID].push_back(ID);
1279 }
1280
1281 if (objID == 0) {
1282 UUID.push_back(ID);
1283 }
1284
1285 // * Primitive Data * //
1286
1287 loadPData(p, ID);
1288 } // end patches
1289
1290 //-------------- TRIANGLES ---------------//
1291
1292 // looping over any triangles specified in XML file
1293 for (pugi::xml_node tri = helios.child("triangle"); tri; tri = tri.next_sibling("triangle")) {
1294 // * Triangle Object ID * //
1295 uint objID = 0;
1296 if (XMLparser::parse_objID(tri, objID) > 1) {
1297 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'triangle' block must be a non-negative integer value.");
1298 }
1299
1300 // * Triangle Transformation Matrix * //
1301 float transform[16];
1302 int result = XMLparser::parse_transform(tri, transform);
1303 if (result == 3) {
1304 helios_runtime_error("ERROR (Context::loadXML): Triangle <transform> node contains less than 16 data values.");
1305 } else if (result == 2) {
1306 helios_runtime_error("ERROR (Context::loadXML): Triangle <transform> node contains invalid data.");
1307 }
1308
1309 // * Triangle Texture * //
1310 std::string texture_file;
1311 XMLparser::parse_texture(tri, texture_file);
1312
1313 // * Triangle Texture (u,v) Coordinates * //
1314 std::vector<vec2> uv;
1315 if (XMLparser::parse_textureUV(tri, uv) == 2) {
1316 helios_runtime_error("ERROR (Context::loadXML): (u,v) coordinates given in 'triangle' block contain invalid data.");
1317 }
1318
1319 // * Triangle Solid Fraction * //
1320 float solid_fraction = -1;
1321 if (XMLparser::parse_solid_fraction(tri, solid_fraction) == 2) {
1322 helios_runtime_error("ERROR (Context::loadXML): Solid fraction given in 'triangle' block contains invalid data.");
1323 }
1324
1325 // * Triangle Diffuse Colors * //
1326 RGBAcolor color;
1327 pugi::xml_node color_node = tri.child("color");
1328
1329 const char *color_str = color_node.child_value();
1330 if (strlen(color_str) == 0) {
1331 color = make_RGBAcolor(0, 0, 0, 1); // assume default color of black
1332 } else {
1333 color = string2RGBcolor(color_str);
1334 }
1335
1336 std::vector<vec3> vert_pos;
1337 vert_pos.resize(3);
1338 vert_pos.at(0) = make_vec3(0.f, 0.f, 0.f);
1339 vert_pos.at(1) = make_vec3(0.f, 1.f, 0.f);
1340 vert_pos.at(2) = make_vec3(1.f, 1.f, 0.f);
1341
1342 // * Add the Triangle * //
1343 if (strcmp(texture_file.c_str(), "none") == 0 || uv.empty()) {
1344 ID = addTriangle(vert_pos.at(0), vert_pos.at(1), vert_pos.at(2), color);
1345 } else {
1346 std::string texture_file_copy;
1347 if (solid_fraction < 1.f && solid_fraction >= 0.f) { // solid fraction was given in the XML, and is not equal to 1.0
1348 texture_file_copy = texture_file;
1349 texture_file = "lib/images/solid.jpg"; // load dummy solid texture to avoid re-calculating the solid fraction
1350 }
1351 ID = addTriangle(vert_pos.at(0), vert_pos.at(1), vert_pos.at(2), texture_file.c_str(), uv.at(0), uv.at(1), uv.at(2));
1352 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1353 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1354 addTexture(texture_file_copy.c_str());
1355 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1356 }
1357 }
1358 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1359
1360 if (objID > 0) {
1361 object_prim_UUIDs[objID].push_back(ID);
1362 }
1363
1364 if (objID == 0) {
1365 UUID.push_back(ID);
1366 }
1367
1368 // * Primitive Data * //
1369
1370 loadPData(tri, ID);
1371 }
1372
1373 //-------------- VOXELS ---------------//
1374 for (pugi::xml_node p = helios.child("voxel"); p; p = p.next_sibling("voxel")) {
1375 // * Voxel Object ID * //
1376 uint objID = 0;
1377 if (XMLparser::parse_objID(p, objID) > 1) {
1378 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'voxel' block must be a non-negative integer value.");
1379 }
1380
1381 // * Voxel Transformation Matrix * //
1382 float transform[16];
1383 int result = XMLparser::parse_transform(p, transform);
1384 if (result == 3) {
1385 helios_runtime_error("ERROR (Context::loadXML): Voxel <transform> node contains less than 16 data values.");
1386 } else if (result == 2) {
1387 helios_runtime_error("ERROR (Context::loadXML): Voxel <transform> node contains invalid data.");
1388 }
1389
1390 // * Voxel Solid Fraction * //
1391 float solid_fraction = 1;
1392 if (XMLparser::parse_solid_fraction(p, solid_fraction) == 2) {
1393 helios_runtime_error("ERROR (Context::loadXML): Solid fraction given in 'voxel' block contains invalid data.");
1394 }
1395
1396 // * Voxel Diffuse Colors * //
1397 RGBAcolor color;
1398 pugi::xml_node color_node = p.child("color");
1399
1400 const char *color_str = color_node.child_value();
1401 if (strlen(color_str) == 0) {
1402 color = make_RGBAcolor(0, 0, 0, 1); // assume default color of black
1403 } else {
1404 color = string2RGBcolor(color_str);
1405 }
1406
1407 // * Add the Voxel * //
1408 ID = addVoxel(make_vec3(0, 0, 0), make_vec3(0, 0, 0), 0, color);
1409 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1410
1411 if (objID > 0) {
1412 object_prim_UUIDs[objID].push_back(ID);
1413 }
1414
1415 if (objID == 0) {
1416 UUID.push_back(ID);
1417 }
1418
1419 // * Primitive Data * //
1420
1421 loadPData(p, ID);
1422 }
1423
1424 //-------------- COMPOUND OBJECTS ---------------//
1425
1426 //-------------- TILES ---------------//
1427 for (pugi::xml_node p = helios.child("tile"); p; p = p.next_sibling("tile")) {
1428 // * Tile Object ID * //
1429 uint objID = 0;
1430 if (XMLparser::parse_objID(p, objID) > 1) {
1431 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'tile' block must be a non-negative integer value.");
1432 }
1433
1434 // * Tile Transformation Matrix * //
1435 float transform[16];
1436 int result = XMLparser::parse_transform(p, transform);
1437 if (result == 3) {
1438 helios_runtime_error("ERROR (Context::loadXML): Tile <transform> node contains less than 16 data values.");
1439 } else if (result == 2) {
1440 helios_runtime_error("ERROR (Context::loadXML): Tile <transform> node contains invalid data.");
1441 }
1442
1443 // * Tile Texture * //
1444 std::string texture_file;
1445 XMLparser::parse_texture(p, texture_file);
1446
1447 // * Tile Texture (u,v) Coordinates * //
1448 std::vector<vec2> uv;
1449 if (XMLparser::parse_textureUV(p, uv) == 2) {
1450 helios_runtime_error("ERROR (Context::loadXML): (u,v) coordinates given in 'tile' block contain invalid data.");
1451 }
1452
1453 // * Tile Diffuse Colors * //
1454 RGBAcolor color;
1455 pugi::xml_node color_node = p.child("color");
1456
1457 const char *color_str = color_node.child_value();
1458 if (strlen(color_str) != 0) {
1459 color = string2RGBcolor(color_str);
1460 }
1461
1462 // * Tile Subdivisions * //
1463 int2 subdiv;
1464 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1465 if (result_subdiv == 1) {
1466 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for tile was not provided. Assuming 1x1." << std::endl;
1467 subdiv = make_int2(1, 1);
1468 } else if (result_subdiv == 2) {
1469 helios_runtime_error("ERROR (Context::loadXML): Tile <subdivisions> node contains invalid data. ");
1470 }
1471
1472 // Create a dummy patch in order to get the center, size, and rotation based on transformation matrix
1473 Patch patch(make_RGBAcolor(0, 0, 0, 0), 0, 0);
1474 patch.setTransformationMatrix(transform);
1475 // SphericalCoord rotation = cart2sphere(patch.getNormal());
1476 // rotation.elevation = rotation.zenith;
1477
1478 // * Add the Tile * //
1479 // if (strcmp(texture_file.c_str(), "none") == 0) {
1480 // if( strlen(color_str) == 0 ){
1481 // ID = addTileObject(patch.getCenter(), patch.getSize(), rotation, subdiv );
1482 // }else {
1483 // ID = addTileObject(patch.getCenter(), patch.getSize(), rotation, subdiv, make_RGBcolor(color.r, color.g, color.b));
1484 // }
1485 // } else {
1486 // ID = addTileObject(patch.getCenter(), patch.getSize(), rotation, subdiv, texture_file.c_str());
1487 // }
1488
1489 if (strcmp(texture_file.c_str(), "none") == 0) {
1490 if (strlen(color_str) == 0) {
1491 ID = addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, subdiv);
1492 } else {
1493 ID = addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, subdiv, make_RGBcolor(color.r, color.g, color.b));
1494 }
1495 } else {
1496 ID = addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, subdiv, texture_file.c_str());
1497 }
1498
1500
1502
1503 // if primitives exist that were assigned to this object, delete all primitives that were just created
1504 if (objID > 0 && !object_prim_UUIDs.empty() && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1505 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1506 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1507 deletePrimitive(uuids_to_delete);
1508 // \todo This is fairly inefficient, it would be nice to have a way to do this without having to create and delete a bunch of primitives
1509 }
1510
1512
1513 // * Tile Sub-Patch Data * //
1514
1515 loadOsubPData(p, ID);
1516
1517 // * Tile Object Data * //
1518
1519 loadOData(p, ID);
1520
1521 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1522 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1523 } // end tiles
1524
1525 //-------------- SPHERES ---------------//
1526 for (pugi::xml_node p = helios.child("sphere"); p; p = p.next_sibling("sphere")) {
1527 // * Sphere Object ID * //
1528 uint objID = 0;
1529 if (XMLparser::parse_objID(p, objID) > 1) {
1530 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'sphere' block must be a non-negative integer value.");
1531 }
1532
1533 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1534 objID = currentObjectID;
1535 currentObjectID++;
1536 }
1537
1538 // * Sphere Transformation Matrix * //
1539 float transform[16];
1540 int result = XMLparser::parse_transform(p, transform);
1541 if (result == 3) {
1542 helios_runtime_error("ERROR (Context::loadXML): Sphere <transform> node contains less than 16 data values.");
1543 } else if (result == 2) {
1544 helios_runtime_error("ERROR (Context::loadXML): Sphere <transform> node contains invalid data.");
1545 }
1546
1547 // * Sphere Texture * //
1548 std::string texture_file;
1549 XMLparser::parse_texture(p, texture_file);
1550
1551 // * Sphere Diffuse Colors * //
1552 RGBAcolor color;
1553 pugi::xml_node color_node = p.child("color");
1554
1555 const char *color_str = color_node.child_value();
1556 if (strlen(color_str) != 0) {
1557 color = string2RGBcolor(color_str);
1558 }
1559
1560 // * Sphere Subdivisions * //
1561 uint subdiv;
1562 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1563 if (result_subdiv == 1) {
1564 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for sphere was not provided. Assuming 1x1." << std::endl;
1565 subdiv = 5;
1566 } else if (result_subdiv == 2) {
1567 helios_runtime_error("ERROR (Context::loadXML): Sphere <subdivisions> node contains invalid data. ");
1568 }
1569
1570 // Create a dummy sphere in order to get the center and radius based on transformation matrix
1571 std::vector<uint> empty;
1572 Sphere sphere(0, empty, 3, "", this);
1573 sphere.setTransformationMatrix(transform);
1574
1575 // * Add the Sphere * //
1576 if (strcmp(texture_file.c_str(), "none") == 0) {
1577 if (strlen(color_str) == 0) {
1578 ID = addSphereObject(subdiv, sphere.getCenter(), sphere.getRadius());
1579 } else {
1580 ID = addSphereObject(subdiv, sphere.getCenter(), sphere.getRadius(), make_RGBcolor(color.r, color.g, color.b));
1581 }
1582 } else {
1583 ID = addSphereObject(subdiv, sphere.getCenter(), sphere.getRadius(), texture_file.c_str());
1584 }
1585
1586 // if primitives exist that were assigned to this object, delete all primitives that were just created
1587 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1588 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1589 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1590 deletePrimitive(uuids_to_delete);
1591 // if( !doesObjectExist(ID) ){ //if the above method deleted all primitives for this object, move on
1592 // continue;
1593 // }
1594 }
1595
1597
1598 // * Sphere Sub-Triangle Data * //
1599
1600 loadOsubPData(p, ID);
1601
1602 // * Sphere Object Data * //
1603
1604 loadOData(p, ID);
1605
1606 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1607 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1608 } // end spheres
1609
1610 //-------------- TUBES ---------------//
1611 for (pugi::xml_node p = helios.child("tube"); p; p = p.next_sibling("tube")) {
1612 // * Tube Object ID * //
1613 uint objID = 0;
1614 if (XMLparser::parse_objID(p, objID) > 1) {
1615 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'tube' block must be a non-negative integer value.");
1616 }
1617
1618 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1619 objID = currentObjectID;
1620 currentObjectID++;
1621 }
1622
1623 // * Tube Transformation Matrix * //
1624 float transform[16];
1625 int result = XMLparser::parse_transform(p, transform);
1626 if (result == 3) {
1627 helios_runtime_error("ERROR (Context::loadXML): Tube <transform> node contains less than 16 data values.");
1628 } else if (result == 2) {
1629 helios_runtime_error("ERROR (Context::loadXML): Tube <transform> node contains invalid data.");
1630 }
1631
1632 // * Tube Texture * //
1633 std::string texture_file;
1634 XMLparser::parse_texture(p, texture_file);
1635
1636 // * Tube Subdivisions * //
1637 uint subdiv;
1638 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1639 if (result_subdiv == 1) {
1640 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for tube was not provided. Assuming 1x1." << std::endl;
1641 subdiv = 5;
1642 } else if (result_subdiv == 2) {
1643 helios_runtime_error("ERROR (Context::loadXML): Tube <subdivisions> node contains invalid data. ");
1644 }
1645
1646 // * Tube Nodes * //
1647 std::vector<vec3> nodes;
1648 pugi::xml_node nodes_node = p.child("nodes");
1649 if (XMLparser::parse_data_vec3(nodes_node, nodes) != 0 || nodes.size() < 2) {
1650 helios_runtime_error("ERROR (Context::loadXML): Tube <nodes> node contains invalid data. ");
1651 }
1652
1653 // * Tube Radius * //
1654 std::vector<float> radii;
1655 pugi::xml_node radii_node = p.child("radius");
1656 if (XMLparser::parse_data_float(radii_node, radii) != 0 || radii.size() < 2) {
1657 helios_runtime_error("ERROR (Context::loadXML): Tube <radius> node contains invalid data. ");
1658 }
1659
1660 // * Tube Color * //
1661
1662 pugi::xml_node color_node = p.child("color");
1663 const char *color_str = color_node.child_value();
1664
1665 std::vector<RGBcolor> colors;
1666 if (strlen(color_str) > 0) {
1667 std::istringstream data_stream(color_str);
1668 std::vector<float> tmp;
1669 tmp.resize(3);
1670 int c = 0;
1671 while (data_stream >> tmp.at(c)) {
1672 c++;
1673 if (c == 3) {
1674 colors.push_back(make_RGBcolor(tmp.at(0), tmp.at(1), tmp.at(2)));
1675 c = 0;
1676 }
1677 }
1678 }
1679
1680 // * Add the Tube * //
1681 if (texture_file == "none") {
1682 ID = addTubeObject(subdiv, nodes, radii, colors);
1683 } else {
1684 ID = addTubeObject(subdiv, nodes, radii, texture_file.c_str());
1685 }
1686
1688
1689 // if primitives exist that were assigned to this object, delete all primitives that were just created
1690 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1691 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1692 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1693 deletePrimitive(uuids_to_delete);
1694 // if( !doesObjectExist(ID) ){ //if the above method deleted all primitives for this object, move on
1695 // continue;
1696 // }
1697 }
1698
1700
1701 // * Tube Sub-Triangle Data * //
1702
1703 loadOsubPData(p, ID);
1704
1705 // * tube Object Data * //
1706
1707 loadOData(p, ID);
1708
1709 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1710 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1711 } // end tubes
1712
1713 //-------------- BOXES ---------------//
1714 for (pugi::xml_node p = helios.child("box"); p; p = p.next_sibling("box")) {
1715 // * Box Object ID * //
1716 uint objID = 0;
1717 if (XMLparser::parse_objID(p, objID) > 1) {
1718 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'box' block must be a non-negative integer value.");
1719 }
1720
1721 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1722 objID = currentObjectID;
1723 currentObjectID++;
1724 }
1725
1726 // * Box Transformation Matrix * //
1727 float transform[16];
1728 int result = XMLparser::parse_transform(p, transform);
1729 if (result == 3) {
1730 helios_runtime_error("ERROR (Context::loadXML): Box <transform> node contains less than 16 data values.");
1731 } else if (result == 2) {
1732 helios_runtime_error("ERROR (Context::loadXML): Box <transform> node contains invalid data.");
1733 }
1734
1735 // * Box Texture * //
1736 std::string texture_file;
1737 XMLparser::parse_texture(p, texture_file);
1738
1739 // * Box Diffuse Colors * //
1740 RGBAcolor color;
1741 pugi::xml_node color_node = p.child("color");
1742
1743 const char *color_str = color_node.child_value();
1744 if (strlen(color_str) != 0) {
1745 color = string2RGBcolor(color_str);
1746 }
1747
1748 // * Box Subdivisions * //
1749 int3 subdiv;
1750 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1751 if (result_subdiv == 1) {
1752 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for box was not provided. Assuming 1x1." << std::endl;
1753 subdiv = make_int3(1, 1, 1);
1754 } else if (result_subdiv == 2) {
1755 helios_runtime_error("ERROR (Context::loadXML): Box <subdivisions> node contains invalid data. ");
1756 }
1757
1758 // Create a dummy box in order to get the center and size based on transformation matrix
1759 std::vector<uint> empty;
1760 Box box(0, empty, make_int3(1, 1, 1), "", this);
1761 box.setTransformationMatrix(transform);
1762
1763 // * Add the box * //
1764 if (strcmp(texture_file.c_str(), "none") == 0) {
1765 if (strlen(color_str) == 0) {
1766 ID = addBoxObject(box.getCenter(), box.getSize(), subdiv);
1767 } else {
1768 ID = addBoxObject(box.getCenter(), box.getSize(), subdiv, make_RGBcolor(color.r, color.g, color.b));
1769 }
1770 } else {
1771 ID = addBoxObject(box.getCenter(), box.getSize(), subdiv, texture_file.c_str());
1772 }
1773
1774 // if primitives exist that were assigned to this object, delete all primitives that were just created
1775 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1776 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1777 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1778 deletePrimitive(uuids_to_delete);
1779 // if( !doesObjectExist(ID) ){ //if the above method deleted all primitives for this object, move on
1780 // continue;
1781 // }
1782 }
1783
1785
1786 // * Box Sub-Patch Data * //
1787
1788 loadOsubPData(p, ID);
1789
1790 // * Box Object Data * //
1791
1792 loadOData(p, ID);
1793
1794 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1795 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1796 } // end boxes
1797
1798 //-------------- DISKS ---------------//
1799 for (pugi::xml_node p = helios.child("disk"); p; p = p.next_sibling("disk")) {
1800 // * Disk Object ID * //
1801 uint objID = 0;
1802 if (XMLparser::parse_objID(p, objID) > 1) {
1803 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'disk' block must be a non-negative integer value.");
1804 }
1805
1806 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1807 objID = currentObjectID;
1808 currentObjectID++;
1809 }
1810
1811 // * Disk Transformation Matrix * //
1812 float transform[16];
1813 int result = XMLparser::parse_transform(p, transform);
1814 if (result == 3) {
1815 helios_runtime_error("ERROR (Context::loadXML): Disk <transform> node contains less than 16 data values.");
1816 } else if (result == 2) {
1817 helios_runtime_error("ERROR (Context::loadXML): Disk <transform> node contains invalid data.");
1818 }
1819
1820 // * Disk Texture * //
1821 std::string texture_file;
1822 XMLparser::parse_texture(p, texture_file);
1823
1824 // * Disk Diffuse Colors * //
1825 RGBAcolor color;
1826 pugi::xml_node color_node = p.child("color");
1827
1828 const char *color_str = color_node.child_value();
1829 if (strlen(color_str) != 0) {
1830 color = string2RGBcolor(color_str);
1831 }
1832
1833 // * Disk Subdivisions * //
1834 int2 subdiv;
1835 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1836 if (result_subdiv == 1) {
1837 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for disk was not provided. Assuming 1x1." << std::endl;
1838 subdiv = make_int2(5, 1);
1839 } else if (result_subdiv == 2) {
1840 helios_runtime_error("ERROR (Context::loadXML): Disk <subdivisions> node contains invalid data. ");
1841 }
1842
1843 // Create a dummy disk in order to get the center and size based on transformation matrix
1844 std::vector<uint> empty;
1845 Disk disk(0, empty, make_int2(1, 1), "", this);
1846 disk.setTransformationMatrix(transform);
1847
1848 // * Add the disk * //
1849 if (strcmp(texture_file.c_str(), "none") == 0) {
1850 if (strlen(color_str) == 0) {
1851 ID = addDiskObject(subdiv, disk.getCenter(), disk.getSize(), nullrotation, RGB::red);
1852 } else {
1853 ID = addDiskObject(subdiv, disk.getCenter(), disk.getSize(), nullrotation, make_RGBcolor(color.r, color.g, color.b));
1854 }
1855 } else {
1856 ID = addDiskObject(subdiv, disk.getCenter(), disk.getSize(), nullrotation, texture_file.c_str());
1857 }
1858
1859 // if primitives exist that were assigned to this object, delete all primitives that were just created
1860 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1861 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1862 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1863 deletePrimitive(uuids_to_delete);
1864 // if( !doesObjectExist(ID) ){ //if the above method deleted all primitives for this object, move on
1865 // continue;
1866 // }
1867 }
1868
1870
1871 // * Disk Sub-Triangle Data * //
1872
1873 loadOsubPData(p, ID);
1874
1875 // * Disk Object Data * //
1876
1877 loadOData(p, ID);
1878
1879 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1880 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1881 } // end disks
1882
1883 //-------------- CONES ---------------//
1884 for (pugi::xml_node p = helios.child("cone"); p; p = p.next_sibling("cone")) {
1885 // * Cone Object ID * //
1886 uint objID = 0;
1887 if (XMLparser::parse_objID(p, objID) > 1) {
1888 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'cone' block must be a non-negative integer value.");
1889 }
1890
1891 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1892 objID = currentObjectID;
1893 currentObjectID++;
1894 }
1895
1896 // * Cone Transformation Matrix * //
1897 float transform[16];
1898 int result = XMLparser::parse_transform(p, transform);
1899 if (result == 3) {
1900 helios_runtime_error("ERROR (Context::loadXML): Cone <transform> node contains less than 16 data values.");
1901 } else if (result == 2) {
1902 helios_runtime_error("ERROR (Context::loadXML): Cone <transform> node contains invalid data.");
1903 }
1904
1905 // * Cone Texture * //
1906 std::string texture_file;
1907 XMLparser::parse_texture(p, texture_file);
1908
1909 // * Cone Diffuse Colors * //
1910 RGBAcolor color;
1911 pugi::xml_node color_node = p.child("color");
1912
1913 const char *color_str = color_node.child_value();
1914 if (strlen(color_str) != 0) {
1915 color = string2RGBcolor(color_str);
1916 }
1917
1918 // * Cone Subdivisions * //
1919 uint subdiv;
1920 int result_subdiv = XMLparser::parse_subdivisions(p, subdiv);
1921 if (result_subdiv == 1) {
1922 std::cerr << "WARNING (Context::loadXML): Number of subdivisions for cone was not provided. Assuming 1x1." << std::endl;
1923 subdiv = 5;
1924 } else if (result_subdiv == 2) {
1925 helios_runtime_error("ERROR (Context::loadXML): Cone <subdivisions> node contains invalid data. ");
1926 }
1927
1928 // * Cone Nodes * //
1929 std::vector<vec3> nodes;
1930 pugi::xml_node nodes_node = p.child("nodes");
1931 if (XMLparser::parse_data_vec3(nodes_node, nodes) != 0 || nodes.size() != 2) {
1932 helios_runtime_error("ERROR (Context::loadXML): Cone <nodes> node contains invalid data. ");
1933 }
1934
1935 // * Cone Radius * //
1936 std::vector<float> radii;
1937 pugi::xml_node radii_node = p.child("radius");
1938 if (XMLparser::parse_data_float(radii_node, radii) != 0 || radii.size() != 2) {
1939 helios_runtime_error("ERROR (Context::loadXML): Cone <radius> node contains invalid data. ");
1940 }
1941
1942 // * Add the Cone * //
1943 if (texture_file == "none") {
1944 ID = addConeObject(subdiv, nodes.at(0), nodes.at(1), radii.at(0), radii.at(1), make_RGBcolor(color.r, color.g, color.b));
1945 } else {
1946 ID = addConeObject(subdiv, nodes.at(0), nodes.at(1), radii.at(0), radii.at(1), texture_file.c_str());
1947 }
1948
1950
1951 // if primitives exist that were assigned to this object, delete all primitives that were just created
1952 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1953 std::vector<uint> uuids_to_delete = getObjectPrimitiveUUIDs(ID);
1954 getObjectPointer(ID)->setPrimitiveUUIDs(object_prim_UUIDs.at(objID));
1955 deletePrimitive(uuids_to_delete);
1956 // if( !doesObjectExist(ID) ){ //if the above method deleted all primitives for this object, move on
1957 // continue;
1958 // }
1959 }
1960
1962
1963 // * Cone Sub-Triangle Data * //
1964
1965 loadOsubPData(p, ID);
1966
1967 // * Cone Object Data * //
1968
1969 loadOData(p, ID);
1970
1971 std::vector<uint> childUUIDs = getObjectPrimitiveUUIDs(ID);
1972 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1973 } // end cones
1974
1975 //-------------- POLYMESH ---------------//
1976 for (pugi::xml_node p = helios.child("polymesh"); p; p = p.next_sibling("polymesh")) {
1977 // * Polymesh Object ID * //
1978 uint objID = 0;
1979 if (XMLparser::parse_objID(p, objID) > 1) {
1980 helios_runtime_error("ERROR (Context::loadXML): Object ID (objID) given in 'polymesh' block must be a non-negative integer value.");
1981 }
1982
1983 if (doesObjectExist(objID)) { // if this object ID is already in use, assign a new one
1984 objID = currentObjectID;
1985 currentObjectID++;
1986 }
1987
1988 ID = addPolymeshObject(object_prim_UUIDs.at(objID));
1989
1990 setPrimitiveParentObjectID(object_prim_UUIDs.at(objID), ID);
1991
1992 // * Polymesh Sub-Primitive Data * //
1993
1994 loadOsubPData(p, ID);
1995
1996 // * Polymesh Object Data * //
1997
1998 loadOData(p, ID);
1999
2000 std::vector<uint> childUUIDs = object_prim_UUIDs.at(objID);
2001 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2002 } // end polymesh
2003
2004 //-------------- GLOBAL DATA ---------------//
2005
2006 for (pugi::xml_node data = helios.child("globaldata_int"); data; data = data.next_sibling("globaldata_int")) {
2007 const char *label = data.attribute("label").value();
2008
2009 std::vector<int> datav;
2010 if (XMLparser::parse_data_int(data, datav) != 0) {
2011 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_int> with label " + std::string(label) + " contained invalid data.");
2012 }
2013
2014 if (datav.size() == 1) {
2015 setGlobalData(label, datav.front());
2016 } else if (datav.size() > 1) {
2017 setGlobalData(label, datav);
2018 }
2019 }
2020
2021 for (pugi::xml_node data = helios.child("globaldata_uint"); data; data = data.next_sibling("globaldata_uint")) {
2022 const char *label = data.attribute("label").value();
2023
2024 std::vector<uint> datav;
2025 if (XMLparser::parse_data_uint(data, datav) != 0) {
2026 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_uint> with label " + std::string(label) + " contained invalid data.");
2027 }
2028
2029 if (datav.size() == 1) {
2030 setGlobalData(label, datav.front());
2031 } else if (datav.size() > 1) {
2032 setGlobalData(label, datav);
2033 }
2034 }
2035
2036 for (pugi::xml_node data = helios.child("globaldata_float"); data; data = data.next_sibling("globaldata_float")) {
2037 const char *label = data.attribute("label").value();
2038
2039 std::vector<float> datav;
2040 if (XMLparser::parse_data_float(data, datav) != 0) {
2041 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_float> with label " + std::string(label) + " contained invalid data.");
2042 }
2043
2044 if (datav.size() == 1) {
2045 setGlobalData(label, datav.front());
2046 } else if (datav.size() > 1) {
2047 setGlobalData(label, datav);
2048 }
2049 }
2050
2051 for (pugi::xml_node data = helios.child("globaldata_double"); data; data = data.next_sibling("globaldata_double")) {
2052 const char *label = data.attribute("label").value();
2053
2054 std::vector<double> datav;
2055 if (XMLparser::parse_data_double(data, datav) != 0) {
2056 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_double> with label " + std::string(label) + " contained invalid data.");
2057 }
2058
2059 if (datav.size() == 1) {
2060 setGlobalData(label, datav.front());
2061 } else if (datav.size() > 1) {
2062 setGlobalData(label, datav);
2063 }
2064 }
2065
2066 for (pugi::xml_node data = helios.child("globaldata_vec2"); data; data = data.next_sibling("globaldata_vec2")) {
2067 const char *label = data.attribute("label").value();
2068
2069 std::vector<vec2> datav;
2070 if (XMLparser::parse_data_vec2(data, datav) != 0) {
2071 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_vec2> with label " + std::string(label) + " contained invalid data.");
2072 }
2073
2074 if (datav.size() == 1) {
2075 setGlobalData(label, datav.front());
2076 } else if (datav.size() > 1) {
2077 setGlobalData(label, datav);
2078 }
2079 }
2080
2081 for (pugi::xml_node data = helios.child("globaldata_vec3"); data; data = data.next_sibling("globaldata_vec3")) {
2082 const char *label = data.attribute("label").value();
2083
2084 std::vector<vec3> datav;
2085 if (XMLparser::parse_data_vec3(data, datav) != 0) {
2086 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_vec3> with label " + std::string(label) + " contained invalid data.");
2087 }
2088
2089 if (datav.size() == 1) {
2090 setGlobalData(label, datav.front());
2091 } else if (datav.size() > 1) {
2092 setGlobalData(label, datav);
2093 }
2094 }
2095
2096 for (pugi::xml_node data = helios.child("globaldata_vec4"); data; data = data.next_sibling("globaldata_vec4")) {
2097 const char *label = data.attribute("label").value();
2098
2099 std::vector<vec4> datav;
2100 if (XMLparser::parse_data_vec4(data, datav) != 0) {
2101 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_vec4> with label " + std::string(label) + " contained invalid data.");
2102 }
2103
2104 if (datav.size() == 1) {
2105 setGlobalData(label, datav.front());
2106 } else if (datav.size() > 1) {
2107 setGlobalData(label, datav);
2108 }
2109 }
2110
2111 for (pugi::xml_node data = helios.child("globaldata_int2"); data; data = data.next_sibling("globaldata_int2")) {
2112 const char *label = data.attribute("label").value();
2113
2114 std::vector<int2> datav;
2115 if (XMLparser::parse_data_int2(data, datav) != 0) {
2116 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_int2> with label " + std::string(label) + " contained invalid data.");
2117 }
2118
2119 if (datav.size() == 1) {
2120 setGlobalData(label, datav.front());
2121 } else if (datav.size() > 1) {
2122 setGlobalData(label, datav);
2123 }
2124 }
2125
2126 for (pugi::xml_node data = helios.child("globaldata_int3"); data; data = data.next_sibling("globaldata_int3")) {
2127 const char *label = data.attribute("label").value();
2128
2129 std::vector<int3> datav;
2130 if (XMLparser::parse_data_int3(data, datav) != 0) {
2131 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_int3> with label " + std::string(label) + " contained invalid data.");
2132 }
2133
2134 if (datav.size() == 1) {
2135 setGlobalData(label, datav.front());
2136 } else if (datav.size() > 1) {
2137 setGlobalData(label, datav);
2138 }
2139 }
2140
2141 for (pugi::xml_node data = helios.child("globaldata_int4"); data; data = data.next_sibling("globaldata_int4")) {
2142 const char *label = data.attribute("label").value();
2143
2144 std::vector<int4> datav;
2145 if (XMLparser::parse_data_int4(data, datav) != 0) {
2146 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_int4> with label " + std::string(label) + " contained invalid data.");
2147 }
2148
2149 if (datav.size() == 1) {
2150 setGlobalData(label, datav.front());
2151 } else if (datav.size() > 1) {
2152 setGlobalData(label, datav);
2153 }
2154 }
2155
2156 for (pugi::xml_node data = helios.child("globaldata_string"); data; data = data.next_sibling("globaldata_string")) {
2157 const char *label = data.attribute("label").value();
2158
2159 std::vector<std::string> datav;
2160 if (XMLparser::parse_data_string(data, datav) != 0) {
2161 helios_runtime_error("ERROR (Context::loadXML): Global data tag <globaldata_string> with label " + std::string(label) + " contained invalid data.");
2162 }
2163
2164 if (datav.size() == 1) {
2165 setGlobalData(label, datav.front());
2166 } else if (datav.size() > 1) {
2167 setGlobalData(label, datav);
2168 }
2169 }
2170
2171 //-------------- TIMESERIES DATA ---------------//
2172 for (pugi::xml_node p = helios.child("timeseries"); p; p = p.next_sibling("timeseries")) {
2173 const char *label = p.attribute("label").value();
2174
2175 for (pugi::xml_node d = p.child("datapoint"); d; d = d.next_sibling("datapoint")) {
2176 Time time;
2177 pugi::xml_node time_node = d.child("time");
2178 const char *time_str = time_node.child_value();
2179 if (strlen(time_str) > 0) {
2180 int3 time_ = string2int3(time_str);
2181 if (time_.x < 0 || time_.x > 23) {
2182 helios_runtime_error("ERROR (Context::loadXML): Invalid hour of " + std::to_string(time_.x) + " given in timeseries. Hour must be positive and not greater than 23.");
2183 } else if (time_.y < 0 || time_.y > 59) {
2184 helios_runtime_error("ERROR (Context::loadXML): Invalid minute of " + std::to_string(time_.y) + " given in timeseries. Minute must be positive and not greater than 59.");
2185 } else if (time_.z < 0 || time_.z > 59) {
2186 helios_runtime_error("ERROR (Context::loadXML): Invalid second of " + std::to_string(time_.z) + " given in timeseries. Second must be positive and not greater than 59.");
2187 }
2188 time = make_Time(time_.x, time_.y, time_.z);
2189 } else {
2190 helios_runtime_error("ERROR (Context::loadXML): No time was specified for timeseries datapoint.");
2191 }
2192
2193 Date date;
2194 bool date_flag = false;
2195
2196 pugi::xml_node date_node = d.child("date");
2197 const char *date_str = date_node.child_value();
2198 if (strlen(date_str) > 0) {
2199 int3 date_ = string2int3(date_str);
2200 if (date_.x < 1 || date_.x > 31) {
2201 helios_runtime_error("ERROR (Context::loadXML): Invalid day of month " + std::to_string(date_.x) + " given in timeseries. Day must be greater than zero and not greater than 31.");
2202 } else if (date_.y < 1 || date_.y > 12) {
2203 helios_runtime_error("ERROR (Context::loadXML): Invalid month of " + std::to_string(date_.y) + " given in timeseries. Month must be greater than zero and not greater than 12.");
2204 } else if (date_.z < 1000 || date_.z > 10000) {
2205 helios_runtime_error("ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.z) + " given in timeseries. Year should be in YYYY format.");
2206 }
2207 date = make_Date(date_.x, date_.y, date_.z);
2208 date_flag = true;
2209 }
2210
2211 pugi::xml_node Jdate_node = d.child("dateJulian");
2212 const char *Jdate_str = Jdate_node.child_value();
2213 if (strlen(Jdate_str) > 0) {
2214 int2 date_ = string2int2(Jdate_str);
2215 if (date_.x < 1 || date_.x > 366) {
2216 helios_runtime_error("ERROR (Context::loadXML): Invalid Julian day of year " + std::to_string(date_.x) + " given in timeseries. Julian day must be greater than zero and not greater than 366.");
2217 } else if (date_.y < 1000 || date_.y > 10000) {
2218 helios_runtime_error("ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.y) + " given in timeseries. Year should be in YYYY format.");
2219 }
2220 date = Julian2Calendar(date_.x, date_.y);
2221 date_flag = true;
2222 }
2223
2224 if (!date_flag) {
2225 helios_runtime_error("ERROR (Context::loadXML): No date was specified for timeseries datapoint.");
2226 }
2227
2228 float value;
2229 pugi::xml_node value_node = d.child("value");
2230 const char *value_str = value_node.child_value();
2231 if (strlen(value_str) > 0) {
2232 if (!parse_float(value_str, value)) {
2233 helios_runtime_error("ERROR (Context::loadXML): Datapoint value in 'timeseries' block must be a float value.");
2234 }
2235 } else {
2236 helios_runtime_error("ERROR (Context::loadXML): No value was specified for timeseries datapoint.");
2237 }
2238
2239 addTimeseriesData(label, value, date, time);
2240 }
2241 }
2242
2243 if (!quiet) {
2244 std::cout << "done." << std::endl;
2245 }
2246
2247 return UUID;
2248}
2249
2250std::vector<std::string> Context::getLoadedXMLFiles() {
2251 return XMLfiles;
2252}
2253
2254bool Context::scanXMLForTag(const std::string &filename, const std::string &tag, const std::string &label) {
2255 const std::string &fn = filename;
2256 std::string ext = getFileExtension(filename);
2257 if (ext != ".xml" && ext != ".XML") {
2258 helios_runtime_error("failed.\n File " + fn + " is not XML format.");
2259 }
2260
2261 // Using "pugixml" parser. See pugixml.org
2262 pugi::xml_document xmldoc;
2263
2264 // load file
2265 pugi::xml_parse_result load_result = xmldoc.load_file(filename.c_str());
2266
2267 // error checking
2268 if (!load_result) {
2269 helios_runtime_error("failed.\n XML [" + filename + "] parsed with errors, attr value: [" + xmldoc.child("node").attribute("attr").value() + "]\nError description: " + load_result.description() +
2270 "\nError offset: " + std::to_string(load_result.offset) + " (error at [..." + (filename.c_str() + load_result.offset) + "]\n");
2271 }
2272
2273 pugi::xml_node helios = xmldoc.child("helios");
2274
2275 if (helios.empty()) {
2276 return false;
2277 }
2278
2279 for (pugi::xml_node p = helios.child(tag.c_str()); p; p = p.next_sibling(tag.c_str())) {
2280 const char *labelquery = p.attribute("label").value();
2281
2282 if (labelquery == label || label.empty()) {
2283 return true;
2284 }
2285 }
2286
2287 return false;
2288}
2289
2290void Context::writeDataToXMLstream(const char *data_group, const std::vector<std::string> &data_labels, void *ptr, std::ofstream &outfile) const {
2291 for (const auto &label: data_labels) {
2293
2294 if (strcmp(data_group, "primitive") == 0) {
2295 dtype = ((Primitive *) ptr)->getPrimitiveDataType(label.c_str());
2296 } else if (strcmp(data_group, "object") == 0) {
2297 dtype = ((CompoundObject *) ptr)->getObjectDataType(label.c_str());
2298 } else if (strcmp(data_group, "global") == 0) {
2299 dtype = getGlobalDataType(label.c_str());
2300 } else {
2301 helios_runtime_error("ERROR (Context::writeDataToXMLstream): unknown data group argument of " + std::string(data_group) + ". Must be one of primitive, object, or global.");
2302 }
2303
2304 if (dtype == HELIOS_TYPE_UINT) {
2305 outfile << "\t<data_uint label=\"" << label << "\">" << std::flush;
2306 std::vector<uint> data;
2307 if (strcmp(data_group, "primitive") == 0) {
2308 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2309 } else if (strcmp(data_group, "object") == 0) {
2310 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2311 } else {
2312 getGlobalData(label.c_str(), data);
2313 }
2314 for (int j = 0; j < data.size(); j++) {
2315 outfile << data.at(j) << std::flush;
2316 if (j != data.size() - 1) {
2317 outfile << " " << std::flush;
2318 }
2319 }
2320 outfile << "</data_uint>" << std::endl;
2321 } else if (dtype == HELIOS_TYPE_INT) {
2322 outfile << "\t<data_int label=\"" << label << "\">" << std::flush;
2323 std::vector<int> data;
2324 if (strcmp(data_group, "primitive") == 0) {
2325 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2326 } else if (strcmp(data_group, "object") == 0) {
2327 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2328 } else {
2329 getGlobalData(label.c_str(), data);
2330 }
2331 for (int j = 0; j < data.size(); j++) {
2332 outfile << data.at(j) << std::flush;
2333 if (j != data.size() - 1) {
2334 outfile << " " << std::flush;
2335 }
2336 }
2337 outfile << "</data_int>" << std::endl;
2338 } else if (dtype == HELIOS_TYPE_FLOAT) {
2339 outfile << "\t<data_float label=\"" << label << "\">" << std::flush;
2340 std::vector<float> data;
2341 if (strcmp(data_group, "primitive") == 0) {
2342 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2343 } else if (strcmp(data_group, "object") == 0) {
2344 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2345 } else {
2346 getGlobalData(label.c_str(), data);
2347 }
2348 for (int j = 0; j < data.size(); j++) {
2349 outfile << data.at(j) << std::flush;
2350 if (j != data.size() - 1) {
2351 outfile << " " << std::flush;
2352 }
2353 }
2354 outfile << "</data_float>" << std::endl;
2355 } else if (dtype == HELIOS_TYPE_DOUBLE) {
2356 outfile << "\t<data_double label=\"" << label << "\">" << std::flush;
2357 std::vector<double> data;
2358 if (strcmp(data_group, "primitive") == 0) {
2359 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2360 } else if (strcmp(data_group, "object") == 0) {
2361 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2362 } else {
2363 getGlobalData(label.c_str(), data);
2364 }
2365 for (int j = 0; j < data.size(); j++) {
2366 outfile << data.at(j) << std::flush;
2367 if (j != data.size() - 1) {
2368 outfile << " " << std::flush;
2369 }
2370 }
2371 outfile << "</data_double>" << std::endl;
2372 } else if (dtype == HELIOS_TYPE_VEC2) {
2373 outfile << "\t<data_vec2 label=\"" << label << "\">" << std::flush;
2374 std::vector<vec2> data;
2375 if (strcmp(data_group, "primitive") == 0) {
2376 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2377 } else if (strcmp(data_group, "object") == 0) {
2378 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2379 } else {
2380 getGlobalData(label.c_str(), data);
2381 }
2382 for (int j = 0; j < data.size(); j++) {
2383 outfile << data.at(j).x << " " << data.at(j).y << std::flush;
2384 if (j != data.size() - 1) {
2385 outfile << " " << std::flush;
2386 }
2387 }
2388 outfile << "</data_vec2>" << std::endl;
2389 } else if (dtype == HELIOS_TYPE_VEC3) {
2390 outfile << "\t<data_vec3 label=\"" << label << "\">" << std::flush;
2391 std::vector<vec3> data;
2392 if (strcmp(data_group, "primitive") == 0) {
2393 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2394 } else if (strcmp(data_group, "object") == 0) {
2395 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2396 } else {
2397 getGlobalData(label.c_str(), data);
2398 }
2399 for (int j = 0; j < data.size(); j++) {
2400 outfile << data.at(j).x << " " << data.at(j).y << " " << data.at(j).z << std::flush;
2401 if (j != data.size() - 1) {
2402 outfile << " " << std::flush;
2403 }
2404 }
2405 outfile << "</data_vec3>" << std::endl;
2406 } else if (dtype == HELIOS_TYPE_VEC4) {
2407 outfile << "\t<data_vec4 label=\"" << label << "\">" << std::flush;
2408 std::vector<vec4> data;
2409 if (strcmp(data_group, "primitive") == 0) {
2410 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2411 } else if (strcmp(data_group, "object") == 0) {
2412 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2413 } else {
2414 getGlobalData(label.c_str(), data);
2415 }
2416 for (int j = 0; j < data.size(); j++) {
2417 outfile << data.at(j).x << " " << data.at(j).y << " " << data.at(j).z << " " << data.at(j).w << std::flush;
2418 if (j != data.size() - 1) {
2419 outfile << " " << std::flush;
2420 }
2421 }
2422 outfile << "</data_vec4>" << std::endl;
2423 } else if (dtype == HELIOS_TYPE_INT2) {
2424 outfile << "\t<data_int2 label=\"" << label << "\">" << std::flush;
2425 std::vector<int2> data;
2426 if (strcmp(data_group, "primitive") == 0) {
2427 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2428 } else if (strcmp(data_group, "object") == 0) {
2429 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2430 } else {
2431 getGlobalData(label.c_str(), data);
2432 }
2433 for (int j = 0; j < data.size(); j++) {
2434 outfile << data.at(j).x << " " << data.at(j).y << std::flush;
2435 if (j != data.size() - 1) {
2436 outfile << " " << std::flush;
2437 }
2438 }
2439 outfile << "</data_int2>" << std::endl;
2440 } else if (dtype == HELIOS_TYPE_INT3) {
2441 outfile << "\t<data_int3 label=\"" << label << "\">" << std::flush;
2442 std::vector<int3> data;
2443 if (strcmp(data_group, "primitive") == 0) {
2444 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2445 } else if (strcmp(data_group, "object") == 0) {
2446 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2447 } else {
2448 getGlobalData(label.c_str(), data);
2449 }
2450 for (int j = 0; j < data.size(); j++) {
2451 outfile << data.at(j).x << " " << data.at(j).y << " " << data.at(j).z << std::flush;
2452 if (j != data.size() - 1) {
2453 outfile << " " << std::flush;
2454 }
2455 }
2456 outfile << "</data_int3>" << std::endl;
2457 } else if (dtype == HELIOS_TYPE_INT4) {
2458 outfile << "\t<data_int3 label=\"" << label << "\">" << std::flush;
2459 std::vector<int4> data;
2460 if (strcmp(data_group, "primitive") == 0) {
2461 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2462 } else if (strcmp(data_group, "object") == 0) {
2463 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2464 } else {
2465 getGlobalData(label.c_str(), data);
2466 }
2467 for (int j = 0; j < data.size(); j++) {
2468 outfile << data.at(j).x << " " << data.at(j).y << " " << data.at(j).z << " " << data.at(j).w << std::flush;
2469 if (j != data.size() - 1) {
2470 outfile << " " << std::flush;
2471 }
2472 }
2473 outfile << "</data_int4>" << std::endl;
2474 } else if (dtype == HELIOS_TYPE_STRING) {
2475 outfile << "\t<data_string label=\"" << label << "\">" << std::flush;
2476 std::vector<std::string> data;
2477 if (strcmp(data_group, "primitive") == 0) {
2478 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2479 } else if (strcmp(data_group, "object") == 0) {
2480 ((CompoundObject *) ptr)->getObjectData(label.c_str(), data);
2481 } else {
2482 getGlobalData(label.c_str(), data);
2483 }
2484 for (int j = 0; j < data.size(); j++) {
2485 outfile << data.at(j) << std::flush;
2486 if (j != data.size() - 1) {
2487 outfile << " " << std::flush;
2488 }
2489 }
2490 outfile << "</data_string>" << std::endl;
2491 }
2492 }
2493}
2494
2495void Context::writeXML(const char *filename, bool quiet) const {
2496 writeXML(filename, getAllUUIDs(), quiet);
2497}
2498
2499void Context::writeXML_byobject(const char *filename, const std::vector<uint> &objIDs, bool quiet) const {
2500 for (uint objID: objIDs) {
2501 if (!doesObjectExist(objID)) {
2502 helios_runtime_error("ERROR (Context::writeXML_byobject): Object with ID of " + std::to_string(objID) + " does not exist.");
2503 }
2504 }
2505 writeXML(filename, getObjectPrimitiveUUIDs(objIDs), quiet);
2506}
2507
2508void Context::writeXML(const char *filename, const std::vector<uint> &UUIDs, bool quiet) const {
2509 if (!quiet) {
2510 std::cout << "Writing XML file " << filename << "..." << std::flush;
2511 }
2512
2513 std::string xmlfilename = filename;
2514
2515 if (!validateOutputPath(xmlfilename)) {
2516 helios_runtime_error("ERROR (Context::writeXML): Invalid output file " + xmlfilename + ".");
2517 }
2518
2519 if (getFileName(xmlfilename).empty()) {
2520 helios_runtime_error("ERROR (Context::writeXML): Invalid output file " + xmlfilename + ". No file name was provided.");
2521 }
2522
2523 auto file_extension = getFileExtension(filename);
2524 if (file_extension != ".xml" && file_extension != ".XML") { // append xml to file name
2525 xmlfilename.append(".xml");
2526 }
2527
2528 std::vector<uint> objectIDs = getUniquePrimitiveParentObjectIDs(UUIDs, false);
2529
2530 std::ofstream outfile;
2531 outfile.open(xmlfilename);
2532
2533 outfile << "<?xml version=\"1.0\"?>\n\n";
2534
2535 outfile << "<helios>\n\n";
2536
2537 // -- time/date -- //
2538
2539 Date date = getDate();
2540
2541 outfile << " <date>" << std::endl;
2542
2543 outfile << "\t<day>" << date.day << "</day>" << std::endl;
2544 outfile << "\t<month>" << date.month << "</month>" << std::endl;
2545 outfile << "\t<year>" << date.year << "</year>" << std::endl;
2546
2547 outfile << " </date>" << std::endl;
2548
2549 Time time = getTime();
2550
2551 outfile << " <time>" << std::endl;
2552
2553 outfile << "\t<hour>" << time.hour << "</hour>" << std::endl;
2554 outfile << "\t<minute>" << time.minute << "</minute>" << std::endl;
2555 outfile << "\t<second>" << time.second << "</second>" << std::endl;
2556
2557 outfile << " </time>" << std::endl;
2558
2559 // -- primitives -- //
2560
2561 for (uint UUID: UUIDs) {
2562 uint p = UUID;
2563
2564 if (!doesPrimitiveExist(p)) {
2565 if (doesObjectExist(p)) {
2566 helios_runtime_error("ERROR (Context::writeXML): Primitive with UUID of " + std::to_string(p) + " does not exist. There is a compound object with this ID - did you mean to call Context::writeXML_byobject()?");
2567 } else {
2568 helios_runtime_error("ERROR (Context::writeXML): Primitive with UUID of " + std::to_string(p) + " does not exist.");
2569 }
2570 }
2571
2572 Primitive *prim = getPrimitivePointer_private(p);
2573
2574 uint parent_objID = prim->getParentObjectID();
2575
2576 RGBAcolor color = prim->getColorRGBA();
2577
2578 std::string texture_file = prim->getTextureFile();
2579
2580 std::vector<std::string> pdata = prim->listPrimitiveData();
2581
2582 // if this primitive is a member of a compound object that is "complete", don't write it to XML
2583 //\todo This was included to make the XML files more efficient and avoid writing all object primitives to file. However, it doesn't work in some cases because it makes it hard to figure out the primitive transformations.
2584 // if( parent_objID>0 && areObjectPrimitivesComplete(parent_objID) ){
2585 // continue;
2586 // }
2587
2588 if (prim->getType() == PRIMITIVE_TYPE_PATCH) {
2589 outfile << " <patch>" << std::endl;
2590 } else if (prim->getType() == PRIMITIVE_TYPE_TRIANGLE) {
2591 outfile << " <triangle>" << std::endl;
2592 } else if (prim->getType() == PRIMITIVE_TYPE_VOXEL) {
2593 outfile << " <voxel>" << std::endl;
2594 }
2595
2596 outfile << "\t<UUID>" << p << "</UUID>" << std::endl;
2597
2598 if (parent_objID > 0) {
2599 outfile << "\t<objID>" << parent_objID << "</objID>" << std::endl;
2600 }
2601
2602 outfile << "\t<color>" << color.r << " " << color.g << " " << color.b << " " << color.a << "</color>" << std::endl;
2603 if (prim->hasTexture()) {
2604 outfile << "\t<texture>" << texture_file << "</texture>" << std::endl;
2605 }
2606
2607 if (!pdata.empty()) {
2608 writeDataToXMLstream("primitive", pdata, prim, outfile);
2609 }
2610
2611 // Patches
2612 if (prim->getType() == PRIMITIVE_TYPE_PATCH) {
2613 Patch *patch = getPatchPointer_private(p);
2614 float transform[16];
2615 prim->getTransformationMatrix(transform);
2616
2617 outfile << "\t<transform>";
2618 for (float i: transform) {
2619 outfile << i << " ";
2620 }
2621 outfile << "</transform>" << std::endl;
2622 std::vector<vec2> uv = patch->getTextureUV();
2623 if (!uv.empty()) {
2624 outfile << "\t<textureUV>" << std::flush;
2625 for (int i = 0; i < uv.size(); i++) {
2626 outfile << uv.at(i).x << " " << uv.at(i).y << std::flush;
2627 if (i != uv.size() - 1) {
2628 outfile << " " << std::flush;
2629 }
2630 }
2631 outfile << "</textureUV>" << std::endl;
2632 }
2634 outfile << "\t<solid_fraction>" << getPrimitiveSolidFraction(p) << "</solid_fraction>\n";
2635 }
2636 outfile << " </patch>" << std::endl;
2637
2638 // Triangles
2639 } else if (prim->getType() == PRIMITIVE_TYPE_TRIANGLE) {
2640 float transform[16];
2641 prim->getTransformationMatrix(transform);
2642
2643 outfile << "\t<transform>";
2644 for (float i: transform) {
2645 outfile << i << " ";
2646 }
2647 outfile << "</transform>" << std::endl;
2648
2649 std::vector<vec2> uv = getTrianglePointer_private(p)->getTextureUV();
2650 if (!uv.empty()) {
2651 outfile << "\t<textureUV>" << std::flush;
2652 for (int i = 0; i < uv.size(); i++) {
2653 outfile << uv.at(i).x << " " << uv.at(i).y << std::flush;
2654 if (i != uv.size() - 1) {
2655 outfile << " " << std::flush;
2656 }
2657 }
2658 outfile << "</textureUV>" << std::endl;
2659 }
2661 outfile << "\t<solid_fraction>" << getPrimitiveSolidFraction(p) << "</solid_fraction>\n";
2662 }
2663 outfile << " </triangle>" << std::endl;
2664
2665 // Voxels
2666 } else if (prim->getType() == PRIMITIVE_TYPE_VOXEL) {
2667 float transform[16];
2668 prim->getTransformationMatrix(transform);
2669
2670 outfile << "\t<transform>";
2671 for (float i: transform) {
2672 outfile << i << " ";
2673 }
2674 outfile << "</transform>" << std::endl;
2676 outfile << "\t<solid_fraction>" << getPrimitiveSolidFraction(p) << "</solid_fraction>\n";
2677 }
2678
2679 outfile << " </voxel>" << std::endl;
2680 }
2681 }
2682
2683 // -- objects -- //
2684
2685 for (auto o: objectIDs) {
2686 CompoundObject *obj = objects.at(o);
2687
2688 std::string texture_file = obj->getTextureFile();
2689
2690 std::vector<std::string> odata = obj->listObjectData();
2691
2692 if (obj->getObjectType() == OBJECT_TYPE_TILE) {
2693 outfile << " <tile>" << std::endl;
2694 } else if (obj->getObjectType() == OBJECT_TYPE_BOX) {
2695 outfile << " <box>" << std::endl;
2696 } else if (obj->getObjectType() == OBJECT_TYPE_CONE) {
2697 outfile << " <cone>" << std::endl;
2698 } else if (obj->getObjectType() == OBJECT_TYPE_DISK) {
2699 outfile << " <disk>" << std::endl;
2700 } else if (obj->getObjectType() == OBJECT_TYPE_SPHERE) {
2701 outfile << " <sphere>" << std::endl;
2702 } else if (obj->getObjectType() == OBJECT_TYPE_TUBE) {
2703 outfile << " <tube>" << std::endl;
2704 } else if (obj->getObjectType() == OBJECT_TYPE_POLYMESH) {
2705 outfile << " <polymesh>" << std::endl;
2706 }
2707
2708 outfile << "\t<objID>" << o << "</objID>" << std::endl;
2709 if (obj->hasTexture()) {
2710 outfile << "\t<texture>" << texture_file << "</texture>" << std::endl;
2711 }
2712
2713 if (!odata.empty()) {
2714 writeDataToXMLstream("object", odata, obj, outfile);
2715 }
2716
2717 std::vector<std::string> pdata_labels;
2718 std::vector<HeliosDataType> pdata_types;
2719 std::vector<uint> primitiveUUIDs = obj->getPrimitiveUUIDs();
2720 for (uint UUID: primitiveUUIDs) {
2721 std::vector<std::string> labels = getPrimitivePointer_private(UUID)->listPrimitiveData();
2722 for (const auto &label: labels) {
2723 if (find(pdata_labels.begin(), pdata_labels.end(), label) == pdata_labels.end()) {
2724 pdata_labels.push_back(label);
2725 pdata_types.push_back(getPrimitiveDataType(label.c_str()));
2726 }
2727 }
2728 }
2729 for (size_t l = 0; l < pdata_labels.size(); l++) {
2730 if (pdata_types.at(l) == HELIOS_TYPE_FLOAT) {
2731 outfile << "\t<primitive_data_float " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2732 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2733 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2734 std::vector<float> data;
2735 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2736 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2737 for (float i: data) {
2738 outfile << i << std::flush;
2739 }
2740 outfile << " </data>" << std::endl;
2741 }
2742 }
2743 outfile << "\t</primitive_data_float>" << std::endl;
2744 } else if (pdata_types.at(l) == HELIOS_TYPE_DOUBLE) {
2745 outfile << "\t<primitive_data_double " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2746 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2747 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2748 std::vector<double> data;
2749 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2750 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2751 for (double i: data) {
2752 outfile << i << std::flush;
2753 }
2754 outfile << " </data>" << std::endl;
2755 }
2756 }
2757 outfile << "\t</primitive_data_double>" << std::endl;
2758 } else if (pdata_types.at(l) == HELIOS_TYPE_UINT) {
2759 outfile << "\t<primitive_data_uint " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2760 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2761 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2762 std::vector<uint> data;
2763 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2764 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2765 for (unsigned int i: data) {
2766 outfile << i << std::flush;
2767 }
2768 outfile << " </data>" << std::endl;
2769 }
2770 }
2771 outfile << "\t</primitive_data_uint>" << std::endl;
2772 } else if (pdata_types.at(l) == HELIOS_TYPE_INT) {
2773 outfile << "\t<primitive_data_int " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2774 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2775 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2776 std::vector<int> data;
2777 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2778 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2779 for (int i: data) {
2780 outfile << i << std::flush;
2781 }
2782 outfile << " </data>" << std::endl;
2783 }
2784 }
2785 outfile << "\t</primitive_data_int>" << std::endl;
2786 } else if (pdata_types.at(l) == HELIOS_TYPE_INT2) {
2787 outfile << "\t<primitive_data_int2 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2788 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2789 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2790 std::vector<int2> data;
2791 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2792 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2793 for (auto &i: data) {
2794 outfile << i.x << " " << i.y << std::flush;
2795 }
2796 outfile << " </data>" << std::endl;
2797 }
2798 }
2799 outfile << "\t</primitive_data_int2>" << std::endl;
2800 } else if (pdata_types.at(l) == HELIOS_TYPE_INT3) {
2801 outfile << "\t<primitive_data_int3 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2802 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2803 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2804 std::vector<int3> data;
2805 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2806 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2807 for (auto &i: data) {
2808 outfile << i.x << " " << i.y << " " << i.z << std::flush;
2809 }
2810 outfile << " </data>" << std::endl;
2811 }
2812 }
2813 outfile << "\t</primitive_data_int3>" << std::endl;
2814 } else if (pdata_types.at(l) == HELIOS_TYPE_INT4) {
2815 outfile << "\t<primitive_data_int4 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2816 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2817 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2818 std::vector<int4> data;
2819 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2820 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2821 for (auto &i: data) {
2822 outfile << i.x << " " << i.y << " " << i.z << " " << i.w << std::flush;
2823 }
2824 outfile << " </data>" << std::endl;
2825 }
2826 }
2827 outfile << "\t</primitive_data_int4>" << std::endl;
2828 } else if (pdata_types.at(l) == HELIOS_TYPE_VEC2) {
2829 outfile << "\t<primitive_data_vec2 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2830 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2831 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2832 std::vector<vec2> data;
2833 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2834 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2835 for (auto &i: data) {
2836 outfile << i.x << " " << i.y << std::flush;
2837 }
2838 outfile << " </data>" << std::endl;
2839 }
2840 }
2841 outfile << "\t</primitive_data_vec2>" << std::endl;
2842 } else if (pdata_types.at(l) == HELIOS_TYPE_VEC3) {
2843 outfile << "\t<primitive_data_vec3 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2844 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2845 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2846 std::vector<vec3> data;
2847 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2848 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2849 for (auto &i: data) {
2850 outfile << i.x << " " << i.y << " " << i.z << std::flush;
2851 }
2852 outfile << " </data>" << std::endl;
2853 }
2854 }
2855 outfile << "\t</primitive_data_vec3>" << std::endl;
2856 } else if (pdata_types.at(l) == HELIOS_TYPE_VEC4) {
2857 outfile << "\t<primitive_data_vec4 " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2858 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2859 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2860 std::vector<vec4> data;
2861 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2862 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2863 for (auto &i: data) {
2864 outfile << i.x << " " << i.y << " " << i.z << " " << i.w << std::flush;
2865 }
2866 outfile << " </data>" << std::endl;
2867 }
2868 }
2869 outfile << "\t</primitive_data_vec4>" << std::endl;
2870 } else if (pdata_types.at(l) == HELIOS_TYPE_STRING) {
2871 outfile << "\t<primitive_data_string " << "label=\"" << pdata_labels.at(l) << "\">" << std::endl;
2872 for (size_t p = 0; p < primitiveUUIDs.size(); p++) {
2873 if (doesPrimitiveDataExist(primitiveUUIDs.at(p), pdata_labels.at(l).c_str())) {
2874 std::vector<std::string> data;
2875 getPrimitiveData(primitiveUUIDs.at(p), pdata_labels.at(l).c_str(), data);
2876 outfile << "\t\t<data label=\"" << p << "\"> " << std::flush;
2877 for (const auto &i: data) {
2878 outfile << i << std::flush;
2879 }
2880 outfile << " </data>" << std::endl;
2881 }
2882 }
2883 outfile << "\t</primitive_data_string>" << std::endl;
2884 }
2885 }
2886
2887 // Tiles
2888 if (obj->getObjectType() == OBJECT_TYPE_TILE) {
2889 Tile *tile = getTileObjectPointer(o);
2890
2891 float transform[16];
2892 tile->getTransformationMatrix(transform);
2893
2894 int2 subdiv = tile->getSubdivisionCount();
2895 outfile << "\t<subdivisions>" << subdiv.x << " " << subdiv.y << "</subdivisions>" << std::endl;
2896
2897 outfile << "\t<transform> ";
2898 for (float i: transform) {
2899 outfile << i << " ";
2900 }
2901 outfile << "</transform>" << std::endl;
2902
2903 outfile << " </tile>" << std::endl;
2904
2905 // Spheres
2906 } else if (obj->getObjectType() == OBJECT_TYPE_SPHERE) {
2907 Sphere *sphere = getSphereObjectPointer(o);
2908
2909 float transform[16];
2910 sphere->getTransformationMatrix(transform);
2911
2912 outfile << "\t<transform> ";
2913 for (float i: transform) {
2914 outfile << i << " ";
2915 }
2916 outfile << "</transform>" << std::endl;
2917
2918 uint subdiv = sphere->getSubdivisionCount();
2919 outfile << "\t<subdivisions> " << subdiv << " </subdivisions>" << std::endl;
2920
2921 outfile << " </sphere>" << std::endl;
2922
2923 // Tubes
2924 } else if (obj->getObjectType() == OBJECT_TYPE_TUBE) {
2925 Tube *tube = getTubeObjectPointer(o);
2926
2927 float transform[16];
2928 tube->getTransformationMatrix(transform);
2929
2930 outfile << "\t<transform> ";
2931 for (float i: transform) {
2932 outfile << i << " ";
2933 }
2934 outfile << "</transform>" << std::endl;
2935
2936 uint subdiv = tube->getSubdivisionCount();
2937 outfile << "\t<subdivisions> " << subdiv << " </subdivisions>" << std::endl;
2938
2939 std::vector<vec3> nodes = tube->getNodes();
2940 std::vector<float> radius = tube->getNodeRadii();
2941
2942 assert(nodes.size() == radius.size());
2943 outfile << "\t<nodes> " << std::endl;
2944 for (auto &node: nodes) {
2945 outfile << "\t\t" << node.x << " " << node.y << " " << node.z << std::endl;
2946 }
2947 outfile << "\t</nodes> " << std::endl;
2948 outfile << "\t<radius> " << std::endl;
2949 for (float radiu: radius) {
2950 outfile << "\t\t" << radiu << std::endl;
2951 }
2952 outfile << "\t</radius> " << std::endl;
2953
2954 if (texture_file.empty()) {
2955 std::vector<RGBcolor> colors = tube->getNodeColors();
2956
2957 outfile << "\t<color> " << std::endl;
2958 for (auto &color: colors) {
2959 outfile << "\t\t" << color.r << " " << color.g << " " << color.b << std::endl;
2960 }
2961 outfile << "\t</color> " << std::endl;
2962 }
2963
2964 outfile << " </tube>" << std::endl;
2965
2966 // Boxes
2967 } else if (obj->getObjectType() == OBJECT_TYPE_BOX) {
2968 Box *box = getBoxObjectPointer(o);
2969
2970 float transform[16];
2971 box->getTransformationMatrix(transform);
2972
2973 outfile << "\t<transform> ";
2974 for (float i: transform) {
2975 outfile << i << " ";
2976 }
2977 outfile << "</transform>" << std::endl;
2978
2979 int3 subdiv = box->getSubdivisionCount();
2980 outfile << "\t<subdivisions> " << subdiv.x << " " << subdiv.y << " " << subdiv.z << " </subdivisions>" << std::endl;
2981
2982 outfile << " </box>" << std::endl;
2983
2984 // Disks
2985 } else if (obj->getObjectType() == OBJECT_TYPE_DISK) {
2986 Disk *disk = getDiskObjectPointer(o);
2987
2988 float transform[16];
2989 disk->getTransformationMatrix(transform);
2990
2991 outfile << "\t<transform> ";
2992 for (float i: transform) {
2993 outfile << i << " ";
2994 }
2995 outfile << "</transform>" << std::endl;
2996
2997 int2 subdiv = disk->getSubdivisionCount();
2998 outfile << "\t<subdivisions> " << subdiv.x << " " << subdiv.y << " </subdivisions>" << std::endl;
2999
3000 outfile << " </disk>" << std::endl;
3001
3002 // Cones
3003 } else if (obj->getObjectType() == OBJECT_TYPE_CONE) {
3004 Cone *cone = getConeObjectPointer(o);
3005
3006 float transform[16];
3007 cone->getTransformationMatrix(transform);
3008
3009 outfile << "\t<transform> ";
3010 for (float i: transform) {
3011 outfile << i << " ";
3012 }
3013 outfile << "</transform>" << std::endl;
3014
3015 uint subdiv = cone->getSubdivisionCount();
3016 outfile << "\t<subdivisions> " << subdiv << " </subdivisions>" << std::endl;
3017
3018 std::vector<vec3> nodes = cone->getNodeCoordinates();
3019 std::vector<float> radius = cone->getNodeRadii();
3020
3021 assert(nodes.size() == radius.size());
3022 outfile << "\t<nodes> " << std::endl;
3023 for (auto &node: nodes) {
3024 outfile << "\t\t" << node.x << " " << node.y << " " << node.z << std::endl;
3025 }
3026 outfile << "\t</nodes> " << std::endl;
3027 outfile << "\t<radius> " << std::endl;
3028 for (float radiu: radius) {
3029 outfile << "\t\t" << radiu << std::endl;
3030 }
3031 outfile << "\t</radius> " << std::endl;
3032
3033 outfile << " </cone>" << std::endl;
3034
3035 // Polymesh
3036 } else if (obj->getObjectType() == OBJECT_TYPE_POLYMESH) {
3037 outfile << " </polymesh>" << std::endl;
3038 }
3039 }
3040
3041
3042 // -- global data -- //
3043
3044 for (const auto &iter: globaldata) {
3045 std::string label = iter.first;
3046 GlobalData data = iter.second;
3047 HeliosDataType type = data.type;
3048 if (type == HELIOS_TYPE_UINT) {
3049 outfile << " <globaldata_uint label=\"" << label << "\">" << std::flush;
3050 for (size_t i = 0; i < data.size; i++) {
3051 outfile << data.global_data_uint.at(i) << std::flush;
3052 if (i != data.size - 1) {
3053 outfile << " " << std::flush;
3054 }
3055 }
3056 outfile << "</globaldata_uint>" << std::endl;
3057 } else if (type == HELIOS_TYPE_INT) {
3058 outfile << " <globaldata_int label=\"" << label << "\">" << std::flush;
3059 for (size_t i = 0; i < data.size; i++) {
3060 outfile << data.global_data_int.at(i) << std::flush;
3061 if (i != data.size - 1) {
3062 outfile << " " << std::flush;
3063 }
3064 }
3065 outfile << "</globaldata_int>" << std::endl;
3066 } else if (type == HELIOS_TYPE_FLOAT) {
3067 outfile << " <globaldata_float label=\"" << label << "\">" << std::flush;
3068 for (size_t i = 0; i < data.size; i++) {
3069 outfile << data.global_data_float.at(i) << std::flush;
3070 if (i != data.size - 1) {
3071 outfile << " " << std::flush;
3072 }
3073 }
3074 outfile << "</globaldata_float>" << std::endl;
3075 } else if (type == HELIOS_TYPE_DOUBLE) {
3076 outfile << " <globaldata_double label=\"" << label << "\">" << std::flush;
3077 for (size_t i = 0; i < data.size; i++) {
3078 outfile << data.global_data_double.at(i) << std::flush;
3079 if (i != data.size - 1) {
3080 outfile << " " << std::flush;
3081 }
3082 }
3083 outfile << "</globaldata_double>" << std::endl;
3084 } else if (type == HELIOS_TYPE_VEC2) {
3085 outfile << " <globaldata_vec2 label=\"" << label << "\">" << std::flush;
3086 for (size_t i = 0; i < data.size; i++) {
3087 outfile << data.global_data_vec2.at(i).x << " " << data.global_data_vec2.at(i).y << std::flush;
3088 if (i != data.size - 1) {
3089 outfile << " " << std::flush;
3090 }
3091 }
3092 outfile << "</globaldata_vec2>" << std::endl;
3093 } else if (type == HELIOS_TYPE_VEC3) {
3094 outfile << " <globaldata_vec3 label=\"" << label << "\">" << std::flush;
3095 for (size_t i = 0; i < data.size; i++) {
3096 outfile << data.global_data_vec3.at(i).x << " " << data.global_data_vec3.at(i).y << " " << data.global_data_vec3.at(i).z << std::flush;
3097 if (i != data.size - 1) {
3098 outfile << " " << std::flush;
3099 }
3100 }
3101 outfile << "</globaldata_vec3>" << std::endl;
3102 } else if (type == HELIOS_TYPE_VEC4) {
3103 outfile << " <globaldata_vec4 label=\"" << label << "\">" << std::flush;
3104 for (size_t i = 0; i < data.size; i++) {
3105 outfile << data.global_data_vec4.at(i).x << " " << data.global_data_vec4.at(i).y << " " << data.global_data_vec4.at(i).z << " " << data.global_data_vec4.at(i).w << std::flush;
3106 if (i != data.size - 1) {
3107 outfile << " " << std::flush;
3108 }
3109 }
3110 outfile << "</globaldata_vec4>" << std::endl;
3111 } else if (type == HELIOS_TYPE_INT2) {
3112 outfile << " <globaldata_int2 label=\"" << label << "\">" << std::flush;
3113 for (size_t i = 0; i < data.size; i++) {
3114 outfile << data.global_data_int2.at(i).x << " " << data.global_data_int2.at(i).y << std::flush;
3115 if (i != data.size - 1) {
3116 outfile << " " << std::flush;
3117 }
3118 }
3119 outfile << "</globaldata_int2>" << std::endl;
3120 } else if (type == HELIOS_TYPE_INT3) {
3121 outfile << " <globaldata_int3 label=\"" << label << "\">" << std::flush;
3122 for (size_t i = 0; i < data.size; i++) {
3123 outfile << data.global_data_int3.at(i).x << " " << data.global_data_int3.at(i).y << data.global_data_int3.at(i).z << std::flush;
3124 if (i != data.size - 1) {
3125 outfile << " " << std::flush;
3126 }
3127 }
3128 outfile << "</globaldata_int3>" << std::endl;
3129 } else if (type == HELIOS_TYPE_INT4) {
3130 outfile << " <globaldata_int4 label=\"" << label << "\">" << std::flush;
3131 for (size_t i = 0; i < data.size; i++) {
3132 outfile << data.global_data_int4.at(i).x << " " << data.global_data_int4.at(i).y << data.global_data_int4.at(i).z << data.global_data_int4.at(i).w << std::flush;
3133 if (i != data.size - 1) {
3134 outfile << " " << std::flush;
3135 }
3136 }
3137 outfile << "</globaldata_int4>" << std::endl;
3138 } else if (type == HELIOS_TYPE_STRING) {
3139 outfile << " <globaldata_string label=\"" << label << "\">" << std::flush;
3140 for (size_t i = 0; i < data.size; i++) {
3141 outfile << data.global_data_string.at(i) << std::flush;
3142 if (i != data.size - 1) {
3143 outfile << " " << std::flush;
3144 }
3145 }
3146 outfile << "</globaldata_string>" << std::endl;
3147 }
3148 }
3149
3150 // -- timeseries -- //
3151
3152 for (const auto &iter: timeseries_data) {
3153 std::string label = iter.first;
3154
3155 std::vector<float> data = iter.second;
3156 std::vector<double> dateval = timeseries_datevalue.at(label);
3157
3158 assert(data.size() == dateval.size());
3159
3160 outfile << " <timeseries label=\"" << label << "\">" << std::endl;
3161
3162 for (size_t i = 0; i < data.size(); i++) {
3163 Date a_date = queryTimeseriesDate(label.c_str(), i);
3164 Time a_time = queryTimeseriesTime(label.c_str(), i);
3165
3166 outfile << "\t<datapoint>" << std::endl;
3167
3168 outfile << "\t <date>" << a_date.day << " " << a_date.month << " " << a_date.year << "</date>" << std::endl;
3169
3170 outfile << "\t <time>" << a_time.hour << " " << a_time.minute << " " << a_time.second << "</time>" << std::endl;
3171
3172 outfile << "\t <value>" << data.at(i) << "</value>" << std::endl;
3173
3174 outfile << "\t</datapoint>" << std::endl;
3175 }
3176
3177 outfile << " </timeseries>" << std::endl;
3178 }
3179
3180 // ----------------- //
3181
3182 outfile << "\n</helios>\n";
3183
3184 outfile.close();
3185
3186 if (!quiet) {
3187 std::cout << "done." << std::endl;
3188 }
3189}
3190
3191std::vector<uint> Context::loadPLY(const char *filename, bool silent) {
3192 return loadPLY(filename, nullorigin, 0, nullrotation, RGB::blue, "YUP", silent);
3193}
3194
3195std::vector<uint> Context::loadPLY(const char *filename, const vec3 &origin, float height, const std::string &upaxis, bool silent) {
3196 return loadPLY(filename, origin, height, make_SphericalCoord(0, 0), RGB::blue, upaxis, silent);
3197}
3198
3199std::vector<uint> Context::loadPLY(const char *filename, const vec3 &origin, float height, const SphericalCoord &rotation, const std::string &upaxis, bool silent) {
3200 return loadPLY(filename, origin, height, rotation, RGB::blue, upaxis, silent);
3201}
3202
3203std::vector<uint> Context::loadPLY(const char *filename, const vec3 &origin, float height, const RGBcolor &default_color, const std::string &upaxis, bool silent) {
3204 return loadPLY(filename, origin, height, make_SphericalCoord(0, 0), default_color, upaxis, silent);
3205}
3206
3207std::vector<uint> Context::loadPLY(const char *filename, const vec3 &origin, float height, const SphericalCoord &rotation, const RGBcolor &default_color, const std::string &upaxis, bool silent) {
3208 if (!silent) {
3209 std::cout << "Reading PLY file " << filename << "..." << std::flush;
3210 }
3211
3212 std::string fn = filename;
3213 std::string ext = getFileExtension(filename);
3214 if (ext != ".ply" && ext != ".PLY") {
3215 helios_runtime_error("ERROR (Context::loadPLY): File " + fn + " is not PLY format.");
3216 }
3217
3218 if (upaxis != "XUP" && upaxis != "YUP" && upaxis != "ZUP") {
3219 helios_runtime_error("ERROR (Context::loadPLY): " + upaxis + " is not a valid up-axis. Please specify a value of XUP, YUP, or ZUP.");
3220 }
3221
3222 std::string line, prop;
3223
3224 uint vertexCount = 0, faceCount = 0;
3225
3226 std::vector<vec3> vertices;
3227 std::vector<std::vector<int>> faces;
3228 std::vector<RGBcolor> colors;
3229 std::vector<std::string> properties;
3230
3231 bool ifColor = false;
3232
3233 // Resolve file path using unified resolution
3234 std::filesystem::path resolved_path = resolveFilePath(filename);
3235 std::string resolved_filename = resolved_path.string();
3236
3237 std::ifstream inputPly;
3238 inputPly.open(resolved_filename);
3239
3240 if (!inputPly.is_open()) {
3241 helios_runtime_error("ERROR (Context::loadPLY): Couldn't open " + std::string(filename));
3242 }
3243
3244 //--- read header info -----//
3245
3246 // first line should always be 'ply'
3247 inputPly >> line;
3248 if ("ply" != line) {
3249 helios_runtime_error("ERROR (Context::loadPLY): " + std::string(filename) + " is not a PLY file.");
3250 }
3251
3252 // read format
3253 inputPly >> line;
3254 if ("format" != line) {
3255 helios_runtime_error("ERROR (Context::loadPLY): could not determine data format of " + std::string(filename));
3256 }
3257
3258 inputPly >> line;
3259 if ("ascii" != line) {
3260 helios_runtime_error("ERROR (Context::loadPLY): Only ASCII data types are supported.");
3261 }
3262
3263 std::string temp_string;
3264
3265 while ("end_header" != line) {
3266 inputPly >> line;
3267
3268 if ("comment" == line) {
3269 getline(inputPly, line);
3270 } else if ("element" == line) {
3271 inputPly >> line;
3272
3273 if ("vertex" == line) {
3274 inputPly >> temp_string;
3275 if (!parse_uint(temp_string, vertexCount)) {
3276 helios_runtime_error("ERROR (Context::loadPLY): PLY file read failed. Vertex count value should be a non-negative integer.");
3277 }
3278 } else if ("face" == line) {
3279 inputPly >> temp_string;
3280 if (!parse_uint(temp_string, faceCount)) {
3281 helios_runtime_error("ERROR (Context::loadPLY): PLY file read failed. Face count value should be a non-negative integer.");
3282 }
3283 }
3284 } else if ("property" == line) {
3285 inputPly >> line; // type
3286
3287 if ("list" != line) {
3288 inputPly >> prop; // value
3289 properties.push_back(prop);
3290 }
3291 }
3292 }
3293
3294 for (auto &property: properties) {
3295 if (property == "red") {
3296 ifColor = true;
3297 }
3298 }
3299 if (!silent) {
3300 std::cout << "forming " << faceCount << " triangles..." << std::flush;
3301 }
3302
3303 vertices.resize(vertexCount);
3304 colors.resize(vertexCount);
3305 faces.resize(faceCount);
3306
3307
3308 //--- read vertices ----//
3309
3310 for (uint row = 0; row < vertexCount; row++) {
3311 for (auto &property: properties) {
3312 if (property == "x") {
3313 inputPly >> temp_string;
3314 float x;
3315 if (!parse_float(temp_string, x)) {
3316 helios_runtime_error("ERROR (Context::loadPLY): X value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3317 }
3318 if (upaxis == "XUP") {
3319 vertices.at(row).z = x;
3320 } else if (upaxis == "YUP") {
3321 vertices.at(row).y = x;
3322 } else if (upaxis == "ZUP") {
3323 vertices.at(row).x = x;
3324 }
3325 } else if (property == "y") {
3326 inputPly >> temp_string;
3327 float y;
3328 if (!parse_float(temp_string, y)) {
3329 helios_runtime_error("ERROR (Context::loadPLY): Y value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3330 }
3331 if (upaxis == "XUP") {
3332 vertices.at(row).x = y;
3333 } else if (upaxis == "YUP") {
3334 vertices.at(row).z = y;
3335 } else if (upaxis == "ZUP") {
3336 vertices.at(row).y = y;
3337 }
3338 } else if (property == "z") {
3339 inputPly >> temp_string;
3340 float z;
3341 if (!parse_float(temp_string, z)) {
3342 helios_runtime_error("ERROR (Context::loadPLY): Z value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3343 }
3344 if (upaxis == "XUP") {
3345 vertices.at(row).y = z;
3346 } else if (upaxis == "YUP") {
3347 vertices.at(row).x = z;
3348 } else if (upaxis == "ZUP") {
3349 vertices.at(row).z = z;
3350 }
3351 } else if (property == "red") {
3352 inputPly >> temp_string;
3353 if (!parse_float(temp_string, colors.at(row).r)) {
3354 helios_runtime_error("ERROR (Context::loadPLY): red color value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3355 }
3356 colors.at(row).r /= 255.f;
3357 } else if (property == "green") {
3358 inputPly >> temp_string;
3359 if (!parse_float(temp_string, colors.at(row).g)) {
3360 helios_runtime_error("ERROR (Context::loadPLY): green color value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3361 }
3362 colors.at(row).g /= 255.f;
3363 } else if (property == "blue") {
3364 inputPly >> temp_string;
3365 if (!parse_float(temp_string, colors.at(row).b)) {
3366 helios_runtime_error("ERROR (Context::loadPLY): blue color value for vertex " + std::to_string(row) + " is invalid and could not be read.");
3367 }
3368 colors.at(row).b /= 255.f;
3369 } else {
3370 inputPly >> line;
3371 }
3372 }
3373
3374 if (inputPly.eof()) {
3375 helios_runtime_error("ERROR (Context::loadPLY): Read past end of file while reading vertices. Vertex count specified in header may be incorrect.");
3376 }
3377 }
3378
3379 // determine bounding box
3380
3381 vec3 boxmin = make_vec3(10000, 10000, 10000);
3382 vec3 boxmax = make_vec3(-10000, -10000, -10000);
3383
3384 for (uint row = 0; row < vertexCount; row++) {
3385 if (vertices.at(row).x < boxmin.x) {
3386 boxmin.x = vertices.at(row).x;
3387 }
3388 if (vertices.at(row).y < boxmin.y) {
3389 boxmin.y = vertices.at(row).y;
3390 }
3391 if (vertices.at(row).z < boxmin.z) {
3392 boxmin.z = vertices.at(row).z;
3393 }
3394
3395 if (vertices.at(row).x > boxmax.x) {
3396 boxmax.x = vertices.at(row).x;
3397 }
3398 if (vertices.at(row).y > boxmax.y) {
3399 boxmax.y = vertices.at(row).y;
3400 }
3401 if (vertices.at(row).z > boxmax.z) {
3402 boxmax.z = vertices.at(row).z;
3403 }
3404 }
3405
3406 // center PLY object at `origin' and scale to have height `height'
3407 float scl = 1.f;
3408 if (height > 0.f) {
3409 scl = height / (boxmax.z - boxmin.z);
3410 }
3411 for (uint row = 0; row < vertexCount; row++) {
3412 vertices.at(row).z -= boxmin.z;
3413
3414 vertices.at(row).x *= scl;
3415 vertices.at(row).y *= scl;
3416 vertices.at(row).z *= scl;
3417
3418 vertices.at(row) = rotatePoint(vertices.at(row), rotation) + origin;
3419 }
3420
3421 //--- read faces ----//
3422
3423 uint v, ID;
3424 std::vector<uint> UUID;
3425 for (uint row = 0; row < faceCount; row++) {
3426 inputPly >> temp_string;
3427
3428 if (!parse_uint(temp_string, v)) {
3429 helios_runtime_error("ERROR (Context::loadPLY): Vertex count for face " + std::to_string(row) + " should be a non-negative integer.");
3430 }
3431
3432 faces.at(row).resize(v);
3433
3434 for (uint i = 0; i < v; i++) {
3435 inputPly >> temp_string;
3436 if (!parse_int(temp_string, faces.at(row).at(i))) {
3437 helios_runtime_error("ERROR (Context::loadPLY): Vertex index for face " + std::to_string(row) + " is invalid and could not be read.");
3438 }
3439 }
3440
3441 // Add triangles to context
3442
3443 for (uint t = 2; t < v; t++) {
3444 RGBcolor color;
3445 if (ifColor) {
3446 color = colors.at(faces.at(row).front());
3447 } else {
3448 color = default_color;
3449 }
3450
3451 vec3 v0 = vertices.at(faces.at(row).front());
3452 vec3 v1 = vertices.at(faces.at(row).at(t - 1));
3453 vec3 v2 = vertices.at(faces.at(row).at(t));
3454
3455 if ((v0 - v1).magnitude() < 1e-10f || (v0 - v2).magnitude() < 1e-10f || (v1 - v2).magnitude() < 1e-10f) {
3456 continue;
3457 }
3458
3459 // Additional check for triangle area to avoid near-degenerate triangles
3460 float triangle_area = calculateTriangleArea(v0, v1, v2);
3461 if (triangle_area < MIN_TRIANGLE_AREA_THRESHOLD) {
3462 continue;
3463 }
3464
3465 ID = addTriangle(v0, v1, v2, color);
3466
3467 UUID.push_back(ID);
3468 }
3469
3470 if (inputPly.eof()) {
3471 helios_runtime_error("ERROR (Context::loadPLY): Read past end of file while reading faces. Face count specified in header may be incorrect.");
3472 }
3473 }
3474
3475 if (!silent) {
3476 std::cout << "done." << std::endl;
3477 }
3478
3479 return UUID;
3480}
3481
3482void Context::writePLY(const char *filename) const {
3483 writePLY(filename, getAllUUIDs());
3484}
3485
3486void Context::writePLY(const char *filename, const std::vector<uint> &UUIDs) const {
3487 // Validate file name / extension
3488 std::string fname{filename ? filename : ""};
3489
3490 const auto dotPos = fname.find_last_of('.');
3491 const std::string ext = (dotPos != std::string::npos) ? fname.substr(dotPos) : "";
3492
3493 auto ciEqual = [](const char a, const char b) { return std::tolower(a) == std::tolower(b); };
3494 bool isPly = (ext.size() == 4) && ciEqual(ext[1], 'p') && ciEqual(ext[2], 'l') && ciEqual(ext[3], 'y');
3495
3496 if (!isPly) {
3497 helios_runtime_error("ERROR (Context::writePLY) Invalid file extension for " + fname + ". Expected a file ending in '.ply'.");
3498 }
3499
3500 // Try to open the output file
3501 std::ofstream PLYfile;
3502 PLYfile.open(fname, std::ios::out | std::ios::trunc);
3503
3504 if (!PLYfile.is_open()) {
3505 helios_runtime_error("ERROR (Context::writePLY) Unable to open " + fname + " for writing.");
3506 }
3507
3508 PLYfile << "ply" << std::endl << "format ascii 1.0" << std::endl << "comment Helios generated" << std::endl;
3509
3510 std::vector<int3> faces;
3511 std::vector<vec3> verts;
3512 std::vector<RGBcolor> colors;
3513
3514 size_t vertex_count = 0;
3515
3516 for (auto UUID: UUIDs) {
3517 std::vector<vec3> vertices = getPrimitivePointer_private(UUID)->getVertices();
3518 PrimitiveType type = getPrimitivePointer_private(UUID)->getType();
3519 RGBcolor C = getPrimitivePointer_private(UUID)->getColor();
3520 C.scale(255.f);
3521
3522 if (type == PRIMITIVE_TYPE_TRIANGLE) {
3523 faces.push_back(make_int3((int) vertex_count, (int) vertex_count + 1, (int) vertex_count + 2));
3524 for (int i = 0; i < 3; i++) {
3525 verts.push_back(vertices.at(i));
3526 colors.push_back(C);
3527 vertex_count++;
3528 }
3529 } else if (type == PRIMITIVE_TYPE_PATCH) {
3530 faces.push_back(make_int3((int) vertex_count, (int) vertex_count + 1, (int) vertex_count + 2));
3531 faces.push_back(make_int3((int) vertex_count, (int) vertex_count + 2, (int) vertex_count + 3));
3532 for (int i = 0; i < 4; i++) {
3533 verts.push_back(vertices.at(i));
3534 colors.push_back(C);
3535 vertex_count++;
3536 }
3537 }
3538 }
3539
3540 PLYfile << "element vertex " << verts.size() << std::endl;
3541 PLYfile << "property float x" << std::endl << "property float y" << std::endl << "property float z" << std::endl;
3542 PLYfile << "property uchar red" << std::endl << "property uchar green" << std::endl << "property uchar blue" << std::endl;
3543 PLYfile << "element face " << faces.size() << std::endl;
3544 PLYfile << "property list uchar int vertex_indices" << std::endl << "end_header" << std::endl;
3545
3546 for (size_t v = 0; v < verts.size(); v++) {
3547 PLYfile << verts.at(v).x << " " << verts.at(v).y << " " << verts.at(v).z << " " << round(colors.at(v).r) << " " << round(colors.at(v).g) << " " << round(colors.at(v).b) << std::endl;
3548 }
3549
3550 for (auto &face: faces) {
3551 PLYfile << "3 " << face.x << " " << face.y << " " << face.z << std::endl;
3552 }
3553
3554 PLYfile.close();
3555}
3556
3557std::vector<uint> Context::loadOBJ(const char *filename, bool silent) {
3558 return loadOBJ(filename, nullorigin, 0, nullrotation, RGB::blue, "ZUP", silent);
3559}
3560
3561std::vector<uint> Context::loadOBJ(const char *filename, const vec3 &origin, float height, const SphericalCoord &rotation, const RGBcolor &default_color, bool silent) {
3562 return loadOBJ(filename, origin, make_vec3(0, 0, height), rotation, default_color, "ZUP", silent);
3563}
3564
3565std::vector<uint> Context::loadOBJ(const char *filename, const vec3 &origin, float height, const SphericalCoord &rotation, const RGBcolor &default_color, const char *upaxis, bool silent) {
3566 return loadOBJ(filename, origin, make_vec3(0, 0, height), rotation, default_color, upaxis, silent);
3567}
3568
3569std::vector<uint> Context::loadOBJ(const char *filename, const vec3 &origin, const helios::vec3 &scale, const SphericalCoord &rotation, const RGBcolor &default_color, const char *upaxis, bool silent) {
3570
3571 if (!silent) {
3572 std::cout << "Reading OBJ file " << filename << "..." << std::flush;
3573 }
3574
3575 std::string fn = filename;
3576 std::string ext = getFileExtension(filename);
3577 if (ext != ".obj" && ext != ".OBJ") {
3578 helios_runtime_error("ERROR (Context::loadOBJ): File " + fn + " is not OBJ format.");
3579 }
3580
3581 if (strcmp(upaxis, "XUP") != 0 && strcmp(upaxis, "YUP") != 0 && strcmp(upaxis, "ZUP") != 0) {
3582 helios_runtime_error("ERROR (Context::loadOBJ): Up axis of " + std::string(upaxis) + " is not valid. Should be one of 'XUP', 'YUP', or 'ZUP'.");
3583 }
3584
3585 std::string line, prop;
3586
3587 std::vector<vec3> vertices;
3588 std::vector<std::string> objects;
3589 std::vector<vec2> texture_uv;
3590 std::map<std::string, std::vector<std::vector<int>>> face_inds, texture_inds;
3591
3592 std::map<std::string, OBJmaterial> materials;
3593
3594 std::vector<uint> UUID;
3595
3596 // Resolve file path using unified resolution
3597 std::filesystem::path resolved_path = resolveFilePath(filename);
3598 std::string resolved_filename = resolved_path.string();
3599
3600 std::ifstream inputOBJ, inputMTL;
3601 inputOBJ.open(resolved_filename);
3602
3603 if (!inputOBJ.is_open()) {
3604 helios_runtime_error("ERROR (Context::loadOBJ): Couldn't open " + std::string(filename));
3605 }
3606
3607 // determine the base file path for resolved filename
3608 std::string filebase = getFilePath(resolved_filename);
3609
3610 // determine bounding box
3611 float boxmin = 100000;
3612 float boxmax = -100000;
3613
3614 std::string current_material = "none";
3615 std::string current_object = "none";
3616
3617 size_t lineno = 0;
3618 while (inputOBJ.good()) {
3619 lineno++;
3620
3621 inputOBJ >> line;
3622
3623 // ------- COMMENTS --------- //
3624 if (line == "#") {
3625 getline(inputOBJ, line);
3626
3627 // ------- MATERIAL LIBRARY ------- //
3628 } else if (line == "mtllib") {
3629 getline(inputOBJ, line);
3630 std::string material_file = trim_whitespace(line);
3631 materials = loadMTL(filebase, material_file);
3632
3633 // ------- OBJECT ------- //
3634 } else if (line == "o") {
3635 getline(inputOBJ, line);
3636 current_object = trim_whitespace(line);
3637
3638 // ------- VERTICES --------- //
3639 } else if (line == "v") {
3640 getline(inputOBJ, line);
3641 // parse vertices into points
3642 vec3 verts(string2vec3(line.c_str()));
3643 vertices.emplace_back(verts);
3644 objects.emplace_back(current_object);
3645
3646 if (verts.z < boxmin) {
3647 boxmin = verts.z;
3648 }
3649 if (verts.z > boxmax) {
3650 boxmax = verts.z;
3651 }
3652
3653 // ------- TEXTURE COORDINATES --------- //
3654 } else if (line == "vt") {
3655 getline(inputOBJ, line);
3656 line = trim_whitespace(line);
3657 // parse coordinates into uv
3658 vec2 uv(string2vec2(line.c_str()));
3659 texture_uv.emplace_back(uv);
3660
3661 // ------- MATERIALS --------- //
3662 } else if (line == "usemtl") {
3663 getline(inputOBJ, line);
3664 current_material = trim_whitespace(line);
3665
3666 // ------- FACES --------- //
3667 } else if (line == "f") {
3668 getline(inputOBJ, line);
3669 // parse face vertices
3670 std::istringstream stream(line);
3671 std::string tmp, digitf, digitu;
3672 std::vector<int> f, u;
3673 while (stream.good()) {
3674 stream >> tmp;
3675
3676 digitf = "";
3677 int ic = 0;
3678 for (char i: tmp) {
3679 if (isdigit(i)) {
3680 digitf.push_back(i);
3681 ic++;
3682 } else {
3683 break;
3684 }
3685 }
3686
3687 digitu = "";
3688 for (int i = ic + 1; i < tmp.size(); i++) {
3689 if (isdigit(tmp[i])) {
3690 digitu.push_back(tmp[i]);
3691 } else {
3692 break;
3693 }
3694 }
3695
3696 if (!digitf.empty()) {
3697 int face;
3698 if (!parse_int(digitf, face)) {
3699 helios_runtime_error("ERROR (Context::loadOBJ): Face index on line " + std::to_string(lineno) + " must be a non-negative integer value.");
3700 }
3701 // Add bounds checking for face indices
3702 if (face <= 0 || face > vertices.size()) {
3703 helios_runtime_error("ERROR (Context::loadOBJ): Face vertex index " + std::to_string(face) + " on line " + std::to_string(lineno) + " is out of range. Valid range is 1-" + std::to_string(vertices.size()) +
3704 ". Check that vertex indices in face definitions reference existing vertices.");
3705 }
3706 f.push_back(face);
3707 }
3708 if (!digitu.empty()) {
3709 int uv;
3710 if (!parse_int(digitu, uv)) {
3711 helios_runtime_error("ERROR (Context::loadOBJ): u,v index on line " + std::to_string(lineno) + " must be a non-negative integer value.");
3712 }
3713 // Add bounds checking for UV indices
3714 if (uv <= 0 || uv > texture_uv.size()) {
3715 helios_runtime_error("ERROR (Context::loadOBJ): Texture coordinate index " + std::to_string(uv) + " on line " + std::to_string(lineno) + " is out of range. Valid range is 1-" + std::to_string(texture_uv.size()) +
3716 ". Check that texture coordinate indices in face definitions reference existing texture coordinates.");
3717 }
3718 u.push_back(uv);
3719 }
3720 }
3721 face_inds[current_material].push_back(f);
3722 texture_inds[current_material].push_back(u);
3723
3724 // ------ OTHER STUFF --------- //
3725 } else {
3726 getline(inputOBJ, line);
3727 }
3728 }
3729
3730 vec3 scl = scale;
3731 if (scl.x == 0 && scl.y == 0 && scl.z > 0) {
3732 if (boxmax - boxmin > 1e-6f) {
3733 scl = make_vec3(scale.z / (boxmax - boxmin), scale.z / (boxmax - boxmin), scale.z / (boxmax - boxmin));
3734 } else {
3735 // Object is flat or has zero height - use uniform scaling of requested height
3736 scl = make_vec3(scale.z, scale.z, scale.z);
3737 }
3738 } else {
3739 if (scl.x == 0 && (scl.y != 0 || scl.z != 0)) {
3740 std::cout << "WARNING (Context::loadOBJ): Scaling factor given for x-direction is zero. Setting scaling factor to 1" << std::endl;
3741 }
3742 if (scl.y == 0 && (scl.x != 0 || scl.z != 0)) {
3743 std::cout << "WARNING (Context::loadOBJ): Scaling factor given for y-direction is zero. Setting scaling factor to 1" << std::endl;
3744 }
3745 if (scl.z == 0 && (scl.x != 0 || scl.y != 0)) {
3746 std::cout << "WARNING (Context::loadOBJ): Scaling factor given for z-direction is zero. Setting scaling factor to 1" << std::endl;
3747 }
3748
3749 if (scl.x == 0) {
3750 scl.x = 1;
3751 }
3752 if (scl.y == 0) {
3753 scl.y = 1;
3754 }
3755 if (scl.z == 0) {
3756 scl.z = 1;
3757 }
3758 }
3759
3760 // Structure to hold triangle data for parallel processing
3761 struct TriangleData {
3762 vec3 vert0, vert1, vert2;
3763 std::string texture;
3764 vec2 uv0, uv1, uv2;
3765 RGBcolor color;
3766 bool hasTexture;
3767 bool textureColorIsOverridden;
3768 std::string object;
3769 };
3770
3771 std::vector<TriangleData> triangleDataList;
3772
3773 // First pass: Parallel data preparation - compute all triangle vertex data
3774 for (auto iter = face_inds.begin(); iter != face_inds.end(); ++iter) {
3775 std::string materialname = iter->first;
3776
3777 std::string texture;
3778 RGBcolor color = default_color;
3779 bool textureColorIsOverridden = false;
3780
3781 if (materials.find(materialname) != materials.end()) {
3782 const OBJmaterial &mat = materials.at(materialname);
3783
3784 texture = mat.texture;
3785 color = mat.color;
3786 textureColorIsOverridden = mat.textureColorIsOverridden;
3787 }
3788
3789
3790 const auto &material_faces = face_inds.at(materialname);
3791 const auto &material_texture_inds = texture_inds.count(materialname) ? texture_inds.at(materialname) : std::vector<std::vector<int>>();
3792
3793 // Exception handling for OpenMP - capture exceptions and rethrow after parallel region
3794 std::string exception_message;
3795 bool exception_occurred = false;
3796
3797#ifdef USE_OPENMP
3798#pragma omp parallel for schedule(dynamic)
3799#endif
3800 for (int i = 0; i < static_cast<int>(material_faces.size()); i++) {
3801 try {
3802 for (uint t = 2; t < material_faces[i].size(); t++) {
3803 vec3 v0 = vertices.at(material_faces[i][0] - 1);
3804 vec3 v1 = vertices.at(material_faces[i][t - 1] - 1);
3805 vec3 v2 = vertices.at(material_faces[i][t] - 1);
3806
3807 if ((v0 - v1).magnitude() == 0 || (v0 - v2).magnitude() == 0 || (v1 - v2).magnitude() == 0) {
3808 continue;
3809 }
3810
3811 if (strcmp(upaxis, "YUP") == 0) {
3812 v0 = rotatePointAboutLine(v0, make_vec3(0, 0, 0), make_vec3(1, 0, 0), 0.5 * M_PI);
3813 v1 = rotatePointAboutLine(v1, make_vec3(0, 0, 0), make_vec3(1, 0, 0), 0.5 * M_PI);
3814 v2 = rotatePointAboutLine(v2, make_vec3(0, 0, 0), make_vec3(1, 0, 0), 0.5 * M_PI);
3815 }
3816
3817 v0 = rotatePoint(v0, rotation);
3818 v1 = rotatePoint(v1, rotation);
3819 v2 = rotatePoint(v2, rotation);
3820
3821 // Calculate final triangle vertices after transformations
3822 vec3 vert0 = origin + make_vec3(v0.x * scl.x, v0.y * scl.y, v0.z * scl.z);
3823 vec3 vert1 = origin + make_vec3(v1.x * scl.x, v1.y * scl.y, v1.z * scl.z);
3824 vec3 vert2 = origin + make_vec3(v2.x * scl.x, v2.y * scl.y, v2.z * scl.z);
3825
3826 // Check if triangle has sufficient area to avoid zero-area triangles
3827 float triangle_area = calculateTriangleArea(vert0, vert1, vert2);
3828
3829 if (triangle_area > MIN_TRIANGLE_AREA_THRESHOLD) { // Only process triangle if area is not negligible
3830 TriangleData triangleData;
3831 triangleData.vert0 = vert0;
3832 triangleData.vert1 = vert1;
3833 triangleData.vert2 = vert2;
3834 triangleData.texture = texture;
3835 triangleData.color = color;
3836 triangleData.textureColorIsOverridden = textureColorIsOverridden;
3837 triangleData.object = objects.at(material_faces[i][0] - 1);
3838
3839 // Handle texture coordinates if present
3840 // First check if material has texture file
3841 triangleData.hasTexture = !texture.empty();
3842
3843
3844 // If texture exists, try to get UV coordinates
3845 if (triangleData.hasTexture && i < material_texture_inds.size() && !material_texture_inds[i].empty() && t < material_texture_inds[i].size()) {
3846
3847 int iuv0 = material_texture_inds[i][0] - 1;
3848 int iuv1 = material_texture_inds[i][t - 1] - 1;
3849 int iuv2 = material_texture_inds[i][t] - 1;
3850
3851 if (iuv0 >= 0 && iuv0 < texture_uv.size() && iuv1 >= 0 && iuv1 < texture_uv.size() && iuv2 >= 0 && iuv2 < texture_uv.size()) {
3852 triangleData.uv0 = texture_uv.at(iuv0);
3853 triangleData.uv1 = texture_uv.at(iuv1);
3854 triangleData.uv2 = texture_uv.at(iuv2);
3855 } else {
3856 helios_runtime_error("ERROR (Context::loadOBJ): Invalid texture coordinate indices in face for material '" + materialname + "'. " + "UV indices [" + std::to_string(iuv0 + 1) + ", " + std::to_string(iuv1 + 1) + ", " +
3857 std::to_string(iuv2 + 1) + "] " + "exceed available UV coordinates (1-" + std::to_string(texture_uv.size()) + "). " +
3858 "Check that all face texture coordinate references in the OBJ file are valid.");
3859 }
3860 } else if (triangleData.hasTexture) {
3861 helios_runtime_error("ERROR (Context::loadOBJ): Material '" + materialname + "' specifies texture file '" + texture + "' " + "but face has no texture coordinates. Either remove the texture from the material " +
3862 "or add texture coordinates (vt) and face texture indices (f v1/vt1 v2/vt2 v3/vt3) to the OBJ file.");
3863 }
3864
3865#ifdef USE_OPENMP
3866#pragma omp critical
3867#endif
3868 {
3869 triangleDataList.push_back(triangleData);
3870 }
3871 }
3872 }
3873 } catch (const std::exception &e) {
3874 // Capture exception in OpenMP-safe way
3875#ifdef USE_OPENMP
3876#pragma omp critical
3877#endif
3878 {
3879 if (!exception_occurred) {
3880 exception_message = e.what();
3881 exception_occurred = true;
3882 }
3883 }
3884 }
3885 }
3886
3887 // Rethrow captured exception after parallel region
3888 if (exception_occurred) {
3889 helios_runtime_error(exception_message);
3890 }
3891 }
3892
3893 // Second pass: Sequential triangle creation to maintain thread safety
3894 for (const auto &triangleData: triangleDataList) {
3895 uint ID = 0;
3896
3897 if (triangleData.hasTexture) {
3898 ID = addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.texture.c_str(), triangleData.uv0, triangleData.uv1, triangleData.uv2);
3899
3900 if (triangleData.textureColorIsOverridden) {
3901 setPrimitiveColor(ID, triangleData.color);
3903 }
3904 } else {
3905 ID = addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.color);
3906 }
3907
3908 UUID.push_back(ID);
3909
3910 if (triangleData.object != "none" && doesPrimitiveExist(ID)) {
3911 setPrimitiveData(ID, "object_label", triangleData.object);
3912 }
3913 }
3914
3915 if (!silent) {
3916 std::cout << "done." << std::endl;
3917 }
3918
3919 return UUID;
3920}
3921
3922std::map<std::string, Context::OBJmaterial> Context::loadMTL(const std::string &filebase, const std::string &material_file) {
3923 std::ifstream inputMTL;
3924
3925 std::string file = material_file;
3926
3927 // For relative paths, resolve relative to the OBJ file's directory (filebase)
3928 // For absolute paths, use unified file resolution
3929 std::filesystem::path resolved_path;
3930
3931 if (std::filesystem::path(file).is_absolute()) {
3932 // Absolute path - use unified resolution
3933 resolved_path = resolveFilePath(file);
3934 } else {
3935 // Relative path - resolve relative to OBJ file directory
3936 std::filesystem::path mtl_path = std::filesystem::path(filebase) / file;
3937 resolved_path = resolveFilePath(mtl_path.string());
3938 }
3939
3940 std::string resolved_file = resolved_path.string();
3941 inputMTL.open(resolved_file.c_str());
3942
3943 if (!inputMTL.is_open()) {
3944 helios_runtime_error("ERROR (Context::loadMTL): Could not open material file " + resolved_file + " after successful path resolution.");
3945 }
3946
3947 std::map<std::string, OBJmaterial> materials;
3948
3949 std::string line;
3950
3951 inputMTL >> line;
3952
3953 while (inputMTL.good()) {
3954 if (strcmp("#", line.c_str()) == 0) { // comments
3955 getline(inputMTL, line);
3956 inputMTL >> line;
3957 } else if (line == "newmtl") { // material library
3958 getline(inputMTL, line);
3959 std::string material_name = trim_whitespace(line);
3960 OBJmaterial mat(RGB::red, "", 0);
3961 materials.emplace(material_name, mat);
3962
3963 std::string map_Kd, map_d;
3964
3965 while (line != "newmtl" && inputMTL.good()) {
3966 inputMTL >> line;
3967
3968 if (line == "newmtl") {
3969 break;
3970 } else if (line == "map_a" || line == "map_Ka" || line == "Ks" || line == "Ka" || line == "map_Ks") {
3971 getline(inputMTL, line);
3972 } else if (line == "map_Kd" || line == "map_d") {
3973 std::string maptype = line;
3974 getline(inputMTL, line);
3975 line = trim_whitespace(line);
3976 std::istringstream stream(line);
3977 std::string tmp;
3978 while (stream.good()) {
3979 stream >> tmp;
3980 std::string ext = getFileExtension(tmp);
3981 if (ext == ".png" || ext == ".PNG" || ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || ext == ".JPEG") {
3982 std::string texturefile = tmp;
3983
3984 // Check for texture file existence using filesystem operations (more efficient)
3985 std::filesystem::path texture_path = texturefile;
3986 bool texture_exists = false;
3987
3988 // First try the path as given in MTL file
3989 if (std::filesystem::exists(texture_path)) {
3990 texture_exists = true;
3991 } else {
3992 // Try looking in the same directory where OBJ file is located
3993 texture_path = std::filesystem::path(filebase) / tmp;
3994 texturefile = texture_path.string();
3995 if (std::filesystem::exists(texture_path)) {
3996 texture_exists = true;
3997 }
3998 }
3999
4000 if (!texture_exists) {
4001 helios_runtime_error("ERROR (Context::loadOBJ): Texture file '" + tmp + "' referenced in .mtl file cannot be found. " + "Searched in current directory and OBJ file directory (" + filebase + "). " +
4002 "Ensure texture file exists or remove texture reference from material.");
4003 }
4004
4005 if (maptype == "map_d") {
4006 map_d = texturefile;
4007 } else {
4008 map_Kd = texturefile;
4009 }
4010 }
4011 }
4012 } else if (line == "Kd") {
4013 getline(inputMTL, line);
4014 std::string color_str = trim_whitespace(line);
4015 RGBAcolor color = string2RGBcolor(color_str.c_str());
4016 materials.at(material_name).color = make_RGBcolor(color.r, color.g, color.b);
4017 } else {
4018 getline(inputMTL, line);
4019 }
4020 }
4021
4022 if (!map_Kd.empty()) {
4023 materials.at(material_name).texture = map_Kd;
4024 if (!map_d.empty() && map_d != map_Kd) {
4025 materials.at(material_name).textureHasTransparency = true;
4026 }
4027 } else if (!map_d.empty()) {
4028 materials.at(material_name).texture = map_d;
4029 materials.at(material_name).textureColorIsOverridden = true;
4030 }
4031 } else {
4032 getline(inputMTL, line);
4033 inputMTL >> line;
4034 }
4035 }
4036
4037 return materials;
4038}
4039
4040void Context::writeOBJ(const std::string &filename, bool write_normals, bool silent) const {
4041 writeOBJ(filename, getAllUUIDs(), {}, write_normals, silent);
4042}
4043
4044void Context::writeOBJ(const std::string &filename, const std::vector<uint> &UUIDs, bool write_normals, bool silent) const {
4045 writeOBJ(filename, UUIDs, {}, write_normals, silent);
4046}
4047
4048void Context::writeOBJ(const std::string &filename, const std::vector<uint> &UUIDs, const std::vector<std::string> &primitive_dat_fields, bool write_normals, bool silent) const {
4049
4050 if (UUIDs.empty()) {
4051 std::cout << "WARNING (Context::writeOBJ): No primitives found to write - OBJ file " << filename << " will not be written." << std::endl;
4052 return;
4053 }
4054 if (filename.empty()) {
4055 std::cout << "WARNING (Context::writeOBJ): Filename was empty - OBJ file " << filename << " will not be written." << std::endl;
4056 return;
4057 }
4058
4059 std::string objfilename = filename;
4060 std::string mtlfilename = filename;
4061
4062 auto file_extension = getFileExtension(filename);
4063 auto file_stem = getFileStem(filename);
4064 auto file_path = getFilePath(filename);
4065
4066 if (file_extension != ".obj" && file_extension != ".OBJ") { // append obj to file name
4067 objfilename.append(".obj");
4068 mtlfilename.append(".mtl");
4069 } else {
4070 if (!file_path.empty()) {
4071 std::filesystem::path mtl_path = std::filesystem::path(file_path) / (file_stem + ".mtl");
4072 mtlfilename = mtl_path.string();
4073 } else {
4074 mtlfilename = file_stem + ".mtl";
4075 }
4076 }
4077
4078 if (!file_path.empty() && !std::filesystem::exists(file_path)) {
4079 if (!std::filesystem::create_directory(file_path)) {
4080 std::cerr << "failed. Directory " << file_path << " does not exist and it could not be created - OBJ file will not be written." << std::endl;
4081 return;
4082 }
4083 }
4084
4085 if (!silent) {
4086 std::cout << "Writing OBJ file " << objfilename << "..." << std::flush;
4087 }
4088
4089 std::vector<OBJmaterial> materials;
4090 std::unordered_map<std::string, uint> material_cache;
4091 const size_t primitive_count = UUIDs.size();
4092 const size_t estimated_vertices = primitive_count * 4;
4093
4094 std::vector<vec3> verts;
4095 verts.reserve(estimated_vertices);
4096 std::vector<vec3> normals;
4097 if (write_normals) {
4098 normals.reserve(primitive_count);
4099 }
4100 std::vector<vec2> uv;
4101 uv.reserve(estimated_vertices);
4102
4103 std::map<uint, std::vector<int3>> faces;
4104 std::map<uint, std::vector<int>> normal_inds;
4105 std::map<uint, std::vector<int3>> uv_inds;
4106 size_t vertex_count = 1;
4107 size_t normal_count = 0;
4108 size_t uv_count = 1;
4109 std::map<uint, std::vector<uint>> UUIDs_write;
4110
4111 std::map<std::string, std::map<uint, std::vector<int3>>> object_faces;
4112 std::map<std::string, std::map<uint, std::vector<int>>> object_normal_inds;
4113 std::map<std::string, std::map<uint, std::vector<int3>>> object_uv_inds;
4114 std::vector<std::string> object_order;
4115 object_order.reserve(primitive_count / 10);
4116 bool object_groups_found = false;
4117
4118 for (size_t p: UUIDs) {
4119 if (!doesPrimitiveExist(p)) {
4120 std::ostringstream err_stream;
4121 err_stream << "ERROR (Context::writeOBJ): Primitive with UUID " << p << " does not exist. "
4122 << "Ensure all UUIDs in the input vector correspond to valid primitives before calling writeOBJ.";
4123 helios_runtime_error(err_stream.str());
4124 }
4125
4126 const Primitive *prim_ptr = getPrimitivePointer_private(p);
4127
4128 if (prim_ptr->getType() == PRIMITIVE_TYPE_VOXEL) {
4129 std::ostringstream err_stream;
4130 err_stream << "ERROR (Context::writeOBJ): Voxel primitives (UUID " << p << ") cannot be written to OBJ format. "
4131 << "OBJ format only supports surface primitives (triangles, patches). "
4132 << "Filter out voxel primitives before calling writeOBJ.";
4133 helios_runtime_error(err_stream.str());
4134 }
4135
4136 std::vector<vec3> vertices = prim_ptr->getVertices();
4137 PrimitiveType type = prim_ptr->getType();
4138 RGBcolor C = prim_ptr->getColor();
4139 std::string texturefile = prim_ptr->getTextureFile();
4140 bool texture_color_overridden = prim_ptr->isTextureColorOverridden();
4141
4142 std::string obj_label = "default";
4143 if (doesPrimitiveDataExist(p, "object_label")) {
4144 getPrimitiveData(p, "object_label", obj_label);
4145 object_groups_found = true;
4146 }
4147 if (object_faces.find(obj_label) == object_faces.end()) {
4148 object_faces[obj_label] = {};
4149 object_normal_inds[obj_label] = {};
4150 object_uv_inds[obj_label] = {};
4151 object_order.push_back(obj_label);
4152 }
4153
4154 std::string material_key = texturefile + "|" + std::to_string(C.r) + "," + std::to_string(C.g) + "," + std::to_string(C.b) + "|" + std::to_string(texture_color_overridden);
4155
4156 uint material_ID;
4157 auto material_iter = material_cache.find(material_key);
4158
4159 if (material_iter != material_cache.end()) {
4160 // Material exists in cache
4161 material_ID = material_iter->second;
4162 } else {
4163 // Create new material
4164 OBJmaterial mat(C, texturefile, materials.size());
4165 materials.emplace_back(mat);
4166 material_ID = mat.materialID;
4167
4169 materials.back().textureHasTransparency = true;
4170 }
4171 if (texture_color_overridden) {
4172 materials.back().textureColorIsOverridden = true;
4173 }
4174
4175 material_cache[material_key] = material_ID;
4176 }
4177
4178 if (!primitive_dat_fields.empty()) {
4179 UUIDs_write[material_ID].push_back(p);
4180 }
4181
4182 if (write_normals) {
4183 vec3 normal = getPrimitiveNormal(p);
4184 normals.push_back(normal);
4185 normal_count++;
4186 }
4187
4188 if (type == PRIMITIVE_TYPE_TRIANGLE) {
4189 int3 ftmp = make_int3((int) vertex_count, (int) vertex_count + 1, (int) vertex_count + 2);
4190 faces[material_ID].push_back(ftmp);
4191 object_faces[obj_label][material_ID].push_back(ftmp);
4192 for (int i = 0; i < 3; i++) {
4193 verts.push_back(vertices.at(i));
4194 vertex_count++;
4195 }
4196
4197 if (write_normals) {
4198 normal_inds[material_ID].push_back(static_cast<int>(normal_count));
4199 object_normal_inds[obj_label][material_ID].push_back(static_cast<int>(normal_count));
4200 }
4201
4202 std::vector<vec2> uv_v = getTrianglePointer_private(p)->getTextureUV();
4203 if (getTrianglePointer_private(p)->hasTexture()) {
4204 int3 tuv = make_int3((int) uv_count, (int) uv_count + 1, (int) uv_count + 2);
4205 uv_inds[material_ID].push_back(tuv);
4206 object_uv_inds[obj_label][material_ID].push_back(tuv);
4207 for (int i = 0; i < 3; i++) {
4208 uv.push_back(uv_v.at(i));
4209 uv_count++;
4210 }
4211 } else {
4212 int3 tuv = make_int3(-1, -1, -1);
4213 uv_inds[material_ID].push_back(tuv);
4214 object_uv_inds[obj_label][material_ID].push_back(tuv);
4215 }
4216 } else if (type == PRIMITIVE_TYPE_PATCH) {
4217 int3 ftmp1 = make_int3((int) vertex_count, (int) vertex_count + 1, (int) vertex_count + 2);
4218 int3 ftmp2 = make_int3((int) vertex_count, (int) vertex_count + 2, (int) vertex_count + 3);
4219 faces[material_ID].push_back(ftmp1);
4220 faces[material_ID].push_back(ftmp2);
4221 object_faces[obj_label][material_ID].push_back(ftmp1);
4222 object_faces[obj_label][material_ID].push_back(ftmp2);
4223 for (int i = 0; i < 4; i++) {
4224 verts.push_back(vertices.at(i));
4225 vertex_count++;
4226 }
4227 std::vector<vec2> uv_v;
4228 uv_v = getPatchPointer_private(p)->getTextureUV();
4229
4230 if (write_normals) {
4231 normal_inds[material_ID].push_back(static_cast<int>(normal_count));
4232 normal_inds[material_ID].push_back(static_cast<int>(normal_count));
4233 object_normal_inds[obj_label][material_ID].push_back(static_cast<int>(normal_count));
4234 object_normal_inds[obj_label][material_ID].push_back(static_cast<int>(normal_count));
4235 }
4236
4237 if (getPatchPointer_private(p)->hasTexture()) {
4238 int3 tuv1 = make_int3((int) uv_count, (int) uv_count + 1, (int) uv_count + 2);
4239 int3 tuv2 = make_int3((int) uv_count, (int) uv_count + 2, (int) uv_count + 3);
4240 uv_inds[material_ID].push_back(tuv1);
4241 uv_inds[material_ID].push_back(tuv2);
4242 object_uv_inds[obj_label][material_ID].push_back(tuv1);
4243 object_uv_inds[obj_label][material_ID].push_back(tuv2);
4244 if (uv_v.empty()) { // default (u,v)
4245 uv.push_back(make_vec2(0, 1));
4246 uv.push_back(make_vec2(1, 1));
4247 uv.push_back(make_vec2(1, 0));
4248 uv.push_back(make_vec2(0, 0));
4249 uv_count += 4;
4250 } else { // custom (u,v)
4251 for (int i = 0; i < 4; i++) {
4252 uv.push_back(uv_v.at(i));
4253 uv_count++;
4254 }
4255 }
4256 } else {
4257 int3 tuv = make_int3(-1, -1, -1);
4258 uv_inds[material_ID].push_back(tuv);
4259 uv_inds[material_ID].push_back(tuv);
4260 object_uv_inds[obj_label][material_ID].push_back(tuv);
4261 object_uv_inds[obj_label][material_ID].push_back(tuv);
4262 }
4263 }
4264 }
4265
4266 if (write_normals)
4267 assert(normal_inds.size() == faces.size());
4268 // assert(verts.size() == faces.size());
4269 assert(uv_inds.size() == faces.size());
4270 for (int i = 0; i < faces.size(); i++) {
4271 assert(uv_inds.at(i).size() == faces.at(i).size());
4272 }
4273
4274 // copy material textures to new directory and edit old file paths
4275 std::filesystem::path output_path = std::filesystem::path(file_path);
4276 std::filesystem::path texture_dir = output_path.parent_path();
4277
4278 // If no parent path (filename only), use current directory
4279 if (texture_dir.empty()) {
4280 texture_dir = ".";
4281 }
4282
4283 for (auto &material: materials) {
4284 std::string texture = material.texture;
4285 if (!texture.empty()) {
4286 std::error_code ec;
4287 std::filesystem::path source_path = std::filesystem::absolute(texture, ec);
4288
4289 // If we can't resolve the absolute path, try the original path
4290 if (ec) {
4291 source_path = std::filesystem::path(texture);
4292 }
4293
4294 if (!std::filesystem::exists(source_path)) {
4295 // Skip missing texture files silently (maintain original behavior)
4296 continue;
4297 }
4298
4299 auto filename = source_path.filename();
4300 std::filesystem::path dest_path = texture_dir / filename;
4301
4302 // Skip copying if source and destination are the same file
4303 bool same_file = false;
4304 try {
4305 same_file = std::filesystem::equivalent(source_path, dest_path, ec);
4306 if (ec)
4307 same_file = false; // If we can't determine equivalence, assume different
4308 } catch (...) {
4309 same_file = false;
4310 }
4311
4312 if (same_file) {
4313 material.texture = filename.string();
4314 continue;
4315 }
4316
4317 // Attempt to copy file, but don't fail if it doesn't work
4318 try {
4319 std::filesystem::copy_file(source_path, dest_path, std::filesystem::copy_options::overwrite_existing, ec);
4320 if (!ec) {
4321 material.texture = filename.string();
4322 } // else keep original texture path
4323 } catch (...) {
4324 // If copy fails for any reason, keep original texture path
4325 // This maintains backward compatibility
4326 }
4327 }
4328 }
4329
4330 std::ofstream objfstream;
4331 objfstream.open(objfilename);
4332 std::ofstream mtlfstream;
4333 mtlfstream.open(mtlfilename);
4334
4335 objfstream << "# Helios auto-generated OBJ File" << std::endl;
4336 objfstream << "# baileylab.ucdavis.edu/software/helios" << std::endl;
4337 objfstream << "mtllib " << getFileName(mtlfilename) << std::endl;
4338
4339 // Parallel string formatting for vertices, normals, and UV coordinates
4340 std::vector<std::string> vertex_chunks;
4341 const int num_threads = std::min(static_cast<int>(verts.size() / 1000 + 1), std::max(1, static_cast<int>(std::thread::hardware_concurrency())));
4342 vertex_chunks.resize(num_threads);
4343
4344#ifdef USE_OPENMP
4345#pragma omp parallel num_threads(num_threads)
4346#endif
4347 {
4348 int tid = 0;
4349#ifdef USE_OPENMP
4350 tid = omp_get_thread_num();
4351#endif
4352 std::ostringstream vertex_stream;
4353 vertex_stream.precision(8);
4354
4355 const size_t chunk_size = (verts.size() + num_threads - 1) / num_threads;
4356 const size_t start_idx = tid * chunk_size;
4357 const size_t end_idx = std::min(start_idx + chunk_size, verts.size());
4358
4359 for (size_t i = start_idx; i < end_idx; i++) {
4360 vertex_stream << "v " << verts[i].x << " " << verts[i].y << " " << verts[i].z << "\n";
4361 }
4362
4363 vertex_chunks[tid] = vertex_stream.str();
4364 }
4365
4366 for (const auto &chunk: vertex_chunks) {
4367 objfstream << chunk;
4368 }
4369
4370 if (write_normals) {
4371 std::vector<std::string> normal_chunks;
4372 normal_chunks.resize(num_threads);
4373
4374#ifdef USE_OPENMP
4375#pragma omp parallel num_threads(num_threads)
4376#endif
4377 {
4378 int tid = 0;
4379#ifdef USE_OPENMP
4380 tid = omp_get_thread_num();
4381#endif
4382 std::ostringstream normal_stream;
4383 normal_stream.precision(8);
4384
4385 const size_t chunk_size = (normals.size() + num_threads - 1) / num_threads;
4386 const size_t start_idx = tid * chunk_size;
4387 const size_t end_idx = std::min(start_idx + chunk_size, normals.size());
4388
4389 const float epsilon = 1e-7;
4390 for (size_t i = start_idx; i < end_idx; i++) {
4391 vec3 n = normals[i];
4392 if (std::abs(n.x) < epsilon)
4393 n.x = 0;
4394 if (std::abs(n.y) < epsilon)
4395 n.y = 0;
4396 if (std::abs(n.z) < epsilon)
4397 n.z = 0;
4398 normal_stream << "vn " << n.x << " " << n.y << " " << n.z << "\n";
4399 }
4400
4401 normal_chunks[tid] = normal_stream.str();
4402 }
4403
4404 for (const auto &chunk: normal_chunks) {
4405 objfstream << chunk;
4406 }
4407 }
4408
4409 if (!uv.empty()) {
4410 std::vector<std::string> uv_chunks;
4411 uv_chunks.resize(num_threads);
4412
4413#ifdef USE_OPENMP
4414#pragma omp parallel num_threads(num_threads)
4415#endif
4416 {
4417 int tid = 0;
4418#ifdef USE_OPENMP
4419 tid = omp_get_thread_num();
4420#endif
4421 std::ostringstream uv_stream;
4422 uv_stream.precision(8);
4423
4424 const size_t chunk_size = (uv.size() + num_threads - 1) / num_threads;
4425 const size_t start_idx = tid * chunk_size;
4426 const size_t end_idx = std::min(start_idx + chunk_size, uv.size());
4427
4428 for (size_t i = start_idx; i < end_idx; i++) {
4429 uv_stream << "vt " << uv[i].x << " " << uv[i].y << "\n";
4430 }
4431
4432 uv_chunks[tid] = uv_stream.str();
4433 }
4434
4435 for (const auto &chunk: uv_chunks) {
4436 objfstream << chunk;
4437 }
4438 }
4439
4440 // Parallel face string generation
4441
4442 if (object_groups_found) {
4443 // Process object groups sequentially (maintain OBJ structure)
4444 // but parallelize face generation within each material group
4445 for (const auto &obj_label: object_order) {
4446 objfstream << "o " << obj_label << "\n";
4447
4448 for (int mat = 0; mat < materials.size(); mat++) {
4449 auto fit = object_faces[obj_label].find(mat);
4450 if (fit == object_faces[obj_label].end())
4451 continue;
4452
4453 objfstream << "usemtl material" << mat << "\n";
4454
4455 const auto &current_faces = fit->second;
4456 if (current_faces.size() > 100) { // Only parallelize if enough faces
4457 // Parallel face string generation for this material
4458 std::vector<std::string> face_chunks;
4459 face_chunks.resize(num_threads);
4460
4461#ifdef USE_OPENMP
4462#pragma omp parallel num_threads(num_threads)
4463#endif
4464 {
4465 int tid = 0;
4466#ifdef USE_OPENMP
4467 tid = omp_get_thread_num();
4468#endif
4469 std::ostringstream face_stream;
4470
4471 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4472 const size_t start_idx = tid * chunk_size;
4473 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4474
4475 for (size_t f = start_idx; f < end_idx; f++) {
4476 if (uv.empty()) {
4477 if (write_normals) {
4478 face_stream << "f " << current_faces[f].x << "//" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].y << "//" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].z << "//"
4479 << object_normal_inds[obj_label][mat][f] << "\n";
4480 } else {
4481 face_stream << "f " << current_faces[f].x << " " << current_faces[f].y << " " << current_faces[f].z << "\n";
4482 }
4483 } else if (object_uv_inds[obj_label][mat][f].x < 0) {
4484 face_stream << "f " << current_faces[f].x << "/1 " << current_faces[f].y << "/1 " << current_faces[f].z << "/1\n";
4485 } else {
4486 if (write_normals) {
4487 face_stream << "f " << current_faces[f].x << "/" << object_uv_inds[obj_label][mat][f].x << "/" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].y << "/" << object_uv_inds[obj_label][mat][f].y
4488 << "/" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].z << "/" << object_uv_inds[obj_label][mat][f].z << "/" << object_normal_inds[obj_label][mat][f] << "\n";
4489 } else {
4490 face_stream << "f " << current_faces[f].x << "/" << object_uv_inds[obj_label][mat][f].x << " " << current_faces[f].y << "/" << object_uv_inds[obj_label][mat][f].y << " " << current_faces[f].z << "/"
4491 << object_uv_inds[obj_label][mat][f].z << "\n";
4492 }
4493 }
4494 }
4495
4496 face_chunks[tid] = face_stream.str();
4497 }
4498
4499 // Sequential write of face chunks
4500 for (const auto &chunk: face_chunks) {
4501 objfstream << chunk;
4502 }
4503 } else {
4504 // For small face counts, use original sequential approach
4505 for (size_t f = 0; f < current_faces.size(); ++f) {
4506 if (uv.empty()) {
4507 if (write_normals) {
4508 objfstream << "f " << current_faces[f].x << "//" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].y << "//" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].z << "//"
4509 << object_normal_inds[obj_label][mat][f] << std::endl;
4510 } else {
4511 objfstream << "f " << current_faces[f].x << " " << current_faces[f].y << " " << current_faces[f].z << std::endl;
4512 }
4513 } else if (object_uv_inds[obj_label][mat][f].x < 0) {
4514 objfstream << "f " << current_faces[f].x << "/1 " << current_faces[f].y << "/1 " << current_faces[f].z << "/1" << std::endl;
4515 } else {
4516 if (write_normals) {
4517 objfstream << "f " << current_faces[f].x << "/" << object_uv_inds[obj_label][mat][f].x << "/" << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].y << "/" << object_uv_inds[obj_label][mat][f].y << "/"
4518 << object_normal_inds[obj_label][mat][f] << " " << current_faces[f].z << "/" << object_uv_inds[obj_label][mat][f].z << "/" << object_normal_inds[obj_label][mat][f] << std::endl;
4519 } else {
4520 objfstream << "f " << current_faces[f].x << "/" << object_uv_inds[obj_label][mat][f].x << " " << current_faces[f].y << "/" << object_uv_inds[obj_label][mat][f].y << " " << current_faces[f].z << "/"
4521 << object_uv_inds[obj_label][mat][f].z << std::endl;
4522 }
4523 }
4524 }
4525 }
4526 }
4527 }
4528 } else {
4529 // No object groups - simpler structure, better parallelization opportunity
4530 for (int mat = 0; mat < materials.size(); mat++) {
4531 assert(materials.at(mat).materialID == mat);
4532 objfstream << "usemtl material" << mat << "\n";
4533
4534 const auto &current_faces = faces.at(mat);
4535 if (current_faces.size() > 100) { // Only parallelize if enough faces
4536 // Parallel face string generation for this material
4537 std::vector<std::string> face_chunks;
4538 face_chunks.resize(num_threads);
4539
4540#ifdef USE_OPENMP
4541#pragma omp parallel num_threads(num_threads)
4542#endif
4543 {
4544 int tid = 0;
4545#ifdef USE_OPENMP
4546 tid = omp_get_thread_num();
4547#endif
4548 std::ostringstream face_stream;
4549
4550 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4551 const size_t start_idx = tid * chunk_size;
4552 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4553
4554 for (size_t f = start_idx; f < end_idx; f++) {
4555 if (uv.empty()) {
4556 if (write_normals) {
4557 face_stream << "f " << current_faces[f].x << "//" << normal_inds.at(mat)[f] << " " << current_faces[f].y << "//" << normal_inds.at(mat)[f] << " " << current_faces[f].z << "//" << normal_inds.at(mat)[f] << "\n";
4558 } else {
4559 face_stream << "f " << current_faces[f].x << " " << current_faces[f].y << " " << current_faces[f].z << "\n";
4560 }
4561 } else if (uv_inds.at(mat)[f].x < 0) {
4562 face_stream << "f " << current_faces[f].x << "/1 " << current_faces[f].y << "/1 " << current_faces[f].z << "/1\n";
4563 } else {
4564 if (write_normals) {
4565 face_stream << "f " << current_faces[f].x << "/" << uv_inds.at(mat)[f].x << "/" << normal_inds.at(mat)[f] << " " << current_faces[f].y << "/" << uv_inds.at(mat)[f].y << "/" << normal_inds.at(mat)[f] << " "
4566 << current_faces[f].z << "/" << uv_inds.at(mat)[f].z << "/" << normal_inds.at(mat)[f] << "\n";
4567 } else {
4568 face_stream << "f " << current_faces[f].x << "/" << uv_inds.at(mat)[f].x << " " << current_faces[f].y << "/" << uv_inds.at(mat)[f].y << " " << current_faces[f].z << "/" << uv_inds.at(mat)[f].z << "\n";
4569 }
4570 }
4571 }
4572
4573 face_chunks[tid] = face_stream.str();
4574 }
4575
4576 // Sequential write of face chunks
4577 for (const auto &chunk: face_chunks) {
4578 objfstream << chunk;
4579 }
4580 } else {
4581 // For small face counts, use original sequential approach
4582 for (int f = 0; f < current_faces.size(); f++) {
4583 if (uv.empty()) {
4584 if (write_normals) {
4585 objfstream << "f " << current_faces[f].x << "//" << normal_inds.at(mat)[f] << " " << current_faces[f].y << "//" << normal_inds.at(mat)[f] << " " << current_faces[f].z << "//" << normal_inds.at(mat)[f] << std::endl;
4586 } else {
4587 objfstream << "f " << current_faces[f].x << " " << current_faces[f].y << " " << current_faces[f].z << std::endl;
4588 }
4589 } else if (uv_inds.at(mat)[f].x < 0) {
4590 objfstream << "f " << current_faces[f].x << "/1 " << current_faces[f].y << "/1 " << current_faces[f].z << "/1" << std::endl;
4591 } else {
4592 if (write_normals) {
4593 objfstream << "f " << current_faces[f].x << "/" << uv_inds.at(mat)[f].x << "/" << normal_inds.at(mat)[f] << " " << current_faces[f].y << "/" << uv_inds.at(mat)[f].y << "/" << normal_inds.at(mat)[f] << " "
4594 << current_faces[f].z << "/" << uv_inds.at(mat)[f].z << "/" << normal_inds.at(mat)[f] << std::endl;
4595 } else {
4596 objfstream << "f " << current_faces[f].x << "/" << uv_inds.at(mat)[f].x << " " << current_faces[f].y << "/" << uv_inds.at(mat)[f].y << " " << current_faces[f].z << "/" << uv_inds.at(mat)[f].z << std::endl;
4597 }
4598 }
4599 }
4600 }
4601 }
4602 }
4603
4604 for (int mat = 0; mat < materials.size(); mat++) {
4605 if (materials.at(mat).texture.empty()) {
4606 RGBcolor current_color = materials.at(mat).color;
4607 mtlfstream << "newmtl material" << mat << std::endl;
4608 mtlfstream << "Ka " << current_color.r << " " << current_color.g << " " << current_color.b << std::endl;
4609 mtlfstream << "Kd " << current_color.r << " " << current_color.g << " " << current_color.b << std::endl;
4610 mtlfstream << "Ks 0.0 0.0 0.0" << std::endl;
4611 mtlfstream << "illum 2 " << std::endl;
4612 } else {
4613 std::string current_texture = materials.at(mat).texture;
4614 mtlfstream << "newmtl material" << mat << std::endl;
4615 if (materials.at(mat).textureColorIsOverridden) {
4616 RGBcolor current_color = materials.at(mat).color;
4617 mtlfstream << "Ka " << current_color.r << " " << current_color.g << " " << current_color.b << std::endl;
4618 mtlfstream << "Kd " << current_color.r << " " << current_color.g << " " << current_color.b << std::endl;
4619 } else {
4620 mtlfstream << "map_Kd " << current_texture << std::endl;
4621 }
4622 if (materials.at(mat).textureHasTransparency) {
4623 mtlfstream << "map_d " << current_texture << std::endl;
4624 }
4625 mtlfstream << "Ks 0.0 0.0 0.0" << std::endl;
4626 mtlfstream << "illum 2 " << std::endl;
4627 }
4628 }
4629
4630 objfstream.close();
4631 mtlfstream.close();
4632
4633 if (!primitive_dat_fields.empty()) {
4634 bool uuidexistswarning = false;
4635 bool dataexistswarning = false;
4636 bool datatypewarning = false;
4637
4638 for (const std::string &label: primitive_dat_fields) {
4639 std::filesystem::path dat_path = std::filesystem::path(file_path) / (file_stem + "_" + std::string(label) + ".dat");
4640 std::string datfilename = dat_path.string();
4641 std::ofstream datout(datfilename);
4642
4643 for (int mat = 0; mat < materials.size(); mat++) {
4644 for (uint UUID: UUIDs_write.at(mat)) {
4645 if (!doesPrimitiveExist(UUID)) {
4646 uuidexistswarning = true;
4647 continue;
4648 }
4649
4650 // a patch is converted to 2 triangles, so need to write 2 data values for patches
4651 int Nprims = 1;
4653 Nprims = 2;
4654 }
4655
4656 if (!doesPrimitiveDataExist(UUID, label.c_str())) {
4657 dataexistswarning = true;
4658 for (int i = 0; i < Nprims; i++) {
4659 datout << 0 << std::endl;
4660 }
4661 continue;
4662 }
4663
4664 HeliosDataType type = getPrimitiveDataType(label.c_str());
4665 if (type == HELIOS_TYPE_INT) {
4666 int data;
4667 getPrimitiveData(UUID, label.c_str(), data);
4668 for (int i = 0; i < Nprims; i++) {
4669 datout << data << std::endl;
4670 }
4671 } else if (type == HELIOS_TYPE_UINT) {
4672 uint data;
4673 getPrimitiveData(UUID, label.c_str(), data);
4674 for (int i = 0; i < Nprims; i++) {
4675 datout << data << std::endl;
4676 }
4677 } else if (type == HELIOS_TYPE_FLOAT) {
4678 float data;
4679 getPrimitiveData(UUID, label.c_str(), data);
4680 for (int i = 0; i < Nprims; i++) {
4681 datout << data << std::endl;
4682 }
4683 } else if (type == HELIOS_TYPE_DOUBLE) {
4684 double data;
4685 getPrimitiveData(UUID, label.c_str(), data);
4686 for (int i = 0; i < Nprims; i++) {
4687 datout << data << std::endl;
4688 }
4689 } else if (type == HELIOS_TYPE_STRING) {
4690 std::string data;
4691 getPrimitiveData(UUID, label.c_str(), data);
4692 for (int i = 0; i < Nprims; i++) {
4693 datout << data << std::endl;
4694 }
4695 } else {
4696 datatypewarning = true;
4697 for (int i = 0; i < Nprims; i++) {
4698 datout << 0 << std::endl;
4699 }
4700 }
4701 }
4702 }
4703
4704 datout.close();
4705 }
4706
4707 if (uuidexistswarning) {
4708 helios_runtime_error("Context::writeOBJ: One or more UUIDs do not exist in the Context. Cannot write OBJ file with invalid primitives.");
4709 }
4710 if (dataexistswarning) {
4711 helios_runtime_error("Context::writeOBJ: Primitive data requested did not exist for one or more primitives. Cannot write incomplete data to OBJ file.");
4712 }
4713 if (datatypewarning) {
4714 helios_runtime_error("Context::writeOBJ: Only scalar primitive data types (uint, int, float, double, and string) are supported for primitive data export.");
4715 }
4716 }
4717}
4718
4719void Context::writePrimitiveData(const std::string &filename, const std::vector<std::string> &column_format, bool print_header) const {
4720 writePrimitiveData(filename, column_format, getAllUUIDs(), print_header);
4721}
4722
4723void Context::writePrimitiveData(const std::string &filename, const std::vector<std::string> &column_format, const std::vector<uint> &UUIDs, bool print_header) const {
4724 std::ofstream file(filename);
4725
4726 if (print_header) {
4727 for (const auto &label: column_format) {
4728 file << label << " ";
4729 }
4730 file.seekp(-1, std::ios_base::end);
4731 file << "\n";
4732 }
4733
4734 bool uuidexistswarning = false;
4735 bool dataexistswarning = false;
4736 bool datatypewarning = false;
4737
4738 for (uint UUID: UUIDs) {
4739 if (!doesPrimitiveExist(UUID)) {
4740 uuidexistswarning = true;
4741 continue;
4742 }
4743 for (const auto &label: column_format) {
4744 if (label == "UUID") {
4745 file << UUID << " ";
4746 continue;
4747 }
4748 if (!doesPrimitiveDataExist(UUID, label.c_str())) {
4749 dataexistswarning = true;
4750 file << 0 << " ";
4751 continue;
4752 }
4753 HeliosDataType type = getPrimitiveDataType(label.c_str());
4754 if (type == HELIOS_TYPE_INT) {
4755 int data;
4756 getPrimitiveData(UUID, label.c_str(), data);
4757 file << data << " ";
4758 } else if (type == HELIOS_TYPE_UINT) {
4759 uint data;
4760 getPrimitiveData(UUID, label.c_str(), data);
4761 file << data << " ";
4762 } else if (type == HELIOS_TYPE_FLOAT) {
4763 float data;
4764 getPrimitiveData(UUID, label.c_str(), data);
4765 file << data << " ";
4766 } else if (type == HELIOS_TYPE_DOUBLE) {
4767 double data;
4768 getPrimitiveData(UUID, label.c_str(), data);
4769 file << data << " ";
4770 } else if (type == HELIOS_TYPE_STRING) {
4771 std::string data;
4772 getPrimitiveData(UUID, label.c_str(), data);
4773 file << data << " ";
4774 } else {
4775 datatypewarning = true;
4776 file << 0 << " ";
4777 }
4778 }
4779 file.seekp(-1, std::ios_base::end);
4780 file << "\n";
4781 }
4782
4783 if (uuidexistswarning) {
4784 std::cerr << "WARNING (Context::writePrimitiveData): Vector of UUIDs passed to writePrimitiveData() function contained UUIDs that do not exist, which were skipped." << std::endl;
4785 }
4786 if (dataexistswarning) {
4787 std::cerr << "WARNING (Context::writePrimitiveData): Primitive data requested did not exist for one or more primitives. A default value of 0 was written in these cases." << std::endl;
4788 }
4789 if (datatypewarning) {
4790 std::cerr << "WARNING (Context::writePrimitiveData): Only scalar primitive data types (uint, int, float, and double) are supported for this function. A column of 0's was written in these cases." << std::endl;
4791 }
4792
4793 file.close();
4794}
4795
4796void Context::loadTabularTimeseriesData(const std::string &data_file, const std::vector<std::string> &col_labels, const std::string &a_delimeter, const std::string &a_date_string_format, uint headerlines) {
4797 // Resolve file path using project-based resolution
4798 std::filesystem::path resolved_path = resolveProjectFile(data_file);
4799 std::string resolved_filename = resolved_path.string();
4800
4801 std::ifstream datafile(resolved_filename); // open the file
4802
4803 if (!datafile.is_open()) { // check that file exists
4804 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Weather data file '" + data_file + "' does not exist.");
4805 }
4806
4807 int yearcol = -1;
4808 int DOYcol = -1;
4809 int datestrcol = -1;
4810 int hourcol = -1;
4811 int minutecol = -1;
4812 int secondcol = -1;
4813 std::map<std::string, int> datacols;
4814
4815 size_t Ncolumns = 0;
4816
4817 size_t row = headerlines;
4818
4819 std::vector<std::string> column_labels = col_labels;
4820 std::string delimiter = a_delimeter;
4821 std::string date_string_format = a_date_string_format;
4822
4823 // pre-defined labels for CIMIS weather data files
4824 if (col_labels.size() == 1 && (col_labels.front() == "CIMIS" || col_labels.front() == "cimis")) {
4825 column_labels = {
4826 "", "", "", "date", "hour", "DOY", "ETo", "", "precipitation", "", "net_radiation", "", "vapor_pressure", "", "air_temperature", "", "air_humidity", "", "dew_point", "", "wind_speed", "", "wind_direction", "", "soil_temperature", ""};
4827 headerlines = 1;
4828 delimiter = ",";
4829 date_string_format = "MMDDYYYY";
4830 }
4831
4832 // If user specified column labels as an argument, parse them
4833 if (!column_labels.empty()) {
4834 int col = 0;
4835 for (auto &label: column_labels) {
4836 if (label == "year" || label == "Year") {
4837 yearcol = col;
4838 } else if (label == "DOY" || label == "Jul") {
4839 DOYcol = col;
4840 } else if (label == "date" || label == "Date") {
4841 datestrcol = col;
4842 } else if (label == "hour" || label == "Hour") {
4843 hourcol = col;
4844 } else if (label == "minute" || label == "Minute") {
4845 minutecol = col;
4846 } else if (label == "second" || label == "Second") {
4847 secondcol = col;
4848 } else if (!label.empty()) {
4849 if (datacols.find(label) == datacols.end()) {
4850 datacols[label] = col;
4851 } else {
4852 datacols[label + "_dup"] = col;
4853 }
4854 }
4855
4856 col++;
4857 }
4858
4859 Ncolumns = column_labels.size();
4860
4861 // If column labels were not provided, read the first line of the text file and parse it for labels
4862 } else {
4863 if (headerlines == 0) {
4864 std::cout << "WARNING (Context::loadTabularTimeseriesData): "
4865 "headerlines"
4866 " argument was specified as zero, and no column label information was given. Attempting to read the first line to see if it contains label information."
4867 << std::endl;
4868 headerlines++;
4869 }
4870
4871 std::string line;
4872 if (std::getline(datafile, line)) {
4873 std::vector<std::string> line_parsed = separate_string_by_delimiter(line, delimiter);
4874
4875 if (line_parsed.empty()) {
4876 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but it did not contain the specified delimiter.");
4877 }
4878
4879 Ncolumns = line_parsed.size();
4880
4881 for (int col = 0; col < Ncolumns; col++) {
4882 const std::string &label = line_parsed.at(col);
4883
4884 if (label == "year" || label == "Year") {
4885 yearcol = col;
4886 } else if (label == "DOY" || label == "Jul") {
4887 DOYcol = col;
4888 } else if (label == "date" || label == "Date") {
4889 datestrcol = col;
4890 } else if (label == "hour" || label == "Hour") {
4891 hourcol = col;
4892 } else if (label == "minute" || label == "Minute") {
4893 minutecol = col;
4894 } else if (label == "second" || label == "Second") {
4895 secondcol = col;
4896 } else if (!label.empty()) {
4897 if (datacols.find(label) == datacols.end()) {
4898 datacols[label] = col;
4899 } else {
4900 datacols[label + "_dup"] = col;
4901 }
4902 }
4903 }
4904
4905 headerlines--;
4906 } else {
4907 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but read failed.");
4908 }
4909
4910 if (yearcol == -1 && DOYcol == -1 && datestrcol == -1) {
4911 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but could not find valid label information.");
4912 }
4913 }
4914
4915 if (datestrcol < 0 && (yearcol < 0 || DOYcol < 0)) {
4916 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): The date must be specified by either a column labeled "
4917 "date"
4918 ", or by two columns labeled "
4919 "year"
4920 " and "
4921 "DOY"
4922 ".");
4923 } else if (hourcol < 0) {
4924 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): At a minimum, the time must be specified by a column labeled "
4925 "hour"
4926 ".");
4927 } else if (datacols.empty()) {
4928 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): No columns were found containing data variables (e.g., temperature, humidity, wind speed).");
4929 }
4930
4931 std::string line;
4932
4933 // skip header lines
4934 // note: if we read labels from the first header line above, we don't need to skip another line
4935 for (int i = 0; i < headerlines; i++) {
4936 std::getline(datafile, line);
4937 }
4938
4939 while (std::getline(datafile, line)) { // loop through file to read data
4940 row++;
4941
4942 if (trim_whitespace(line).empty() && row > 1) {
4943 break;
4944 }
4945
4946 // separate the line by delimiter
4947 std::vector<std::string> line_separated = separate_string_by_delimiter(line, delimiter);
4948
4949 if (line_separated.size() != Ncolumns) {
4950 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Line " + std::to_string(row) + " had " + std::to_string(line_separated.size()) + " columns, but was expecting " + std::to_string(Ncolumns));
4951 }
4952
4953 // compile date
4954 Date date;
4955 if (yearcol >= 0 && DOYcol >= 0) {
4956 int DOY;
4957 parse_int(line_separated.at(DOYcol), DOY);
4958 if (DOY < 1 || DOY > 366) {
4959 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Invalid date specified on line " + std::to_string(row) + ".");
4960 }
4961 int year;
4962 parse_int(line_separated.at(yearcol), year);
4963 if (year < 1000) {
4964 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Invalid year specified on line " + std::to_string(row) + ".");
4965 }
4966 date = make_Date(DOY, year);
4967 } else if (datestrcol >= 0) {
4968 // parse date string. expecting format YYYY-MM-DD with delimiter '-' or '/'
4969 const std::string &datestr = line_separated.at(datestrcol);
4970
4971 // try parsing date string based on '-' delimiter
4972 std::vector<std::string> thisdatestr = separate_string_by_delimiter(datestr, "-");
4973
4974 if (thisdatestr.size() != 3) {
4975 // try parsing date string based on '/' delimiter
4976 thisdatestr = separate_string_by_delimiter(datestr, "/");
4977 }
4978
4979 if (thisdatestr.size() != 3) {
4980 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) + " of file " + data_file +
4981 ". It should be in the format YYYY-MM-DD, delimited by either "
4982 "-"
4983 " or "
4984 "/"
4985 ".");
4986 }
4987
4988 // convert parsed date strings into a vector of integers
4989 std::vector<int> thisdate(3);
4990 for (int i = 0; i < 3; i++) {
4991 if (!parse_int(thisdatestr.at(i), thisdate.at(i))) {
4992 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) + " of file " + data_file +
4993 ". It should be in the format YYYY-MM-DD, delimited by either "
4994 "-"
4995 " or "
4996 "/"
4997 ".");
4998 }
4999 }
5000
5001 // figure out ordering of values
5002 int year;
5003 int month;
5004 int day;
5005 if (date_string_format == "YYYYMMDD") {
5006 year = thisdate.at(0);
5007 month = thisdate.at(1);
5008 day = thisdate.at(2);
5009 } else if (date_string_format == "YYYYDDMM") {
5010 year = thisdate.at(0);
5011 month = thisdate.at(2);
5012 day = thisdate.at(1);
5013 } else if (date_string_format == "DDMMYYYY") {
5014 year = thisdate.at(2);
5015 month = thisdate.at(1);
5016 day = thisdate.at(0);
5017 } else if (date_string_format == "MMDDYYYY") {
5018 year = thisdate.at(2);
5019 month = thisdate.at(0);
5020 day = thisdate.at(1);
5021 } else {
5022 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Invalid date string format in file " + data_file + ": " + date_string_format +
5023 ". Must be one of "
5024 "YYYYMMDD"
5025 ", "
5026 "YYYYDDMM"
5027 ", "
5028 "DDMMYYYY"
5029 ", or "
5030 "MMDDYYYY"
5031 ". Check that the date string does not include a delimiter (i.e., should be MMDDYYYY not MM/DD/YYYY).");
5032 }
5033
5034 if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31) {
5035 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) + " of file " + data_file +
5036 ". It should be in the format YYYY-MM-DD, delimited by either "
5037 "-"
5038 " or "
5039 "/"
5040 ".");
5041 }
5042
5043 date = make_Date(day, month, year);
5044 } else {
5045 assert(1); // shouldn't be here
5046 }
5047
5048 // compile time
5049 Time time;
5050 int hour = 0;
5051 int minute = 0;
5052 int second = 0;
5053
5054 if (!parse_int(line_separated.at(hourcol), hour)) {
5055 helios_runtime_error("ERROR (Context::loadTabularTimeseriesData): Could not parse hour string on line " + std::to_string(row) + " of file " + data_file + ".");
5056 }
5057 if (hour > 24 && minutecol < 0 && secondcol < 0) {
5058 int hr_min = hour;
5059 hour = std::floor(hr_min / 100);
5060 minute = hr_min - hour * 100;
5061 }
5062 if (hour == 24) {
5063 hour = 0;
5064 date.incrementDay();
5065 }
5066 if (minutecol >= 0) {
5067 if (!parse_int(line_separated.at(minutecol), minute)) {
5068 minute = 0;
5069 std::cout << "WARNING (Context::loadTabularTimeseriesData): Could not parse minute string on line " << row << " of file " << data_file << ". Setting minute equal to 0." << std::endl;
5070 }
5071 }
5072 if (secondcol >= 0) {
5073 if (!parse_int(line_separated.at(secondcol), second)) {
5074 second = 0;
5075 std::cout << "WARNING (Context::loadTabularTimeseriesData): Could not parse second string on line " << row << " of file " << data_file << ". Setting second equal to 0." << std::endl;
5076 }
5077 }
5078 time = make_Time(hour, minute, second);
5079
5080 // compile data values
5081 for (auto &dat: datacols) {
5082 std::string label = dat.first;
5083 int col = dat.second;
5084
5085 float dataval;
5086 if (!parse_float(line_separated.at(col), dataval)) {
5087 std::cout << "WARNING (Context::loadTabularTimeseriesData): Failed to parse data value as "
5088 "float"
5089 " on line "
5090 << row << ", column " << col + 1 << " of file " << data_file << ". Skipping this value..." << std::endl;
5091 continue;
5092 }
5093
5094 if (label == "air_humidity" && col_labels.size() == 1 && (col_labels.front() == "CIMIS" || col_labels.front() == "cimis")) {
5095 dataval = dataval / 100.f;
5096 }
5097
5098 addTimeseriesData(label.c_str(), dataval, date, time);
5099 }
5100 }
5101
5102 datafile.close();
5103}