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