18using namespace helios;
22static constexpr float MIN_TRIANGLE_AREA_THRESHOLD = 1e-8f;
25 std::string data_str = node_data.child_value();
27 if (!data_str.empty()) {
28 std::istringstream data_stream(data_str);
31 while (data_stream >> tmp_s) {
33 data.push_back(tmp_f);
46 std::string data_str = node_data.child_value();
48 if (!data_str.empty()) {
49 std::istringstream data_stream(data_str);
52 while (data_stream >> tmp_s) {
54 data.push_back(tmp_f);
67 std::string data_str = node_data.child_value();
69 if (!data_str.empty()) {
70 std::istringstream data_stream(data_str);
73 while (data_stream >> tmp_s) {
75 data.push_back(tmp_f);
88 std::string data_str = node_data.child_value();
90 if (!data_str.empty()) {
91 std::istringstream data_stream(data_str);
94 while (data_stream >> tmp_s) {
96 data.push_back(tmp_f);
109 std::string data_str = node_data.child_value();
111 if (!data_str.empty()) {
112 std::istringstream data_stream(data_str);
114 while (data_stream >> tmp_s) {
115 data.push_back(tmp_s);
125 std::string data_str = node_data.child_value();
127 if (!data_str.empty()) {
128 std::istringstream data_stream(data_str);
129 std::vector<std::string> tmp_s(2);
131 while (data_stream >> tmp_s[0]) {
132 data_stream >> tmp_s[1];
147 std::string data_str = node_data.child_value();
149 if (!data_str.empty()) {
150 std::istringstream data_stream(data_str);
151 std::vector<std::string> tmp_s(3);
153 while (data_stream >> tmp_s[0]) {
154 data_stream >> tmp_s[1];
155 data_stream >> tmp_s[2];
170 std::string data_str = node_data.child_value();
172 if (!data_str.empty()) {
173 std::istringstream data_stream(data_str);
174 std::vector<std::string> tmp_s(4);
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];
194 std::string data_str = node_data.child_value();
196 if (!data_str.empty()) {
197 std::istringstream data_stream(data_str);
198 std::vector<std::string> tmp_s(2);
200 while (data_stream >> tmp_s[0]) {
201 data_stream >> tmp_s[1];
216 std::string data_str = node_data.child_value();
218 if (!data_str.empty()) {
219 std::istringstream data_stream(data_str);
220 std::vector<std::string> tmp_s(3);
222 while (data_stream >> tmp_s[0]) {
223 data_stream >> tmp_s[1];
224 data_stream >> tmp_s[2];
239 std::string data_str = node_data.child_value();
241 if (!data_str.empty()) {
242 std::istringstream data_stream(data_str);
243 std::vector<std::string> tmp_s(4);
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];
263 pugi::xml_node objID_node = node.child(
"objID");
278 pugi::xml_node transform_node = node.child(
"transform");
280 std::string transform_str = transform_node.child_value();
281 if (transform_str.empty()) {
285 std::istringstream stream(transform_str);
289 while (stream >> tmp_s) {
305 pugi::xml_node texture_node = node.child(
"texture");
307 if (texfile.empty()) {
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);
323 while (uv_stream >> tmp_s[0]) {
324 uv_stream >> tmp_s[1];
339 pugi::xml_node sfrac_node = node.child(
"solid_fraction");
341 if (!sfrac.empty()) {
354 pugi::xml_node vertices_node = node.child(
"vertices");
356 std::string vertices_str = vertices_node.child_value();
357 if (!vertices_str.empty()) {
358 std::istringstream stream(vertices_str);
362 while (stream >> tmp_s) {
366 vertices.at(i) = tmp;
381 pugi::xml_node subdiv_node = node.child(
"subdivisions");
383 if (!subdiv.empty()) {
394 pugi::xml_node subdiv_node = node.child(
"subdivisions");
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];
411 pugi::xml_node subdiv_node = node.child(
"subdivisions");
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];
429 pugi::xml_node node_data = node.child(
"nodes");
430 std::string data_str = node_data.child_value();
432 if (!data_str.empty()) {
433 std::istringstream data_stream(data_str);
434 std::vector<std::string> tmp_s(3);
436 while (data_stream >> tmp_s[0]) {
437 data_stream >> tmp_s[1];
438 data_stream >> tmp_s[2];
442 nodes.push_back(tmp);
453 pugi::xml_node node_data = node.child(
"radius");
454 std::string data_str = node_data.child_value();
456 if (!data_str.empty()) {
457 std::istringstream data_stream(data_str);
460 while (data_stream >> tmp_s) {
462 radius.push_back(tmp_f);
474void Context::loadMaterialData(pugi::xml_node mat_node,
const std::string &material_label) {
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;
480 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
482 if (datav.size() == 1) {
484 }
else if (datav.size() > 1) {
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;
494 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
496 if (datav.size() == 1) {
498 }
else if (datav.size() > 1) {
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;
508 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
510 if (datav.size() == 1) {
512 }
else if (datav.size() > 1) {
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;
522 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
524 if (datav.size() == 1) {
526 }
else if (datav.size() > 1) {
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;
536 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
538 if (datav.size() == 1) {
540 }
else if (datav.size() > 1) {
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;
550 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
552 if (datav.size() == 1) {
554 }
else if (datav.size() > 1) {
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;
564 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
566 if (datav.size() == 1) {
568 }
else if (datav.size() > 1) {
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;
578 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
580 if (datav.size() == 1) {
582 }
else if (datav.size() > 1) {
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;
592 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
594 if (datav.size() == 1) {
596 }
else if (datav.size() > 1) {
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;
606 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
608 if (datav.size() == 1) {
610 }
else if (datav.size() > 1) {
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;
620 helios_runtime_error(
"ERROR (Context::loadXML): Material data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
622 if (datav.size() == 1) {
624 }
else if (datav.size() > 1) {
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();
634 std::vector<int> datav;
636 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
639 if (datav.size() == 1) {
641 }
else if (datav.size() > 1) {
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();
649 std::vector<uint> datav;
651 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
654 if (datav.size() == 1) {
656 }
else if (datav.size() > 1) {
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();
664 std::vector<float> datav;
666 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
669 if (datav.size() == 1) {
671 }
else if (datav.size() > 1) {
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();
679 std::vector<double> datav;
681 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
684 if (datav.size() == 1) {
686 }
else if (datav.size() > 1) {
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();
694 std::vector<vec2> datav;
696 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
699 if (datav.size() == 1) {
701 }
else if (datav.size() > 1) {
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();
709 std::vector<vec3> datav;
711 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
714 if (datav.size() == 1) {
716 }
else if (datav.size() > 1) {
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();
724 std::vector<vec4> datav;
726 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
729 if (datav.size() == 1) {
731 }
else if (datav.size() > 1) {
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();
739 std::vector<int2> datav;
741 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
744 if (datav.size() == 1) {
746 }
else if (datav.size() > 1) {
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();
754 std::vector<int3> datav;
756 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
759 if (datav.size() == 1) {
761 }
else if (datav.size() > 1) {
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();
769 std::vector<int4> datav;
771 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
774 if (datav.size() == 1) {
776 }
else if (datav.size() > 1) {
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();
784 std::vector<std::string> datav;
786 helios_runtime_error(
"ERROR (Context::loadXML): Primitive data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
789 if (datav.size() == 1) {
791 }
else if (datav.size() > 1) {
797void Context::loadOData(pugi::xml_node p,
uint ID) {
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();
803 std::vector<int> datav;
805 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int> with label " + std::string(label) +
" contained invalid data.");
808 if (datav.size() == 1) {
810 }
else if (datav.size() > 1) {
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();
818 std::vector<uint> datav;
820 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_uint> with label " + std::string(label) +
" contained invalid data.");
823 if (datav.size() == 1) {
825 }
else if (datav.size() > 1) {
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();
833 std::vector<float> datav;
835 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_float> with label " + std::string(label) +
" contained invalid data.");
838 if (datav.size() == 1) {
840 }
else if (datav.size() > 1) {
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();
848 std::vector<double> datav;
850 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_double> with label " + std::string(label) +
" contained invalid data.");
853 if (datav.size() == 1) {
855 }
else if (datav.size() > 1) {
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();
863 std::vector<vec2> datav;
865 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec2> with label " + std::string(label) +
" contained invalid data.");
868 if (datav.size() == 1) {
870 }
else if (datav.size() > 1) {
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();
878 std::vector<vec3> datav;
880 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec3> with label " + std::string(label) +
" contained invalid data.");
883 if (datav.size() == 1) {
885 }
else if (datav.size() > 1) {
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();
893 std::vector<vec4> datav;
895 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_vec4> with label " + std::string(label) +
" contained invalid data.");
898 if (datav.size() == 1) {
900 }
else if (datav.size() > 1) {
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();
908 std::vector<int2> datav;
910 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int2> with label " + std::string(label) +
" contained invalid data.");
913 if (datav.size() == 1) {
915 }
else if (datav.size() > 1) {
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();
923 std::vector<int3> datav;
925 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int3> with label " + std::string(label) +
" contained invalid data.");
928 if (datav.size() == 1) {
930 }
else if (datav.size() > 1) {
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();
938 std::vector<int4> datav;
940 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_int4> with label " + std::string(label) +
" contained invalid data.");
943 if (datav.size() == 1) {
945 }
else if (datav.size() > 1) {
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();
953 std::vector<std::string> datav;
955 helios_runtime_error(
"ERROR (Context::loadXML): Object data tag <data_string> with label " + std::string(label) +
" contained invalid data.");
958 if (datav.size() == 1) {
960 }
else if (datav.size() > 1) {
969 std::vector<uint> prim_UUIDs = getObjectPointer_private(ID)->
getPrimitiveUUIDs();
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();
977 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
978 if (u >= prim_UUIDs.size()) {
979 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
983 std::vector<int> datav;
985 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int> with label " + std::string(label) +
" contained invalid data.");
989 if (datav.size() == 1) {
991 }
else if (datav.size() > 1) {
999 for (pugi::xml_node prim_data = p.child(
"primitive_data_uint"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_uint")) {
1000 const char *label = prim_data.attribute(
"label").value();
1003 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1004 if (u >= prim_UUIDs.size()) {
1005 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1009 std::vector<uint> datav;
1011 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_uint> with label " + std::string(label) +
" contained invalid data.");
1015 if (datav.size() == 1) {
1017 }
else if (datav.size() > 1) {
1025 for (pugi::xml_node prim_data = p.child(
"primitive_data_float"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_float")) {
1026 const char *label = prim_data.attribute(
"label").value();
1029 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1030 if (u >= prim_UUIDs.size()) {
1031 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1035 std::vector<float> datav;
1037 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_float> with label " + std::string(label) +
" contained invalid data.");
1041 if (datav.size() == 1) {
1043 }
else if (datav.size() > 1) {
1051 for (pugi::xml_node prim_data = p.child(
"primitive_data_double"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_double")) {
1052 const char *label = prim_data.attribute(
"label").value();
1055 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1056 if (u >= prim_UUIDs.size()) {
1057 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1061 std::vector<double> datav;
1063 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_double> with label " + std::string(label) +
" contained invalid data.");
1067 if (datav.size() == 1) {
1069 }
else if (datav.size() > 1) {
1077 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec2"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec2")) {
1078 const char *label = prim_data.attribute(
"label").value();
1081 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1082 if (u >= prim_UUIDs.size()) {
1083 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1087 std::vector<vec2> datav;
1089 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec2> with label " + std::string(label) +
" contained invalid data.");
1093 if (datav.size() == 1) {
1095 }
else if (datav.size() > 1) {
1103 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec3"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec3")) {
1104 const char *label = prim_data.attribute(
"label").value();
1107 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1108 if (u >= prim_UUIDs.size()) {
1109 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1113 std::vector<vec3> datav;
1115 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec3> with label " + std::string(label) +
" contained invalid data.");
1119 if (datav.size() == 1) {
1121 }
else if (datav.size() > 1) {
1129 for (pugi::xml_node prim_data = p.child(
"primitive_data_vec4"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_vec4")) {
1130 const char *label = prim_data.attribute(
"label").value();
1133 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1134 if (u >= prim_UUIDs.size()) {
1135 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1139 std::vector<vec4> datav;
1141 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_vec4> with label " + std::string(label) +
" contained invalid data.");
1145 if (datav.size() == 1) {
1147 }
else if (datav.size() > 1) {
1155 for (pugi::xml_node prim_data = p.child(
"primitive_data_int2"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int2")) {
1156 const char *label = prim_data.attribute(
"label").value();
1159 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1160 if (u >= prim_UUIDs.size()) {
1161 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1165 std::vector<int2> datav;
1167 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int2> with label " + std::string(label) +
" contained invalid data.");
1171 if (datav.size() == 1) {
1173 }
else if (datav.size() > 1) {
1181 for (pugi::xml_node prim_data = p.child(
"primitive_data_int3"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int3")) {
1182 const char *label = prim_data.attribute(
"label").value();
1185 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1186 if (u >= prim_UUIDs.size()) {
1187 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1191 std::vector<int3> datav;
1193 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int3> with label " + std::string(label) +
" contained invalid data.");
1197 if (datav.size() == 1) {
1199 }
else if (datav.size() > 1) {
1207 for (pugi::xml_node prim_data = p.child(
"primitive_data_int4"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_int4")) {
1208 const char *label = prim_data.attribute(
"label").value();
1211 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1212 if (u >= prim_UUIDs.size()) {
1213 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1217 std::vector<int4> datav;
1219 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_int4> with label " + std::string(label) +
" contained invalid data.");
1223 if (datav.size() == 1) {
1225 }
else if (datav.size() > 1) {
1233 for (pugi::xml_node prim_data = p.child(
"primitive_data_string"); prim_data; prim_data = prim_data.next_sibling(
"primitive_data_string")) {
1234 const char *label = prim_data.attribute(
"label").value();
1237 for (pugi::xml_node data = prim_data.child(
"data"); data; data = data.next_sibling(
"data")) {
1238 if (u >= prim_UUIDs.size()) {
1239 warnings.
addWarning(
"osubpdata_length_mismatch",
"There was a problem with reading object primitive data \"" + std::string(label) +
"\". The number of data values provided does not match the number of primitives contained in this object. Skipping remaining data values.");
1243 std::vector<std::string> datav;
1245 helios_runtime_error(
"ERROR (Context::loadXML): Object member primitive data tag <primitive_data_string> with label " + std::string(label) +
" contained invalid data.");
1249 if (datav.size() == 1) {
1251 }
else if (datav.size() > 1) {
1262 std::cout <<
"Loading XML file: " << filename <<
"..." << std::flush;
1265 std::string fn = filename;
1267 if (ext !=
".xml" && ext !=
".XML") {
1273 std::string resolved_filename = resolved_path.string();
1275 XMLfiles.emplace_back(resolved_filename);
1278 std::vector<uint> UUID;
1281 pugi::xml_document xmldoc;
1284 pugi::xml_parse_result load_result = xmldoc.load_file(resolved_filename.c_str());
1288 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() +
1289 "\nError offset: " + std::to_string(load_result.offset) +
" (error at [..." + (filename + load_result.offset) +
"]\n");
1292 pugi::xml_node helios = xmldoc.child(
"helios");
1294 if (helios.empty()) {
1296 std::cout <<
"failed." << std::endl;
1298 helios_runtime_error(
"ERROR (Context::loadXML): XML file must have tag '<helios> ... </helios>' bounding all other tags.");
1302 std::map<uint, std::vector<uint>> object_prim_UUIDs;
1308 for (pugi::xml_node p = helios.child(
"date"); p; p = p.next_sibling(
"date")) {
1309 pugi::xml_node year_node = p.child(
"year");
1310 const char *year_str = year_node.child_value();
1313 helios_runtime_error(
"ERROR (Context::loadXML): Year given in 'date' block must be an integer value.");
1316 pugi::xml_node month_node = p.child(
"month");
1317 const char *month_str = month_node.child_value();
1320 helios_runtime_error(
"ERROR (Context::loadXML): Month given in 'date' block must be an integer value.");
1323 pugi::xml_node day_node = p.child(
"day");
1324 const char *day_str = day_node.child_value();
1327 helios_runtime_error(
"ERROR (Context::loadXML): Day given in 'date' block must be an integer value.");
1333 for (pugi::xml_node p = helios.child(
"time"); p; p = p.next_sibling(
"time")) {
1334 pugi::xml_node hour_node = p.child(
"hour");
1335 const char *hour_str = hour_node.child_value();
1338 helios_runtime_error(
"ERROR (Context::loadXML): Hour given in 'time' block must be an integer value.");
1341 pugi::xml_node minute_node = p.child(
"minute");
1342 const char *minute_str = minute_node.child_value();
1345 helios_runtime_error(
"ERROR (Context::loadXML): Minute given in 'time' block must be an integer value.");
1348 pugi::xml_node second_node = p.child(
"second");
1349 const char *second_str = second_node.child_value();
1352 helios_runtime_error(
"ERROR (Context::loadXML): Second given in 'time' block must be an integer value.");
1355 setTime(second, minute, hour);
1360 std::map<uint, std::string> legacy_material_id_to_label;
1362 for (pugi::xml_node m = helios.child(
"materials"); m; m = m.next_sibling(
"materials")) {
1363 for (pugi::xml_node mat = m.child(
"material"); mat; mat = mat.next_sibling(
"material")) {
1364 std::string material_label;
1366 std::string texture_file;
1367 bool texture_override =
false;
1370 pugi::xml_attribute label_attr = mat.attribute(
"label");
1371 if (!label_attr.empty()) {
1372 material_label = label_attr.value();
1375 pugi::xml_attribute id_attr = mat.attribute(
"id");
1376 if (!id_attr.empty()) {
1378 const char *id_str = id_attr.value();
1380 helios_runtime_error(
"ERROR (Context::loadXML): Material ID must be an unsigned integer value.");
1383 material_label =
"__auto_material_" + std::to_string(matID);
1384 legacy_material_id_to_label[matID] = material_label;
1386 helios_runtime_error(
"ERROR (Context::loadXML): Material must have either a 'label' or 'id' attribute.");
1391 pugi::xml_node color_node = mat.child(
"color");
1392 if (!color_node.empty()) {
1393 const char *color_str = color_node.child_value();
1394 std::istringstream color_stream(color_str);
1395 std::vector<float> color_vec;
1397 while (color_stream >> tmp) {
1398 color_vec.push_back(tmp);
1400 if (color_vec.size() == 3) {
1401 color =
make_RGBAcolor(color_vec.at(0), color_vec.at(1), color_vec.at(2), 1.f);
1402 }
else if (color_vec.size() == 4) {
1403 color =
make_RGBAcolor(color_vec.at(0), color_vec.at(1), color_vec.at(2), color_vec.at(3));
1408 pugi::xml_node texture_node = mat.child(
"texture");
1409 if (!texture_node.empty()) {
1410 texture_file =
deblank(texture_node.child_value());
1411 if (!texture_file.empty()) {
1412 addTexture(texture_file.c_str());
1417 pugi::xml_node override_node = mat.child(
"texture_override");
1418 if (!override_node.empty()) {
1419 const char *override_str = override_node.child_value();
1421 if (
parse_int(override_str, override_val)) {
1422 texture_override = (override_val != 0);
1428 pugi::xml_node twosided_node = mat.child(
"twosided_flag");
1429 if (!twosided_node.empty()) {
1430 const char *twosided_str = twosided_node.child_value();
1432 if (
parse_int(twosided_str, twosided_val) && twosided_val >= 0) {
1433 twosided = (
uint) twosided_val;
1440 uint newID = currentMaterialID++;
1441 Material loaded_mat(newID, material_label, color, texture_file, texture_override, twosided);
1442 materials[newID] = loaded_mat;
1443 material_label_to_id[material_label] = newID;
1447 if (!texture_file.empty()) {
1455 loadMaterialData(mat, material_label);
1460 for (pugi::xml_node p = helios.child(
"patch"); p; p = p.next_sibling(
"patch")) {
1464 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'patch' block must be a non-negative integer value.");
1468 float transform[16];
1471 helios_runtime_error(
"ERROR (Context::loadXML): Patch <transform> node contains less than 16 data values.");
1472 }
else if (result == 2) {
1473 helios_runtime_error(
"ERROR (Context::loadXML): Patch <transform> node contains invalid data.");
1477 std::string texture_file;
1481 std::vector<vec2> uv;
1483 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'patch' block contain invalid data.");
1487 float solid_fraction = -1;
1489 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'patch' block contains invalid data.");
1493 pugi::xml_node material_node = p.child(
"material");
1494 pugi::xml_node material_id_node = p.child(
"material_id");
1495 std::string material_label_from_xml;
1496 bool has_material =
false;
1498 if (!material_node.empty()) {
1500 material_label_from_xml =
deblank(material_node.child_value());
1501 if (!material_label_from_xml.empty() &&
doesMaterialExist(material_label_from_xml)) {
1502 has_material =
true;
1503 ID =
addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1),
make_SphericalCoord(0, 0),
make_RGBAcolor(0, 0, 0, 1));
1505 }
else if (!material_id_node.empty()) {
1507 uint materialID_from_xml = 0;
1508 const char *mat_id_str = material_id_node.child_value();
1509 if (
parse_uint(mat_id_str, materialID_from_xml)) {
1511 auto it = legacy_material_id_to_label.find(materialID_from_xml);
1512 if (it != legacy_material_id_to_label.end()) {
1513 material_label_from_xml = it->second;
1514 has_material =
true;
1515 ID =
addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1),
make_SphericalCoord(0, 0),
make_RGBAcolor(0, 0, 0, 1));
1520 if (!has_material) {
1523 pugi::xml_node color_node = p.child(
"color");
1525 const char *color_str = color_node.child_value();
1526 if (strlen(color_str) == 0) {
1533 if (strcmp(texture_file.c_str(),
"none") == 0) {
1536 std::string texture_file_copy;
1537 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1538 texture_file_copy = texture_file;
1539 texture_file =
"lib/images/solid.jpg";
1546 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1547 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1548 addTexture(texture_file_copy.c_str());
1549 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1554 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1557 if (has_material && !material_label_from_xml.empty()) {
1562 object_prim_UUIDs[objID].push_back(ID);
1577 for (pugi::xml_node tri = helios.child(
"triangle"); tri; tri = tri.next_sibling(
"triangle")) {
1581 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'triangle' block must be a non-negative integer value.");
1585 float transform[16];
1588 helios_runtime_error(
"ERROR (Context::loadXML): Triangle <transform> node contains less than 16 data values.");
1589 }
else if (result == 2) {
1590 helios_runtime_error(
"ERROR (Context::loadXML): Triangle <transform> node contains invalid data.");
1594 std::string texture_file;
1598 std::vector<vec2> uv;
1600 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'triangle' block contain invalid data.");
1604 float solid_fraction = -1;
1606 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'triangle' block contains invalid data.");
1610 pugi::xml_node material_node_tri = tri.child(
"material");
1611 pugi::xml_node material_id_node_tri = tri.child(
"material_id");
1612 std::string material_label_from_xml_tri;
1613 bool has_material_tri =
false;
1615 if (!material_node_tri.empty()) {
1617 material_label_from_xml_tri =
deblank(material_node_tri.child_value());
1618 if (!material_label_from_xml_tri.empty() &&
doesMaterialExist(material_label_from_xml_tri)) {
1619 has_material_tri =
true;
1621 }
else if (!material_id_node_tri.empty()) {
1623 uint materialID_from_xml_tri = 0;
1624 const char *mat_id_str = material_id_node_tri.child_value();
1625 if (
parse_uint(mat_id_str, materialID_from_xml_tri)) {
1627 auto it = legacy_material_id_to_label.find(materialID_from_xml_tri);
1628 if (it != legacy_material_id_to_label.end()) {
1629 material_label_from_xml_tri = it->second;
1630 has_material_tri =
true;
1635 std::vector<vec3> vert_pos;
1637 vert_pos.at(0) =
make_vec3(0.f, 0.f, 0.f);
1638 vert_pos.at(1) =
make_vec3(0.f, 1.f, 0.f);
1639 vert_pos.at(2) =
make_vec3(1.f, 1.f, 0.f);
1641 if (has_material_tri) {
1647 pugi::xml_node color_node = tri.child(
"color");
1649 const char *color_str = color_node.child_value();
1650 if (strlen(color_str) == 0) {
1657 if (strcmp(texture_file.c_str(),
"none") == 0 || uv.empty()) {
1658 ID =
addTriangle(vert_pos.at(0), vert_pos.at(1), vert_pos.at(2), color);
1660 std::string texture_file_copy;
1661 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1662 texture_file_copy = texture_file;
1663 texture_file =
"lib/images/solid.jpg";
1665 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));
1666 if (solid_fraction < 1.f && solid_fraction >= 0.f) {
1667 getPrimitivePointer_private(ID)->setTextureFile(texture_file_copy.c_str());
1668 addTexture(texture_file_copy.c_str());
1669 getPrimitivePointer_private(ID)->setSolidFraction(solid_fraction);
1674 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1677 if (has_material_tri && !material_label_from_xml_tri.empty()) {
1682 object_prim_UUIDs[objID].push_back(ID);
1695 for (pugi::xml_node p = helios.child(
"voxel"); p; p = p.next_sibling(
"voxel")) {
1699 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'voxel' block must be a non-negative integer value.");
1703 float transform[16];
1706 helios_runtime_error(
"ERROR (Context::loadXML): Voxel <transform> node contains less than 16 data values.");
1707 }
else if (result == 2) {
1708 helios_runtime_error(
"ERROR (Context::loadXML): Voxel <transform> node contains invalid data.");
1712 float solid_fraction = 1;
1714 helios_runtime_error(
"ERROR (Context::loadXML): Solid fraction given in 'voxel' block contains invalid data.");
1718 pugi::xml_node material_node_vox = p.child(
"material");
1719 pugi::xml_node material_id_node_vox = p.child(
"material_id");
1720 std::string material_label_from_xml_vox;
1721 bool has_material_vox =
false;
1723 if (!material_node_vox.empty()) {
1725 material_label_from_xml_vox =
deblank(material_node_vox.child_value());
1726 if (!material_label_from_xml_vox.empty() &&
doesMaterialExist(material_label_from_xml_vox)) {
1727 has_material_vox =
true;
1729 }
else if (!material_id_node_vox.empty()) {
1731 uint materialID_from_xml_vox = 0;
1732 const char *mat_id_str = material_id_node_vox.child_value();
1733 if (
parse_uint(mat_id_str, materialID_from_xml_vox)) {
1735 auto it = legacy_material_id_to_label.find(materialID_from_xml_vox);
1736 if (it != legacy_material_id_to_label.end()) {
1737 material_label_from_xml_vox = it->second;
1738 has_material_vox =
true;
1743 if (has_material_vox) {
1745 ID =
addVoxel(
make_vec3(0, 0, 0),
make_vec3(0, 0, 0), 0,
make_RGBAcolor(0, 0, 0, 1));
1749 pugi::xml_node color_node = p.child(
"color");
1751 const char *color_str = color_node.child_value();
1752 if (strlen(color_str) == 0) {
1762 getPrimitivePointer_private(ID)->setTransformationMatrix(transform);
1765 if (has_material_vox && !material_label_from_xml_vox.empty()) {
1770 object_prim_UUIDs[objID].push_back(ID);
1785 for (pugi::xml_node p = helios.child(
"tile"); p; p = p.next_sibling(
"tile")) {
1789 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'tile' block must be a non-negative integer value.");
1793 float transform[16];
1796 helios_runtime_error(
"ERROR (Context::loadXML): Tile <transform> node contains less than 16 data values.");
1797 }
else if (result == 2) {
1798 helios_runtime_error(
"ERROR (Context::loadXML): Tile <transform> node contains invalid data.");
1802 std::string texture_file;
1806 std::vector<vec2> uv;
1808 helios_runtime_error(
"ERROR (Context::loadXML): (u,v) coordinates given in 'tile' block contain invalid data.");
1813 pugi::xml_node color_node = p.child(
"color");
1815 const char *color_str = color_node.child_value();
1816 if (strlen(color_str) != 0) {
1823 if (result_subdiv == 1) {
1824 load_xml_warnings.
addWarning(
"missing_subdivisions_tile",
"Number of subdivisions for tile was not provided. Assuming 1x1.");
1826 }
else if (result_subdiv == 2) {
1827 helios_runtime_error(
"ERROR (Context::loadXML): Tile <subdivisions> node contains invalid data. ");
1832 patch.setTransformationMatrix(transform);
1847 if (strcmp(texture_file.c_str(),
"none") == 0) {
1848 if (strlen(color_str) == 0) {
1862 if (objID > 0 && !object_prim_UUIDs.empty() && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1873 loadOsubPData(p, ID, load_xml_warnings);
1880 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1884 for (pugi::xml_node p = helios.child(
"sphere"); p; p = p.next_sibling(
"sphere")) {
1888 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'sphere' block must be a non-negative integer value.");
1892 objID = currentObjectID;
1897 float transform[16];
1900 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <transform> node contains less than 16 data values.");
1901 }
else if (result == 2) {
1902 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <transform> node contains invalid data.");
1906 std::string texture_file;
1911 pugi::xml_node color_node = p.child(
"color");
1913 const char *color_str = color_node.child_value();
1914 if (strlen(color_str) != 0) {
1921 if (result_subdiv == 1) {
1922 load_xml_warnings.
addWarning(
"missing_subdivisions_sphere",
"Number of subdivisions for sphere was not provided. Assuming 1x1.");
1924 }
else if (result_subdiv == 2) {
1925 helios_runtime_error(
"ERROR (Context::loadXML): Sphere <subdivisions> node contains invalid data. ");
1929 std::vector<uint> empty;
1930 Sphere sphere(0, empty, 3,
"",
this);
1931 sphere.setTransformationMatrix(transform);
1934 if (strcmp(texture_file.c_str(),
"none") == 0) {
1935 if (strlen(color_str) == 0) {
1941 ID =
addSphereObject(subdiv, sphere.getCenter(), sphere.getRadius(), texture_file.c_str());
1945 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
1958 loadOsubPData(p, ID, load_xml_warnings);
1965 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
1969 for (pugi::xml_node p = helios.child(
"tube"); p; p = p.next_sibling(
"tube")) {
1973 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'tube' block must be a non-negative integer value.");
1977 objID = currentObjectID;
1982 float transform[16];
1985 helios_runtime_error(
"ERROR (Context::loadXML): Tube <transform> node contains less than 16 data values.");
1986 }
else if (result == 2) {
1987 helios_runtime_error(
"ERROR (Context::loadXML): Tube <transform> node contains invalid data.");
1991 std::string texture_file;
1997 if (result_subdiv == 1) {
1998 load_xml_warnings.
addWarning(
"missing_subdivisions_tube",
"Number of subdivisions for tube was not provided. Assuming 1x1.");
2000 }
else if (result_subdiv == 2) {
2001 helios_runtime_error(
"ERROR (Context::loadXML): Tube <subdivisions> node contains invalid data. ");
2005 std::vector<vec3> nodes;
2006 pugi::xml_node nodes_node = p.child(
"nodes");
2012 std::vector<float> radii;
2013 pugi::xml_node radii_node = p.child(
"radius");
2020 pugi::xml_node color_node = p.child(
"color");
2021 const char *color_str = color_node.child_value();
2023 std::vector<RGBcolor> colors;
2024 if (strlen(color_str) > 0) {
2025 std::istringstream data_stream(color_str);
2026 std::vector<float> tmp;
2029 while (data_stream >> tmp.at(c)) {
2032 colors.push_back(
make_RGBcolor(tmp.at(0), tmp.at(1), tmp.at(2)));
2039 if (texture_file ==
"none") {
2042 ID =
addTubeObject(subdiv, nodes, radii, texture_file.c_str());
2048 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2061 loadOsubPData(p, ID, load_xml_warnings);
2068 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2072 for (pugi::xml_node p = helios.child(
"box"); p; p = p.next_sibling(
"box")) {
2076 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'box' block must be a non-negative integer value.");
2080 objID = currentObjectID;
2085 float transform[16];
2088 helios_runtime_error(
"ERROR (Context::loadXML): Box <transform> node contains less than 16 data values.");
2089 }
else if (result == 2) {
2094 std::string texture_file;
2099 pugi::xml_node color_node = p.child(
"color");
2101 const char *color_str = color_node.child_value();
2102 if (strlen(color_str) != 0) {
2109 if (result_subdiv == 1) {
2110 load_xml_warnings.
addWarning(
"missing_subdivisions_box",
"Number of subdivisions for box was not provided. Assuming 1x1.");
2112 }
else if (result_subdiv == 2) {
2113 helios_runtime_error(
"ERROR (Context::loadXML): Box <subdivisions> node contains invalid data. ");
2117 std::vector<uint> empty;
2122 if (strcmp(texture_file.c_str(),
"none") == 0) {
2123 if (strlen(color_str) == 0) {
2133 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2146 loadOsubPData(p, ID, load_xml_warnings);
2153 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2157 for (pugi::xml_node p = helios.child(
"disk"); p; p = p.next_sibling(
"disk")) {
2161 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'disk' block must be a non-negative integer value.");
2165 objID = currentObjectID;
2170 float transform[16];
2173 helios_runtime_error(
"ERROR (Context::loadXML): Disk <transform> node contains less than 16 data values.");
2174 }
else if (result == 2) {
2175 helios_runtime_error(
"ERROR (Context::loadXML): Disk <transform> node contains invalid data.");
2179 std::string texture_file;
2184 pugi::xml_node color_node = p.child(
"color");
2186 const char *color_str = color_node.child_value();
2187 if (strlen(color_str) != 0) {
2194 if (result_subdiv == 1) {
2195 load_xml_warnings.
addWarning(
"missing_subdivisions_disk",
"Number of subdivisions for disk was not provided. Assuming 1x1.");
2197 }
else if (result_subdiv == 2) {
2198 helios_runtime_error(
"ERROR (Context::loadXML): Disk <subdivisions> node contains invalid data. ");
2202 std::vector<uint> empty;
2207 if (strcmp(texture_file.c_str(),
"none") == 0) {
2208 if (strlen(color_str) == 0) {
2218 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2231 loadOsubPData(p, ID, load_xml_warnings);
2238 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2242 for (pugi::xml_node p = helios.child(
"cone"); p; p = p.next_sibling(
"cone")) {
2246 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'cone' block must be a non-negative integer value.");
2250 objID = currentObjectID;
2255 float transform[16];
2258 helios_runtime_error(
"ERROR (Context::loadXML): Cone <transform> node contains less than 16 data values.");
2259 }
else if (result == 2) {
2260 helios_runtime_error(
"ERROR (Context::loadXML): Cone <transform> node contains invalid data.");
2264 std::string texture_file;
2269 pugi::xml_node color_node = p.child(
"color");
2271 const char *color_str = color_node.child_value();
2272 if (strlen(color_str) != 0) {
2279 if (result_subdiv == 1) {
2280 load_xml_warnings.
addWarning(
"missing_subdivisions_cone",
"Number of subdivisions for cone was not provided. Assuming 1x1.");
2282 }
else if (result_subdiv == 2) {
2283 helios_runtime_error(
"ERROR (Context::loadXML): Cone <subdivisions> node contains invalid data. ");
2287 std::vector<vec3> nodes;
2288 pugi::xml_node nodes_node = p.child(
"nodes");
2294 std::vector<float> radii;
2295 pugi::xml_node radii_node = p.child(
"radius");
2301 if (texture_file ==
"none") {
2304 ID =
addConeObject(subdiv, nodes.at(0), nodes.at(1), radii.at(0), radii.at(1), texture_file.c_str());
2310 if (objID > 0 && object_prim_UUIDs.find(objID) != object_prim_UUIDs.end()) {
2323 loadOsubPData(p, ID, load_xml_warnings);
2330 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2334 for (pugi::xml_node p = helios.child(
"polymesh"); p; p = p.next_sibling(
"polymesh")) {
2338 helios_runtime_error(
"ERROR (Context::loadXML): Object ID (objID) given in 'polymesh' block must be a non-negative integer value.");
2342 objID = currentObjectID;
2352 loadOsubPData(p, ID, load_xml_warnings);
2358 std::vector<uint> childUUIDs = object_prim_UUIDs.at(objID);
2359 UUID.insert(UUID.end(), childUUIDs.begin(), childUUIDs.end());
2364 for (pugi::xml_node data = helios.child(
"globaldata_int"); data; data = data.next_sibling(
"globaldata_int")) {
2365 const char *label = data.attribute(
"label").value();
2367 std::vector<int> datav;
2369 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int> with label " + std::string(label) +
" contained invalid data.");
2372 if (datav.size() == 1) {
2374 }
else if (datav.size() > 1) {
2379 for (pugi::xml_node data = helios.child(
"globaldata_uint"); data; data = data.next_sibling(
"globaldata_uint")) {
2380 const char *label = data.attribute(
"label").value();
2382 std::vector<uint> datav;
2384 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_uint> with label " + std::string(label) +
" contained invalid data.");
2387 if (datav.size() == 1) {
2389 }
else if (datav.size() > 1) {
2394 for (pugi::xml_node data = helios.child(
"globaldata_float"); data; data = data.next_sibling(
"globaldata_float")) {
2395 const char *label = data.attribute(
"label").value();
2397 std::vector<float> datav;
2399 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_float> with label " + std::string(label) +
" contained invalid data.");
2402 if (datav.size() == 1) {
2404 }
else if (datav.size() > 1) {
2409 for (pugi::xml_node data = helios.child(
"globaldata_double"); data; data = data.next_sibling(
"globaldata_double")) {
2410 const char *label = data.attribute(
"label").value();
2412 std::vector<double> datav;
2414 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_double> with label " + std::string(label) +
" contained invalid data.");
2417 if (datav.size() == 1) {
2419 }
else if (datav.size() > 1) {
2424 for (pugi::xml_node data = helios.child(
"globaldata_vec2"); data; data = data.next_sibling(
"globaldata_vec2")) {
2425 const char *label = data.attribute(
"label").value();
2427 std::vector<vec2> datav;
2429 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec2> with label " + std::string(label) +
" contained invalid data.");
2432 if (datav.size() == 1) {
2434 }
else if (datav.size() > 1) {
2439 for (pugi::xml_node data = helios.child(
"globaldata_vec3"); data; data = data.next_sibling(
"globaldata_vec3")) {
2440 const char *label = data.attribute(
"label").value();
2442 std::vector<vec3> datav;
2444 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec3> with label " + std::string(label) +
" contained invalid data.");
2447 if (datav.size() == 1) {
2449 }
else if (datav.size() > 1) {
2454 for (pugi::xml_node data = helios.child(
"globaldata_vec4"); data; data = data.next_sibling(
"globaldata_vec4")) {
2455 const char *label = data.attribute(
"label").value();
2457 std::vector<vec4> datav;
2459 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_vec4> with label " + std::string(label) +
" contained invalid data.");
2462 if (datav.size() == 1) {
2464 }
else if (datav.size() > 1) {
2469 for (pugi::xml_node data = helios.child(
"globaldata_int2"); data; data = data.next_sibling(
"globaldata_int2")) {
2470 const char *label = data.attribute(
"label").value();
2472 std::vector<int2> datav;
2474 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int2> with label " + std::string(label) +
" contained invalid data.");
2477 if (datav.size() == 1) {
2479 }
else if (datav.size() > 1) {
2484 for (pugi::xml_node data = helios.child(
"globaldata_int3"); data; data = data.next_sibling(
"globaldata_int3")) {
2485 const char *label = data.attribute(
"label").value();
2487 std::vector<int3> datav;
2489 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int3> with label " + std::string(label) +
" contained invalid data.");
2492 if (datav.size() == 1) {
2494 }
else if (datav.size() > 1) {
2499 for (pugi::xml_node data = helios.child(
"globaldata_int4"); data; data = data.next_sibling(
"globaldata_int4")) {
2500 const char *label = data.attribute(
"label").value();
2502 std::vector<int4> datav;
2504 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_int4> with label " + std::string(label) +
" contained invalid data.");
2507 if (datav.size() == 1) {
2509 }
else if (datav.size() > 1) {
2514 for (pugi::xml_node data = helios.child(
"globaldata_string"); data; data = data.next_sibling(
"globaldata_string")) {
2515 const char *label = data.attribute(
"label").value();
2517 std::vector<std::string> datav;
2519 helios_runtime_error(
"ERROR (Context::loadXML): Global data tag <globaldata_string> with label " + std::string(label) +
" contained invalid data.");
2522 if (datav.size() == 1) {
2524 }
else if (datav.size() > 1) {
2530 for (pugi::xml_node p = helios.child(
"timeseries"); p; p = p.next_sibling(
"timeseries")) {
2531 const char *label = p.attribute(
"label").value();
2533 for (pugi::xml_node d = p.child(
"datapoint"); d; d = d.next_sibling(
"datapoint")) {
2535 pugi::xml_node time_node = d.child(
"time");
2536 const char *time_str = time_node.child_value();
2537 if (strlen(time_str) > 0) {
2539 if (time_.
x < 0 || time_.
x > 23) {
2540 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.");
2541 }
else if (time_.
y < 0 || time_.
y > 59) {
2542 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.");
2543 }
else if (time_.
z < 0 || time_.
z > 59) {
2544 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.");
2548 helios_runtime_error(
"ERROR (Context::loadXML): No time was specified for timeseries datapoint.");
2552 bool date_flag =
false;
2554 pugi::xml_node date_node = d.child(
"date");
2555 const char *date_str = date_node.child_value();
2556 if (strlen(date_str) > 0) {
2558 if (date_.
x < 1 || date_.
x > 31) {
2559 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.");
2560 }
else if (date_.
y < 1 || date_.
y > 12) {
2561 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.");
2562 }
else if (date_.
z < 1000 || date_.
z > 10000) {
2563 helios_runtime_error(
"ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.
z) +
" given in timeseries. Year should be in YYYY format.");
2569 pugi::xml_node Jdate_node = d.child(
"dateJulian");
2570 const char *Jdate_str = Jdate_node.child_value();
2571 if (strlen(Jdate_str) > 0) {
2573 if (date_.
x < 1 || date_.
x > 366) {
2574 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.");
2575 }
else if (date_.
y < 1000 || date_.
y > 10000) {
2576 helios_runtime_error(
"ERROR (Context::loadXML): Invalid year of " + std::to_string(date_.
y) +
" given in timeseries. Year should be in YYYY format.");
2583 helios_runtime_error(
"ERROR (Context::loadXML): No date was specified for timeseries datapoint.");
2587 pugi::xml_node value_node = d.child(
"value");
2588 const char *value_str = value_node.child_value();
2589 if (strlen(value_str) > 0) {
2591 helios_runtime_error(
"ERROR (Context::loadXML): Datapoint value in 'timeseries' block must be a float value.");
2594 helios_runtime_error(
"ERROR (Context::loadXML): No value was specified for timeseries datapoint.");
2601 load_xml_warnings.
report(std::cerr);
2604 std::cout <<
"done." << std::endl;
2615 const std::string &fn = filename;
2617 if (ext !=
".xml" && ext !=
".XML") {
2622 pugi::xml_document xmldoc;
2625 pugi::xml_parse_result load_result = xmldoc.load_file(filename.c_str());
2629 helios_runtime_error(
"failed.\n XML [" + filename +
"] parsed with errors, attr value: [" + xmldoc.child(
"node").attribute(
"attr").value() +
"]\nError description: " + load_result.description() +
2630 "\nError offset: " + std::to_string(load_result.offset) +
" (error at [..." + (filename.c_str() + load_result.offset) +
"]\n");
2633 pugi::xml_node helios = xmldoc.child(
"helios");
2635 if (helios.empty()) {
2639 for (pugi::xml_node p = helios.child(tag.c_str()); p; p = p.next_sibling(tag.c_str())) {
2640 const char *labelquery = p.attribute(
"label").value();
2642 if (labelquery == label || label.empty()) {
2650void Context::writeDataToXMLstream(
const char *data_group,
const std::vector<std::string> &data_labels,
void *ptr, std::ofstream &outfile)
const {
2651 for (
const auto &label: data_labels) {
2654 if (strcmp(data_group,
"primitive") == 0) {
2655 dtype = ((Primitive *) ptr)->getPrimitiveDataType(label.c_str());
2656 }
else if (strcmp(data_group,
"object") == 0) {
2657 dtype = ((
CompoundObject *) ptr)->getObjectDataType(label.c_str());
2658 }
else if (strcmp(data_group,
"material") == 0) {
2659 dtype = ((
Material *) ptr)->getMaterialDataType(label.c_str());
2660 }
else if (strcmp(data_group,
"global") == 0) {
2663 helios_runtime_error(
"ERROR (Context::writeDataToXMLstream): unknown data group argument of " + std::string(data_group) +
". Must be one of primitive, object, material, or global.");
2666 if (dtype == HELIOS_TYPE_UINT) {
2667 outfile <<
"\t<data_uint label=\"" << label <<
"\">" << std::flush;
2668 std::vector<uint> data;
2669 if (strcmp(data_group,
"primitive") == 0) {
2670 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2671 }
else if (strcmp(data_group,
"object") == 0) {
2673 }
else if (strcmp(data_group,
"material") == 0) {
2674 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2678 for (
int j = 0; j < data.size(); j++) {
2679 outfile << data.at(j) << std::flush;
2680 if (j != data.size() - 1) {
2681 outfile <<
" " << std::flush;
2684 outfile <<
"</data_uint>" << std::endl;
2685 }
else if (dtype == HELIOS_TYPE_INT) {
2686 outfile <<
"\t<data_int label=\"" << label <<
"\">" << std::flush;
2687 std::vector<int> data;
2688 if (strcmp(data_group,
"primitive") == 0) {
2689 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2690 }
else if (strcmp(data_group,
"object") == 0) {
2692 }
else if (strcmp(data_group,
"material") == 0) {
2693 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2697 for (
int j = 0; j < data.size(); j++) {
2698 outfile << data.at(j) << std::flush;
2699 if (j != data.size() - 1) {
2700 outfile <<
" " << std::flush;
2703 outfile <<
"</data_int>" << std::endl;
2704 }
else if (dtype == HELIOS_TYPE_FLOAT) {
2705 outfile <<
"\t<data_float label=\"" << label <<
"\">" << std::flush;
2706 std::vector<float> data;
2707 if (strcmp(data_group,
"primitive") == 0) {
2708 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2709 }
else if (strcmp(data_group,
"object") == 0) {
2711 }
else if (strcmp(data_group,
"material") == 0) {
2712 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2716 for (
int j = 0; j < data.size(); j++) {
2717 outfile << data.at(j) << std::flush;
2718 if (j != data.size() - 1) {
2719 outfile <<
" " << std::flush;
2722 outfile <<
"</data_float>" << std::endl;
2723 }
else if (dtype == HELIOS_TYPE_DOUBLE) {
2724 outfile <<
"\t<data_double label=\"" << label <<
"\">" << std::flush;
2725 std::vector<double> data;
2726 if (strcmp(data_group,
"primitive") == 0) {
2727 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2728 }
else if (strcmp(data_group,
"object") == 0) {
2730 }
else if (strcmp(data_group,
"material") == 0) {
2731 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2735 for (
int j = 0; j < data.size(); j++) {
2736 outfile << data.at(j) << std::flush;
2737 if (j != data.size() - 1) {
2738 outfile <<
" " << std::flush;
2741 outfile <<
"</data_double>" << std::endl;
2742 }
else if (dtype == HELIOS_TYPE_VEC2) {
2743 outfile <<
"\t<data_vec2 label=\"" << label <<
"\">" << std::flush;
2744 std::vector<vec2> data;
2745 if (strcmp(data_group,
"primitive") == 0) {
2746 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2747 }
else if (strcmp(data_group,
"object") == 0) {
2749 }
else if (strcmp(data_group,
"material") == 0) {
2750 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2754 for (
int j = 0; j < data.size(); j++) {
2755 outfile << data.at(j).x <<
" " << data.at(j).y << std::flush;
2756 if (j != data.size() - 1) {
2757 outfile <<
" " << std::flush;
2760 outfile <<
"</data_vec2>" << std::endl;
2761 }
else if (dtype == HELIOS_TYPE_VEC3) {
2762 outfile <<
"\t<data_vec3 label=\"" << label <<
"\">" << std::flush;
2763 std::vector<vec3> data;
2764 if (strcmp(data_group,
"primitive") == 0) {
2765 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2766 }
else if (strcmp(data_group,
"object") == 0) {
2768 }
else if (strcmp(data_group,
"material") == 0) {
2769 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2773 for (
int j = 0; j < data.size(); j++) {
2774 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z << std::flush;
2775 if (j != data.size() - 1) {
2776 outfile <<
" " << std::flush;
2779 outfile <<
"</data_vec3>" << std::endl;
2780 }
else if (dtype == HELIOS_TYPE_VEC4) {
2781 outfile <<
"\t<data_vec4 label=\"" << label <<
"\">" << std::flush;
2782 std::vector<vec4> data;
2783 if (strcmp(data_group,
"primitive") == 0) {
2784 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2785 }
else if (strcmp(data_group,
"object") == 0) {
2787 }
else if (strcmp(data_group,
"material") == 0) {
2788 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2792 for (
int j = 0; j < data.size(); j++) {
2793 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z <<
" " << data.at(j).w << std::flush;
2794 if (j != data.size() - 1) {
2795 outfile <<
" " << std::flush;
2798 outfile <<
"</data_vec4>" << std::endl;
2799 }
else if (dtype == HELIOS_TYPE_INT2) {
2800 outfile <<
"\t<data_int2 label=\"" << label <<
"\">" << std::flush;
2801 std::vector<int2> data;
2802 if (strcmp(data_group,
"primitive") == 0) {
2803 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2804 }
else if (strcmp(data_group,
"object") == 0) {
2806 }
else if (strcmp(data_group,
"material") == 0) {
2807 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2811 for (
int j = 0; j < data.size(); j++) {
2812 outfile << data.at(j).x <<
" " << data.at(j).y << std::flush;
2813 if (j != data.size() - 1) {
2814 outfile <<
" " << std::flush;
2817 outfile <<
"</data_int2>" << std::endl;
2818 }
else if (dtype == HELIOS_TYPE_INT3) {
2819 outfile <<
"\t<data_int3 label=\"" << label <<
"\">" << std::flush;
2820 std::vector<int3> data;
2821 if (strcmp(data_group,
"primitive") == 0) {
2822 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2823 }
else if (strcmp(data_group,
"object") == 0) {
2825 }
else if (strcmp(data_group,
"material") == 0) {
2826 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2830 for (
int j = 0; j < data.size(); j++) {
2831 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z << std::flush;
2832 if (j != data.size() - 1) {
2833 outfile <<
" " << std::flush;
2836 outfile <<
"</data_int3>" << std::endl;
2837 }
else if (dtype == HELIOS_TYPE_INT4) {
2838 outfile <<
"\t<data_int3 label=\"" << label <<
"\">" << std::flush;
2839 std::vector<int4> data;
2840 if (strcmp(data_group,
"primitive") == 0) {
2841 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2842 }
else if (strcmp(data_group,
"object") == 0) {
2844 }
else if (strcmp(data_group,
"material") == 0) {
2845 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2849 for (
int j = 0; j < data.size(); j++) {
2850 outfile << data.at(j).x <<
" " << data.at(j).y <<
" " << data.at(j).z <<
" " << data.at(j).w << std::flush;
2851 if (j != data.size() - 1) {
2852 outfile <<
" " << std::flush;
2855 outfile <<
"</data_int4>" << std::endl;
2856 }
else if (dtype == HELIOS_TYPE_STRING) {
2857 outfile <<
"\t<data_string label=\"" << label <<
"\">" << std::flush;
2858 std::vector<std::string> data;
2859 if (strcmp(data_group,
"primitive") == 0) {
2860 ((Primitive *) ptr)->getPrimitiveData(label.c_str(), data);
2861 }
else if (strcmp(data_group,
"object") == 0) {
2863 }
else if (strcmp(data_group,
"material") == 0) {
2864 ((
Material *) ptr)->getMaterialData(label.c_str(), data);
2868 for (
int j = 0; j < data.size(); j++) {
2869 outfile << data.at(j) << std::flush;
2870 if (j != data.size() - 1) {
2871 outfile <<
" " << std::flush;
2874 outfile <<
"</data_string>" << std::endl;
2884 for (
uint objID: objIDs) {
2886 helios_runtime_error(
"ERROR (Context::writeXML_byobject): Object with ID of " + std::to_string(objID) +
" does not exist.");
2894 std::cout <<
"Writing XML file " << filename <<
"..." << std::flush;
2897 std::string xmlfilename = filename;
2904 helios_runtime_error(
"ERROR (Context::writeXML): Invalid output file " + xmlfilename +
". No file name was provided.");
2908 if (file_extension !=
".xml" && file_extension !=
".XML") {
2909 xmlfilename.append(
".xml");
2914 std::ofstream outfile;
2915 outfile.open(xmlfilename);
2917 outfile <<
"<?xml version=\"1.0\"?>\n\n";
2919 outfile <<
"<helios>\n\n";
2924 std::set<std::string> material_labels_used;
2925 for (
uint UUID: UUIDs) {
2927 uint matID = getPrimitivePointer_private(UUID)->materialID;
2928 if (materials.find(matID) != materials.end()) {
2929 material_labels_used.insert(materials.at(matID).label);
2934 if (!material_labels_used.empty()) {
2935 outfile <<
" <materials>" << std::endl;
2936 for (
const std::string &label: material_labels_used) {
2939 const Material &mat = materials.at(matID);
2940 outfile <<
"\t<material label=\"" << mat.
label <<
"\">" << std::endl;
2941 outfile <<
"\t\t<color>" << mat.
color.
r <<
" " << mat.
color.
g <<
" " << mat.
color.
b <<
" " << mat.
color.
a <<
"</color>" << std::endl;
2943 outfile <<
"\t\t<texture>" << mat.
texture_file <<
"</texture>" << std::endl;
2946 outfile <<
"\t\t<texture_override>1</texture_override>" << std::endl;
2949 outfile <<
"\t\t<twosided_flag>" << mat.
twosided_flag <<
"</twosided_flag>" << std::endl;
2953 if (!mdata.empty()) {
2954 writeDataToXMLstream(
"material", mdata,
const_cast<Material *
>(&mat), outfile);
2956 outfile <<
"\t</material>" << std::endl;
2959 outfile <<
" </materials>\n" << std::endl;
2966 outfile <<
" <date>" << std::endl;
2968 outfile <<
"\t<day>" << date.day <<
"</day>" << std::endl;
2969 outfile <<
"\t<month>" << date.month <<
"</month>" << std::endl;
2970 outfile <<
"\t<year>" << date.year <<
"</year>" << std::endl;
2972 outfile <<
" </date>" << std::endl;
2976 outfile <<
" <time>" << std::endl;
2978 outfile <<
"\t<hour>" << time.hour <<
"</hour>" << std::endl;
2979 outfile <<
"\t<minute>" << time.minute <<
"</minute>" << std::endl;
2980 outfile <<
"\t<second>" << time.second <<
"</second>" << std::endl;
2982 outfile <<
" </time>" << std::endl;
2986 for (
uint UUID: UUIDs) {
2991 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()?");
2993 helios_runtime_error(
"ERROR (Context::writeXML): Primitive with UUID of " + std::to_string(p) +
" does not exist.");
2997 Primitive *prim = getPrimitivePointer_private(p);
2999 uint parent_objID = prim->getParentObjectID();
3003 std::string texture_file = prim->getTextureFile();
3005 std::vector<std::string> pdata = prim->listPrimitiveData();
3014 outfile <<
" <patch>" << std::endl;
3016 outfile <<
" <triangle>" << std::endl;
3018 outfile <<
" <voxel>" << std::endl;
3021 outfile <<
"\t<UUID>" << p <<
"</UUID>" << std::endl;
3023 if (parent_objID > 0) {
3024 outfile <<
"\t<objID>" << parent_objID <<
"</objID>" << std::endl;
3028 if (materials.find(prim->materialID) != materials.end()) {
3029 outfile <<
"\t<material>" << materials.at(prim->materialID).label <<
"</material>" << std::endl;
3032 if (!pdata.empty()) {
3033 writeDataToXMLstream(
"primitive", pdata, prim, outfile);
3038 Patch *patch = getPatchPointer_private(p);
3039 float transform[16];
3040 prim->getTransformationMatrix(transform);
3042 outfile <<
"\t<transform>";
3043 for (
float i: transform) {
3044 outfile << i <<
" ";
3046 outfile <<
"</transform>" << std::endl;
3047 std::vector<vec2> uv = patch->getTextureUV();
3049 outfile <<
"\t<textureUV>" << std::flush;
3050 for (
int i = 0; i < uv.size(); i++) {
3051 outfile << uv.at(i).x <<
" " << uv.at(i).y << std::flush;
3052 if (i != uv.size() - 1) {
3053 outfile <<
" " << std::flush;
3056 outfile <<
"</textureUV>" << std::endl;
3061 outfile <<
" </patch>" << std::endl;
3065 float transform[16];
3066 prim->getTransformationMatrix(transform);
3068 outfile <<
"\t<transform>";
3069 for (
float i: transform) {
3070 outfile << i <<
" ";
3072 outfile <<
"</transform>" << std::endl;
3074 std::vector<vec2> uv = getTrianglePointer_private(p)->getTextureUV();
3076 outfile <<
"\t<textureUV>" << std::flush;
3077 for (
int i = 0; i < uv.size(); i++) {
3078 outfile << uv.at(i).x <<
" " << uv.at(i).y << std::flush;
3079 if (i != uv.size() - 1) {
3080 outfile <<
" " << std::flush;
3083 outfile <<
"</textureUV>" << std::endl;
3088 outfile <<
" </triangle>" << std::endl;
3092 float transform[16];
3093 prim->getTransformationMatrix(transform);
3095 outfile <<
"\t<transform>";
3096 for (
float i: transform) {
3097 outfile << i <<
" ";
3099 outfile <<
"</transform>" << std::endl;
3104 outfile <<
" </voxel>" << std::endl;
3110 for (
auto o: objectIDs) {
3113 std::string texture_file =
obj->getTextureFile();
3115 std::vector<std::string> odata =
obj->listObjectData();
3118 outfile <<
" <tile>" << std::endl;
3120 outfile <<
" <box>" << std::endl;
3122 outfile <<
" <cone>" << std::endl;
3124 outfile <<
" <disk>" << std::endl;
3126 outfile <<
" <sphere>" << std::endl;
3128 outfile <<
" <tube>" << std::endl;
3130 outfile <<
" <polymesh>" << std::endl;
3133 outfile <<
"\t<objID>" << o <<
"</objID>" << std::endl;
3134 if (
obj->hasTexture()) {
3135 outfile <<
"\t<texture>" << texture_file <<
"</texture>" << std::endl;
3138 if (!odata.empty()) {
3139 writeDataToXMLstream(
"object", odata,
obj, outfile);
3142 std::vector<std::string> pdata_labels;
3143 std::vector<HeliosDataType> pdata_types;
3144 std::vector<uint> primitiveUUIDs =
obj->getPrimitiveUUIDs();
3145 for (
uint UUID: primitiveUUIDs) {
3146 std::vector<std::string> labels = getPrimitivePointer_private(UUID)->listPrimitiveData();
3147 for (
const auto &label: labels) {
3148 if (find(pdata_labels.begin(), pdata_labels.end(), label) == pdata_labels.end()) {
3149 pdata_labels.push_back(label);
3154 for (
size_t l = 0; l < pdata_labels.size(); l++) {
3156 outfile <<
"\t<primitive_data_float " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3157 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3159 std::vector<float> data;
3161 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3162 for (
float i: data) {
3163 outfile << i << std::flush;
3165 outfile <<
" </data>" << std::endl;
3168 outfile <<
"\t</primitive_data_float>" << std::endl;
3170 outfile <<
"\t<primitive_data_double " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3171 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3173 std::vector<double> data;
3175 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3176 for (
double i: data) {
3177 outfile << i << std::flush;
3179 outfile <<
" </data>" << std::endl;
3182 outfile <<
"\t</primitive_data_double>" << std::endl;
3184 outfile <<
"\t<primitive_data_uint " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3185 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3187 std::vector<uint> data;
3189 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3190 for (
unsigned int i: data) {
3191 outfile << i << std::flush;
3193 outfile <<
" </data>" << std::endl;
3196 outfile <<
"\t</primitive_data_uint>" << std::endl;
3198 outfile <<
"\t<primitive_data_int " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3199 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3201 std::vector<int> data;
3203 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3205 outfile << i << std::flush;
3207 outfile <<
" </data>" << std::endl;
3210 outfile <<
"\t</primitive_data_int>" << std::endl;
3212 outfile <<
"\t<primitive_data_int2 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3213 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3215 std::vector<int2> data;
3217 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3218 for (
auto &i: data) {
3219 outfile << i.x <<
" " << i.y << std::flush;
3221 outfile <<
" </data>" << std::endl;
3224 outfile <<
"\t</primitive_data_int2>" << std::endl;
3226 outfile <<
"\t<primitive_data_int3 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3227 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3229 std::vector<int3> data;
3231 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3232 for (
auto &i: data) {
3233 outfile << i.x <<
" " << i.y <<
" " << i.z << std::flush;
3235 outfile <<
" </data>" << std::endl;
3238 outfile <<
"\t</primitive_data_int3>" << std::endl;
3240 outfile <<
"\t<primitive_data_int4 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3241 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3243 std::vector<int4> data;
3245 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3246 for (
auto &i: data) {
3247 outfile << i.x <<
" " << i.y <<
" " << i.z <<
" " << i.w << std::flush;
3249 outfile <<
" </data>" << std::endl;
3252 outfile <<
"\t</primitive_data_int4>" << std::endl;
3254 outfile <<
"\t<primitive_data_vec2 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3255 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3257 std::vector<vec2> data;
3259 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3260 for (
auto &i: data) {
3261 outfile << i.x <<
" " << i.y << std::flush;
3263 outfile <<
" </data>" << std::endl;
3266 outfile <<
"\t</primitive_data_vec2>" << std::endl;
3268 outfile <<
"\t<primitive_data_vec3 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3269 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3271 std::vector<vec3> data;
3273 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3274 for (
auto &i: data) {
3275 outfile << i.x <<
" " << i.y <<
" " << i.z << std::flush;
3277 outfile <<
" </data>" << std::endl;
3280 outfile <<
"\t</primitive_data_vec3>" << std::endl;
3282 outfile <<
"\t<primitive_data_vec4 " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3283 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3285 std::vector<vec4> data;
3287 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3288 for (
auto &i: data) {
3289 outfile << i.x <<
" " << i.y <<
" " << i.z <<
" " << i.w << std::flush;
3291 outfile <<
" </data>" << std::endl;
3294 outfile <<
"\t</primitive_data_vec4>" << std::endl;
3296 outfile <<
"\t<primitive_data_string " <<
"label=\"" << pdata_labels.at(l) <<
"\">" << std::endl;
3297 for (
size_t p = 0; p < primitiveUUIDs.size(); p++) {
3299 std::vector<std::string> data;
3301 outfile <<
"\t\t<data label=\"" << p <<
"\"> " << std::flush;
3302 for (
const auto &i: data) {
3303 outfile << i << std::flush;
3305 outfile <<
" </data>" << std::endl;
3308 outfile <<
"\t</primitive_data_string>" << std::endl;
3314 Tile *tile = getTileObjectPointer_private(o);
3316 float transform[16];
3320 outfile <<
"\t<subdivisions>" << subdiv.
x <<
" " << subdiv.
y <<
"</subdivisions>" << std::endl;
3322 outfile <<
"\t<transform> ";
3323 for (
float i: transform) {
3324 outfile << i <<
" ";
3326 outfile <<
"</transform>" << std::endl;
3328 outfile <<
" </tile>" << std::endl;
3332 Sphere *sphere = getSphereObjectPointer_private(o);
3334 float transform[16];
3335 sphere->getTransformationMatrix(transform);
3337 outfile <<
"\t<transform> ";
3338 for (
float i: transform) {
3339 outfile << i <<
" ";
3341 outfile <<
"</transform>" << std::endl;
3343 uint subdiv = sphere->getSubdivisionCount();
3344 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3346 outfile <<
" </sphere>" << std::endl;
3350 Tube *tube = getTubeObjectPointer_private(o);
3352 float transform[16];
3355 outfile <<
"\t<transform> ";
3356 for (
float i: transform) {
3357 outfile << i <<
" ";
3359 outfile <<
"</transform>" << std::endl;
3362 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3364 std::vector<vec3> nodes = tube->
getNodes();
3367 assert(nodes.size() == radius.size());
3368 outfile <<
"\t<nodes> " << std::endl;
3369 for (
auto &node: nodes) {
3370 outfile <<
"\t\t" << node.x <<
" " << node.y <<
" " << node.z << std::endl;
3372 outfile <<
"\t</nodes> " << std::endl;
3373 outfile <<
"\t<radius> " << std::endl;
3374 for (
float radiu: radius) {
3375 outfile <<
"\t\t" << radiu << std::endl;
3377 outfile <<
"\t</radius> " << std::endl;
3379 if (texture_file.empty()) {
3382 outfile <<
"\t<color> " << std::endl;
3383 for (
auto &color: colors) {
3384 outfile <<
"\t\t" << color.r <<
" " << color.g <<
" " << color.b << std::endl;
3386 outfile <<
"\t</color> " << std::endl;
3389 outfile <<
" </tube>" << std::endl;
3393 Box *box = getBoxObjectPointer_private(o);
3395 float transform[16];
3398 outfile <<
"\t<transform> ";
3399 for (
float i: transform) {
3400 outfile << i <<
" ";
3402 outfile <<
"</transform>" << std::endl;
3405 outfile <<
"\t<subdivisions> " << subdiv.
x <<
" " << subdiv.
y <<
" " << subdiv.
z <<
" </subdivisions>" << std::endl;
3407 outfile <<
" </box>" << std::endl;
3411 Disk *disk = getDiskObjectPointer_private(o);
3413 float transform[16];
3416 outfile <<
"\t<transform> ";
3417 for (
float i: transform) {
3418 outfile << i <<
" ";
3420 outfile <<
"</transform>" << std::endl;
3423 outfile <<
"\t<subdivisions> " << subdiv.
x <<
" " << subdiv.
y <<
" </subdivisions>" << std::endl;
3425 outfile <<
" </disk>" << std::endl;
3429 Cone *cone = getConeObjectPointer_private(o);
3431 float transform[16];
3434 outfile <<
"\t<transform> ";
3435 for (
float i: transform) {
3436 outfile << i <<
" ";
3438 outfile <<
"</transform>" << std::endl;
3441 outfile <<
"\t<subdivisions> " << subdiv <<
" </subdivisions>" << std::endl;
3446 assert(nodes.size() == radius.size());
3447 outfile <<
"\t<nodes> " << std::endl;
3448 for (
auto &node: nodes) {
3449 outfile <<
"\t\t" << node.x <<
" " << node.y <<
" " << node.z << std::endl;
3451 outfile <<
"\t</nodes> " << std::endl;
3452 outfile <<
"\t<radius> " << std::endl;
3453 for (
float radiu: radius) {
3454 outfile <<
"\t\t" << radiu << std::endl;
3456 outfile <<
"\t</radius> " << std::endl;
3458 outfile <<
" </cone>" << std::endl;
3462 outfile <<
" </polymesh>" << std::endl;
3469 for (
const auto &iter: globaldata) {
3470 std::string label = iter.first;
3474 outfile <<
" <globaldata_uint label=\"" << label <<
"\">" << std::flush;
3475 for (
size_t i = 0; i < data.size; i++) {
3476 outfile << data.global_data_uint.at(i) << std::flush;
3477 if (i != data.size - 1) {
3478 outfile <<
" " << std::flush;
3481 outfile <<
"</globaldata_uint>" << std::endl;
3483 outfile <<
" <globaldata_int label=\"" << label <<
"\">" << std::flush;
3484 for (
size_t i = 0; i < data.size; i++) {
3485 outfile << data.global_data_int.at(i) << std::flush;
3486 if (i != data.size - 1) {
3487 outfile <<
" " << std::flush;
3490 outfile <<
"</globaldata_int>" << std::endl;
3492 outfile <<
" <globaldata_float label=\"" << label <<
"\">" << std::flush;
3493 for (
size_t i = 0; i < data.size; i++) {
3494 outfile << data.global_data_float.at(i) << std::flush;
3495 if (i != data.size - 1) {
3496 outfile <<
" " << std::flush;
3499 outfile <<
"</globaldata_float>" << std::endl;
3501 outfile <<
" <globaldata_double label=\"" << label <<
"\">" << std::flush;
3502 for (
size_t i = 0; i < data.size; i++) {
3503 outfile << data.global_data_double.at(i) << std::flush;
3504 if (i != data.size - 1) {
3505 outfile <<
" " << std::flush;
3508 outfile <<
"</globaldata_double>" << std::endl;
3510 outfile <<
" <globaldata_vec2 label=\"" << label <<
"\">" << std::endl;
3511 for (
size_t i = 0; i < data.size; i++) {
3512 outfile <<
" " << data.global_data_vec2.at(i).x <<
" " << data.global_data_vec2.at(i).y << std::endl;
3514 outfile <<
" </globaldata_vec2>" << std::endl;
3516 outfile <<
" <globaldata_vec3 label=\"" << label <<
"\">" << std::endl;
3517 for (
size_t i = 0; i < data.size; i++) {
3518 outfile <<
" " << data.global_data_vec3.at(i).x <<
" " << data.global_data_vec3.at(i).y <<
" " << data.global_data_vec3.at(i).z << std::endl;
3520 outfile <<
" </globaldata_vec3>" << std::endl;
3522 outfile <<
" <globaldata_vec4 label=\"" << label <<
"\">" << std::endl;
3523 for (
size_t i = 0; i < data.size; i++) {
3524 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;
3526 outfile <<
" </globaldata_vec4>" << std::endl;
3528 outfile <<
" <globaldata_int2 label=\"" << label <<
"\">" << std::endl;
3529 for (
size_t i = 0; i < data.size; i++) {
3530 outfile <<
" " << data.global_data_int2.at(i).x <<
" " << data.global_data_int2.at(i).y << std::endl;
3532 outfile <<
" </globaldata_int2>" << std::endl;
3534 outfile <<
" <globaldata_int3 label=\"" << label <<
"\">" << std::endl;
3535 for (
size_t i = 0; i < data.size; i++) {
3536 outfile <<
" " << data.global_data_int3.at(i).x <<
" " << data.global_data_int3.at(i).y <<
" " << data.global_data_int3.at(i).z << std::endl;
3538 outfile <<
" </globaldata_int3>" << std::endl;
3540 outfile <<
" <globaldata_int4 label=\"" << label <<
"\">" << std::endl;
3541 for (
size_t i = 0; i < data.size; i++) {
3542 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;
3544 outfile <<
" </globaldata_int4>" << std::endl;
3546 outfile <<
" <globaldata_string label=\"" << label <<
"\">" << std::flush;
3547 for (
size_t i = 0; i < data.size; i++) {
3548 outfile << data.global_data_string.at(i) << std::flush;
3549 if (i != data.size - 1) {
3550 outfile <<
" " << std::flush;
3553 outfile <<
"</globaldata_string>" << std::endl;
3559 for (
const auto &iter: timeseries_data) {
3560 std::string label = iter.first;
3562 std::vector<float> data = iter.second;
3563 std::vector<double> dateval = timeseries_datevalue.at(label);
3565 assert(data.size() == dateval.size());
3567 outfile <<
" <timeseries label=\"" << label <<
"\">" << std::endl;
3569 for (
size_t i = 0; i < data.size(); i++) {
3573 outfile <<
"\t<datapoint>" << std::endl;
3575 outfile <<
"\t <date>" << a_date.
day <<
" " << a_date.
month <<
" " << a_date.
year <<
"</date>" << std::endl;
3577 outfile <<
"\t <time>" << a_time.
hour <<
" " << a_time.
minute <<
" " << a_time.
second <<
"</time>" << std::endl;
3579 outfile <<
"\t <value>" << data.at(i) <<
"</value>" << std::endl;
3581 outfile <<
"\t</datapoint>" << std::endl;
3584 outfile <<
" </timeseries>" << std::endl;
3589 outfile <<
"\n</helios>\n";
3594 std::cout <<
"done." << std::endl;
3602std::vector<uint>
Context::loadPLY(
const char *filename,
const vec3 &origin,
float height,
const std::string &upaxis,
bool silent) {
3607 return loadPLY(filename, origin, height, rotation, RGB::blue, upaxis, silent);
3610std::vector<uint>
Context::loadPLY(
const char *filename,
const vec3 &origin,
float height,
const RGBcolor &default_color,
const std::string &upaxis,
bool silent) {
3616 std::cout <<
"Reading PLY file " << filename <<
"..." << std::flush;
3619 std::string fn = filename;
3621 if (ext !=
".ply" && ext !=
".PLY") {
3625 if (upaxis !=
"XUP" && upaxis !=
"YUP" && upaxis !=
"ZUP") {
3626 helios_runtime_error(
"ERROR (Context::loadPLY): " + upaxis +
" is not a valid up-axis. Please specify a value of XUP, YUP, or ZUP.");
3629 std::string line, prop;
3631 uint vertexCount = 0, faceCount = 0;
3633 std::vector<vec3> vertices;
3634 std::vector<std::vector<int>> faces;
3635 std::vector<RGBcolor> colors;
3636 std::vector<std::string> properties;
3638 bool ifColor =
false;
3642 std::string resolved_filename = resolved_path.string();
3644 std::ifstream inputPly;
3645 inputPly.open(resolved_filename);
3647 if (!inputPly.is_open()) {
3655 if (
"ply" != line) {
3656 helios_runtime_error(
"ERROR (Context::loadPLY): " + std::string(filename) +
" is not a PLY file.");
3661 if (
"format" != line) {
3662 helios_runtime_error(
"ERROR (Context::loadPLY): could not determine data format of " + std::string(filename));
3666 if (
"ascii" != line) {
3670 std::string temp_string;
3672 while (
"end_header" != line) {
3675 if (
"comment" == line) {
3676 getline(inputPly, line);
3677 }
else if (
"element" == line) {
3680 if (
"vertex" == line) {
3681 inputPly >> temp_string;
3683 helios_runtime_error(
"ERROR (Context::loadPLY): PLY file read failed. Vertex count value should be a non-negative integer.");
3685 }
else if (
"face" == line) {
3686 inputPly >> temp_string;
3688 helios_runtime_error(
"ERROR (Context::loadPLY): PLY file read failed. Face count value should be a non-negative integer.");
3691 }
else if (
"property" == line) {
3694 if (
"list" != line) {
3696 properties.push_back(prop);
3701 for (
auto &property: properties) {
3702 if (property ==
"red") {
3707 std::cout <<
"forming " << faceCount <<
" triangles..." << std::flush;
3710 vertices.resize(vertexCount);
3711 colors.resize(vertexCount);
3712 faces.resize(faceCount);
3717 for (
uint row = 0; row < vertexCount; row++) {
3718 for (
auto &property: properties) {
3719 if (property ==
"x") {
3720 inputPly >> temp_string;
3723 helios_runtime_error(
"ERROR (Context::loadPLY): X value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3725 if (upaxis ==
"XUP") {
3726 vertices.at(row).z = x;
3727 }
else if (upaxis ==
"YUP") {
3728 vertices.at(row).y = x;
3729 }
else if (upaxis ==
"ZUP") {
3730 vertices.at(row).x = x;
3732 }
else if (property ==
"y") {
3733 inputPly >> temp_string;
3736 helios_runtime_error(
"ERROR (Context::loadPLY): Y value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3738 if (upaxis ==
"XUP") {
3739 vertices.at(row).x = y;
3740 }
else if (upaxis ==
"YUP") {
3741 vertices.at(row).z = y;
3742 }
else if (upaxis ==
"ZUP") {
3743 vertices.at(row).y = y;
3745 }
else if (property ==
"z") {
3746 inputPly >> temp_string;
3749 helios_runtime_error(
"ERROR (Context::loadPLY): Z value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3751 if (upaxis ==
"XUP") {
3752 vertices.at(row).y = z;
3753 }
else if (upaxis ==
"YUP") {
3754 vertices.at(row).x = z;
3755 }
else if (upaxis ==
"ZUP") {
3756 vertices.at(row).z = z;
3758 }
else if (property ==
"red") {
3759 inputPly >> temp_string;
3760 if (!
parse_float(temp_string, colors.at(row).r)) {
3761 helios_runtime_error(
"ERROR (Context::loadPLY): red color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3763 colors.at(row).r /= 255.f;
3764 }
else if (property ==
"green") {
3765 inputPly >> temp_string;
3766 if (!
parse_float(temp_string, colors.at(row).g)) {
3767 helios_runtime_error(
"ERROR (Context::loadPLY): green color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3769 colors.at(row).g /= 255.f;
3770 }
else if (property ==
"blue") {
3771 inputPly >> temp_string;
3772 if (!
parse_float(temp_string, colors.at(row).b)) {
3773 helios_runtime_error(
"ERROR (Context::loadPLY): blue color value for vertex " + std::to_string(row) +
" is invalid and could not be read.");
3775 colors.at(row).b /= 255.f;
3781 if (inputPly.eof()) {
3782 helios_runtime_error(
"ERROR (Context::loadPLY): Read past end of file while reading vertices. Vertex count specified in header may be incorrect.");
3791 for (
uint row = 0; row < vertexCount; row++) {
3792 if (vertices.at(row).x < boxmin.
x) {
3793 boxmin.
x = vertices.at(row).x;
3795 if (vertices.at(row).y < boxmin.
y) {
3796 boxmin.
y = vertices.at(row).y;
3798 if (vertices.at(row).z < boxmin.
z) {
3799 boxmin.
z = vertices.at(row).z;
3802 if (vertices.at(row).x > boxmax.
x) {
3803 boxmax.
x = vertices.at(row).x;
3805 if (vertices.at(row).y > boxmax.
y) {
3806 boxmax.
y = vertices.at(row).y;
3808 if (vertices.at(row).z > boxmax.
z) {
3809 boxmax.
z = vertices.at(row).z;
3816 scl = height / (boxmax.
z - boxmin.
z);
3818 for (
uint row = 0; row < vertexCount; row++) {
3819 vertices.at(row).z -= boxmin.
z;
3821 vertices.at(row).x *= scl;
3822 vertices.at(row).y *= scl;
3823 vertices.at(row).z *= scl;
3825 vertices.at(row) =
rotatePoint(vertices.at(row), rotation) + origin;
3831 std::vector<uint> UUID;
3832 for (
uint row = 0; row < faceCount; row++) {
3833 inputPly >> temp_string;
3836 helios_runtime_error(
"ERROR (Context::loadPLY): Vertex count for face " + std::to_string(row) +
" should be a non-negative integer.");
3839 faces.at(row).resize(v);
3841 for (
uint i = 0; i < v; i++) {
3842 inputPly >> temp_string;
3843 if (!
parse_int(temp_string, faces.at(row).at(i))) {
3844 helios_runtime_error(
"ERROR (Context::loadPLY): Vertex index for face " + std::to_string(row) +
" is invalid and could not be read.");
3850 for (
uint t = 2; t < v; t++) {
3853 color = colors.at(faces.at(row).front());
3855 color = default_color;
3858 vec3 v0 = vertices.at(faces.at(row).front());
3859 vec3 v1 = vertices.at(faces.at(row).at(t - 1));
3860 vec3 v2 = vertices.at(faces.at(row).at(t));
3862 if ((v0 - v1).magnitude() < 1e-10f || (v0 - v2).magnitude() < 1e-10f || (v1 - v2).magnitude() < 1e-10f) {
3868 if (triangle_area < MIN_TRIANGLE_AREA_THRESHOLD) {
3877 if (inputPly.eof()) {
3878 helios_runtime_error(
"ERROR (Context::loadPLY): Read past end of file while reading faces. Face count specified in header may be incorrect.");
3883 std::cout <<
"done." << std::endl;
3895 std::string fname{filename ? filename :
""};
3897 const auto dotPos = fname.find_last_of(
'.');
3898 const std::string ext = (dotPos != std::string::npos) ? fname.substr(dotPos) :
"";
3900 auto ciEqual = [](
const char a,
const char b) {
return std::tolower(a) == std::tolower(b); };
3901 bool isPly = (ext.size() == 4) && ciEqual(ext[1],
'p') && ciEqual(ext[2],
'l') && ciEqual(ext[3],
'y');
3904 helios_runtime_error(
"ERROR (Context::writePLY) Invalid file extension for " + fname +
". Expected a file ending in '.ply'.");
3908 std::ofstream PLYfile;
3909 PLYfile.open(fname, std::ios::out | std::ios::trunc);
3911 if (!PLYfile.is_open()) {
3915 PLYfile <<
"ply" << std::endl <<
"format ascii 1.0" << std::endl <<
"comment Helios generated" << std::endl;
3917 std::vector<int3> faces;
3918 std::vector<vec3> verts;
3919 std::vector<RGBcolor> colors;
3921 size_t vertex_count = 0;
3923 for (
auto UUID: UUIDs) {
3924 std::vector<vec3> vertices = getPrimitivePointer_private(UUID)->getVertices();
3925 PrimitiveType type = getPrimitivePointer_private(UUID)->getType();
3926 RGBcolor C = getPrimitivePointer_private(UUID)->getColor();
3930 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2));
3931 for (
int i = 0; i < 3; i++) {
3932 verts.push_back(vertices.at(i));
3933 colors.push_back(C);
3937 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2));
3938 faces.push_back(
make_int3((
int) vertex_count, (
int) vertex_count + 2, (
int) vertex_count + 3));
3939 for (
int i = 0; i < 4; i++) {
3940 verts.push_back(vertices.at(i));
3941 colors.push_back(C);
3947 PLYfile <<
"element vertex " << verts.size() << std::endl;
3948 PLYfile <<
"property float x" << std::endl <<
"property float y" << std::endl <<
"property float z" << std::endl;
3949 PLYfile <<
"property uchar red" << std::endl <<
"property uchar green" << std::endl <<
"property uchar blue" << std::endl;
3950 PLYfile <<
"element face " << faces.size() << std::endl;
3951 PLYfile <<
"property list uchar int vertex_indices" << std::endl <<
"end_header" << std::endl;
3953 for (
size_t v = 0; v < verts.size(); v++) {
3954 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;
3957 for (
auto &face: faces) {
3958 PLYfile <<
"3 " << face.x <<
" " << face.y <<
" " << face.z << std::endl;
3969 return loadOBJ(filename, origin,
make_vec3(0, 0, height), rotation, default_color,
"ZUP", silent);
3973 return loadOBJ(filename, origin,
make_vec3(0, 0, height), rotation, default_color, upaxis, silent);
3979 std::cout <<
"Reading OBJ file " << filename <<
"..." << std::flush;
3982 std::string fn = filename;
3984 if (ext !=
".obj" && ext !=
".OBJ") {
3988 if (strcmp(upaxis,
"XUP") != 0 && strcmp(upaxis,
"YUP") != 0 && strcmp(upaxis,
"ZUP") != 0) {
3989 helios_runtime_error(
"ERROR (Context::loadOBJ): Up axis of " + std::string(upaxis) +
" is not valid. Should be one of 'XUP', 'YUP', or 'ZUP'.");
3992 std::string line, prop;
3994 std::vector<vec3> vertices;
3995 std::vector<std::string> objects;
3996 std::vector<vec2> texture_uv;
3997 std::map<std::string, std::vector<std::vector<int>>> face_inds, texture_inds;
3999 std::map<std::string, OBJmaterial> materials;
4001 std::vector<uint> UUID;
4005 std::string resolved_filename = resolved_path.string();
4007 std::ifstream inputOBJ, inputMTL;
4008 inputOBJ.open(resolved_filename);
4010 if (!inputOBJ.is_open()) {
4015 std::string filebase =
getFilePath(resolved_filename);
4018 float boxmin = 100000;
4019 float boxmax = -100000;
4021 std::string current_material =
"none";
4022 std::string current_object =
"none";
4025 while (inputOBJ.good()) {
4032 getline(inputOBJ, line);
4035 }
else if (line ==
"mtllib") {
4036 getline(inputOBJ, line);
4038 materials = loadMTL(filebase, material_file, default_color);
4041 }
else if (line ==
"o") {
4042 getline(inputOBJ, line);
4046 }
else if (line ==
"v") {
4047 getline(inputOBJ, line);
4050 vertices.emplace_back(verts);
4051 objects.emplace_back(current_object);
4053 if (verts.
z < boxmin) {
4056 if (verts.
z > boxmax) {
4061 }
else if (line ==
"vt") {
4062 getline(inputOBJ, line);
4066 texture_uv.emplace_back(uv);
4069 }
else if (line ==
"usemtl") {
4070 getline(inputOBJ, line);
4074 }
else if (line ==
"f") {
4075 getline(inputOBJ, line);
4077 std::istringstream stream(line);
4078 std::string tmp, digitf, digitu;
4079 std::vector<int> f, u;
4080 while (stream.good()) {
4087 digitf.push_back(i);
4095 for (
int i = ic + 1; i < tmp.size(); i++) {
4096 if (isdigit(tmp[i])) {
4097 digitu.push_back(tmp[i]);
4103 if (!digitf.empty()) {
4106 helios_runtime_error(
"ERROR (Context::loadOBJ): Face index on line " + std::to_string(lineno) +
" must be a non-negative integer value.");
4109 if (face <= 0 || face > vertices.size()) {
4110 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()) +
4111 ". Check that vertex indices in face definitions reference existing vertices.");
4115 if (!digitu.empty()) {
4118 helios_runtime_error(
"ERROR (Context::loadOBJ): u,v index on line " + std::to_string(lineno) +
" must be a non-negative integer value.");
4121 if (uv <= 0 || uv > texture_uv.size()) {
4122 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()) +
4123 ". Check that texture coordinate indices in face definitions reference existing texture coordinates.");
4128 face_inds[current_material].push_back(f);
4129 texture_inds[current_material].push_back(u);
4133 getline(inputOBJ, line);
4138 if (scl.
x == 0 && scl.
y == 0 && scl.
z > 0) {
4139 if (boxmax - boxmin > 1e-6f) {
4140 scl =
make_vec3(scale.
z / (boxmax - boxmin), scale.
z / (boxmax - boxmin), scale.
z / (boxmax - boxmin));
4146 if (scl.
x == 0 && (scl.
y != 0 || scl.
z != 0)) {
4147 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for x-direction is zero. Setting scaling factor to 1" << std::endl;
4149 if (scl.
y == 0 && (scl.
x != 0 || scl.
z != 0)) {
4150 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for y-direction is zero. Setting scaling factor to 1" << std::endl;
4152 if (scl.
z == 0 && (scl.
x != 0 || scl.
y != 0)) {
4153 std::cout <<
"WARNING (Context::loadOBJ): Scaling factor given for z-direction is zero. Setting scaling factor to 1" << std::endl;
4168 struct TriangleData {
4169 vec3 vert0, vert1, vert2;
4170 std::string texture;
4174 bool textureColorIsOverridden;
4175 std::string materialname;
4182 std::map<std::string, std::string> mtl_to_context_material;
4183 for (
const auto &mat_entry : materials) {
4184 const std::string &matname = mat_entry.first;
4185 const OBJmaterial &mat = mat_entry.second;
4186 std::string context_matname = matname;
4190 context_matname = matname +
"_" + std::to_string(suffix++);
4195 if (!mat.texture.empty()) {
4199 mtl_to_context_material[matname] = context_matname;
4202 std::vector<TriangleData> triangleDataList;
4205 for (
auto iter = face_inds.begin(); iter != face_inds.end(); ++iter) {
4206 std::string materialname = iter->first;
4208 std::string texture;
4210 bool textureColorIsOverridden =
false;
4211 bool textureHasTransparency =
false;
4213 if (materials.find(materialname) != materials.end()) {
4214 const OBJmaterial &mat = materials.at(materialname);
4216 texture = mat.texture;
4218 textureColorIsOverridden = mat.textureColorIsOverridden;
4219 textureHasTransparency = mat.textureHasTransparency;
4222 const auto &material_faces = face_inds.at(materialname);
4223 const auto &material_texture_inds = texture_inds.count(materialname) ? texture_inds.at(materialname) : std::vector<std::vector<int>>();
4226 std::string exception_message;
4227 bool exception_occurred =
false;
4230#pragma omp parallel for schedule(dynamic)
4232 for (
int i = 0; i < static_cast<int>(material_faces.size()); i++) {
4234 for (
uint t = 2; t < material_faces[i].size(); t++) {
4235 vec3 v0 = vertices.at(material_faces[i][0] - 1);
4236 vec3 v1 = vertices.at(material_faces[i][t - 1] - 1);
4237 vec3 v2 = vertices.at(material_faces[i][t] - 1);
4239 if ((v0 - v1).magnitude() == 0 || (v0 - v2).magnitude() == 0 || (v1 - v2).magnitude() == 0) {
4243 if (strcmp(upaxis,
"YUP") == 0) {
4261 if (triangle_area > MIN_TRIANGLE_AREA_THRESHOLD) {
4262 TriangleData triangleData;
4263 triangleData.vert0 = vert0;
4264 triangleData.vert1 = vert1;
4265 triangleData.vert2 = vert2;
4266 triangleData.texture = texture;
4267 triangleData.color = color;
4268 triangleData.textureColorIsOverridden = textureColorIsOverridden;
4269 triangleData.materialname = mtl_to_context_material.count(materialname) ? mtl_to_context_material.at(materialname) : materialname;
4270 triangleData.object = objects.at(material_faces[i][0] - 1);
4274 triangleData.hasTexture = !texture.empty();
4278 if (triangleData.hasTexture && i < material_texture_inds.size() && !material_texture_inds[i].empty() && t < material_texture_inds[i].size()) {
4280 int iuv0 = material_texture_inds[i][0] - 1;
4281 int iuv1 = material_texture_inds[i][t - 1] - 1;
4282 int iuv2 = material_texture_inds[i][t] - 1;
4284 if (iuv0 >= 0 && iuv0 < texture_uv.size() && iuv1 >= 0 && iuv1 < texture_uv.size() && iuv2 >= 0 && iuv2 < texture_uv.size()) {
4285 triangleData.uv0 = texture_uv.at(iuv0);
4286 triangleData.uv1 = texture_uv.at(iuv1);
4287 triangleData.uv2 = texture_uv.at(iuv2);
4289 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) +
", " +
4290 std::to_string(iuv2 + 1) +
"] " +
"exceed available UV coordinates (1-" + std::to_string(texture_uv.size()) +
"). " +
4291 "Check that all face texture coordinate references in the OBJ file are valid.");
4293 }
else if (triangleData.hasTexture) {
4294 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 " +
4295 "or add texture coordinates (vt) and face texture indices (f v1/vt1 v2/vt2 v3/vt3) to the OBJ file.");
4302 triangleDataList.push_back(triangleData);
4306 }
catch (
const std::exception &e) {
4312 if (!exception_occurred) {
4313 exception_message = e.what();
4314 exception_occurred =
true;
4321 if (exception_occurred) {
4327 for (
const auto &triangleData: triangleDataList) {
4330 if (triangleData.hasTexture) {
4331 ID =
addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.texture.c_str(), triangleData.uv0, triangleData.uv1, triangleData.uv2);
4333 if (triangleData.textureColorIsOverridden) {
4338 ID =
addTriangle(triangleData.vert0, triangleData.vert1, triangleData.vert2, triangleData.color);
4343 if (!triangleData.materialname.empty() &&
doesMaterialExist(triangleData.materialname)) {
4353 std::cout <<
"done." << std::endl;
4359std::map<std::string, Context::OBJmaterial> Context::loadMTL(
const std::string &filebase,
const std::string &material_file,
const RGBcolor &default_color) {
4360 std::ifstream inputMTL;
4362 std::string file = material_file;
4366 std::filesystem::path resolved_path;
4368 if (std::filesystem::path(file).is_absolute()) {
4373 std::filesystem::path mtl_path = std::filesystem::path(filebase) / file;
4377 std::string resolved_file = resolved_path.string();
4378 inputMTL.open(resolved_file.c_str());
4380 if (!inputMTL.is_open()) {
4381 helios_runtime_error(
"ERROR (Context::loadMTL): Could not open material file " + resolved_file +
" after successful path resolution.");
4384 std::map<std::string, OBJmaterial> materials;
4390 while (inputMTL.good()) {
4391 if (strcmp(
"#", line.c_str()) == 0) {
4392 getline(inputMTL, line);
4394 }
else if (line ==
"newmtl") {
4395 getline(inputMTL, line);
4397 OBJmaterial mat(default_color,
"", 0);
4398 materials.emplace(material_name, mat);
4400 std::string map_Kd, map_d;
4402 while (line !=
"newmtl" && inputMTL.good()) {
4405 if (line ==
"newmtl") {
4407 }
else if (line ==
"map_a" || line ==
"map_Ka" || line ==
"Ks" || line ==
"Ka" || line ==
"map_Ks") {
4408 getline(inputMTL, line);
4409 }
else if (line ==
"map_Kd" || line ==
"map_d") {
4410 std::string maptype = line;
4411 getline(inputMTL, line);
4413 std::istringstream stream(line);
4415 while (stream.good()) {
4418 if (ext ==
".png" || ext ==
".PNG" || ext ==
".jpg" || ext ==
".JPG" || ext ==
".jpeg" || ext ==
".JPEG") {
4419 std::string texturefile = tmp;
4422 std::filesystem::path texture_path = texturefile;
4423 bool texture_exists =
false;
4426 if (std::filesystem::exists(texture_path)) {
4427 texture_exists =
true;
4430 texture_path = std::filesystem::path(filebase) / tmp;
4431 texturefile = texture_path.string();
4432 if (std::filesystem::exists(texture_path)) {
4433 texture_exists =
true;
4437 if (!texture_exists) {
4438 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 +
"). " +
4439 "Ensure texture file exists or remove texture reference from material.");
4442 if (maptype ==
"map_d") {
4443 map_d = texturefile;
4445 map_Kd = texturefile;
4449 }
else if (line ==
"Kd") {
4450 getline(inputMTL, line);
4453 materials.at(material_name).color =
make_RGBcolor(color.
r, color.
g, color.
b);
4455 getline(inputMTL, line);
4459 if (!map_Kd.empty()) {
4460 materials.at(material_name).texture = map_Kd;
4461 if (!map_d.empty() && map_d != map_Kd) {
4462 materials.at(material_name).textureHasTransparency =
true;
4464 }
else if (!map_d.empty()) {
4465 materials.at(material_name).texture = map_d;
4466 materials.at(material_name).textureColorIsOverridden =
true;
4469 getline(inputMTL, line);
4481void Context::writeOBJ(
const std::string &filename,
const std::vector<uint> &UUIDs,
bool write_normals,
bool silent)
const {
4482 writeOBJ(filename, UUIDs, {}, write_normals, silent);
4485void 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 {
4487 if (UUIDs.empty()) {
4488 std::cout <<
"WARNING (Context::writeOBJ): No primitives found to write - OBJ file " << filename <<
" will not be written." << std::endl;
4491 if (filename.empty()) {
4492 std::cout <<
"WARNING (Context::writeOBJ): Filename was empty - OBJ file " << filename <<
" will not be written." << std::endl;
4496 std::string objfilename = filename;
4497 std::string mtlfilename = filename;
4503 if (file_extension !=
".obj" && file_extension !=
".OBJ") {
4504 objfilename.append(
".obj");
4505 mtlfilename.append(
".mtl");
4507 if (!file_path.empty()) {
4508 std::filesystem::path mtl_path = std::filesystem::path(file_path) / (file_stem +
".mtl");
4509 mtlfilename = mtl_path.string();
4511 mtlfilename = file_stem +
".mtl";
4515 if (!file_path.empty() && !std::filesystem::exists(file_path)) {
4516 if (!std::filesystem::create_directory(file_path)) {
4517 std::cerr <<
"failed. Directory " << file_path <<
" does not exist and it could not be created - OBJ file will not be written." << std::endl;
4523 std::cout <<
"Writing OBJ file " << objfilename <<
"..." << std::flush;
4526 std::vector<OBJmaterial> materials;
4527 std::unordered_map<std::string, uint> material_cache;
4528 const size_t primitive_count = UUIDs.size();
4529 const size_t estimated_vertices = primitive_count * 4;
4531 std::vector<vec3> verts;
4532 verts.reserve(estimated_vertices);
4533 std::vector<vec3> normals;
4534 if (write_normals) {
4535 normals.reserve(primitive_count);
4537 std::vector<vec2> uv;
4538 uv.reserve(estimated_vertices);
4540 std::map<uint, std::vector<int3>> faces;
4541 std::map<uint, std::vector<int>> normal_inds;
4542 std::map<uint, std::vector<int3>> uv_inds;
4543 size_t vertex_count = 1;
4544 size_t normal_count = 0;
4545 size_t uv_count = 1;
4546 std::map<uint, std::vector<uint>> UUIDs_write;
4548 std::map<std::string, std::map<uint, std::vector<int3>>> object_faces;
4549 std::map<std::string, std::map<uint, std::vector<int>>> object_normal_inds;
4550 std::map<std::string, std::map<uint, std::vector<int3>>> object_uv_inds;
4551 std::vector<std::string> object_order;
4552 object_order.reserve(primitive_count / 10);
4553 bool object_groups_found =
false;
4555 for (
size_t p: UUIDs) {
4557 std::ostringstream err_stream;
4558 err_stream <<
"ERROR (Context::writeOBJ): Primitive with UUID " << p <<
" does not exist. "
4559 <<
"Ensure all UUIDs in the input vector correspond to valid primitives before calling writeOBJ.";
4563 const Primitive *prim_ptr = getPrimitivePointer_private(p);
4566 std::ostringstream err_stream;
4567 err_stream <<
"ERROR (Context::writeOBJ): Voxel primitives (UUID " << p <<
") cannot be written to OBJ format. "
4568 <<
"OBJ format only supports surface primitives (triangles, patches). "
4569 <<
"Filter out voxel primitives before calling writeOBJ.";
4573 std::vector<vec3> vertices = prim_ptr->getVertices();
4576 std::string texturefile = prim_ptr->getTextureFile();
4577 bool texture_color_overridden = prim_ptr->isTextureColorOverridden();
4579 std::string obj_label =
"default";
4582 object_groups_found =
true;
4584 if (object_faces.find(obj_label) == object_faces.end()) {
4585 object_faces[obj_label] = {};
4586 object_normal_inds[obj_label] = {};
4587 object_uv_inds[obj_label] = {};
4588 object_order.push_back(obj_label);
4591 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);
4594 auto material_iter = material_cache.find(material_key);
4596 if (material_iter != material_cache.end()) {
4598 material_ID = material_iter->second;
4601 OBJmaterial mat(C, texturefile, materials.size());
4602 materials.emplace_back(mat);
4603 material_ID = mat.materialID;
4606 materials.back().textureHasTransparency =
true;
4608 if (texture_color_overridden) {
4609 materials.back().textureColorIsOverridden =
true;
4612 material_cache[material_key] = material_ID;
4615 if (!primitive_dat_fields.empty()) {
4616 UUIDs_write[material_ID].push_back(p);
4619 if (write_normals) {
4621 normals.push_back(normal);
4626 int3 ftmp =
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2);
4627 faces[material_ID].push_back(ftmp);
4628 object_faces[obj_label][material_ID].push_back(ftmp);
4629 for (
int i = 0; i < 3; i++) {
4630 verts.push_back(vertices.at(i));
4634 if (write_normals) {
4635 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4636 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4639 std::vector<vec2> uv_v = getTrianglePointer_private(p)->getTextureUV();
4640 if (getTrianglePointer_private(p)->hasTexture()) {
4641 int3 tuv =
make_int3((
int) uv_count, (
int) uv_count + 1, (
int) uv_count + 2);
4642 uv_inds[material_ID].push_back(tuv);
4643 object_uv_inds[obj_label][material_ID].push_back(tuv);
4644 for (
int i = 0; i < 3; i++) {
4645 uv.push_back(uv_v.at(i));
4650 uv_inds[material_ID].push_back(tuv);
4651 object_uv_inds[obj_label][material_ID].push_back(tuv);
4654 int3 ftmp1 =
make_int3((
int) vertex_count, (
int) vertex_count + 1, (
int) vertex_count + 2);
4655 int3 ftmp2 =
make_int3((
int) vertex_count, (
int) vertex_count + 2, (
int) vertex_count + 3);
4656 faces[material_ID].push_back(ftmp1);
4657 faces[material_ID].push_back(ftmp2);
4658 object_faces[obj_label][material_ID].push_back(ftmp1);
4659 object_faces[obj_label][material_ID].push_back(ftmp2);
4660 for (
int i = 0; i < 4; i++) {
4661 verts.push_back(vertices.at(i));
4664 std::vector<vec2> uv_v;
4665 uv_v = getPatchPointer_private(p)->getTextureUV();
4667 if (write_normals) {
4668 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4669 normal_inds[material_ID].push_back(
static_cast<int>(normal_count));
4670 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4671 object_normal_inds[obj_label][material_ID].push_back(
static_cast<int>(normal_count));
4674 if (getPatchPointer_private(p)->hasTexture()) {
4675 int3 tuv1 =
make_int3((
int) uv_count, (
int) uv_count + 1, (
int) uv_count + 2);
4676 int3 tuv2 =
make_int3((
int) uv_count, (
int) uv_count + 2, (
int) uv_count + 3);
4677 uv_inds[material_ID].push_back(tuv1);
4678 uv_inds[material_ID].push_back(tuv2);
4679 object_uv_inds[obj_label][material_ID].push_back(tuv1);
4680 object_uv_inds[obj_label][material_ID].push_back(tuv2);
4688 for (
int i = 0; i < 4; i++) {
4689 uv.push_back(uv_v.at(i));
4695 uv_inds[material_ID].push_back(tuv);
4696 uv_inds[material_ID].push_back(tuv);
4697 object_uv_inds[obj_label][material_ID].push_back(tuv);
4698 object_uv_inds[obj_label][material_ID].push_back(tuv);
4704 assert(normal_inds.size() == faces.size());
4706 assert(uv_inds.size() == faces.size());
4707 for (
int i = 0; i < faces.size(); i++) {
4708 assert(uv_inds.at(i).size() == faces.at(i).size());
4712 std::filesystem::path output_path = std::filesystem::path(file_path);
4713 std::filesystem::path texture_dir = output_path.parent_path();
4716 if (texture_dir.empty()) {
4720 for (
auto &material: materials) {
4721 std::string texture = material.texture;
4722 if (!texture.empty()) {
4724 std::filesystem::path source_path = std::filesystem::absolute(texture, ec);
4728 source_path = std::filesystem::path(texture);
4731 if (!std::filesystem::exists(source_path)) {
4736 auto filename = source_path.filename();
4737 std::filesystem::path dest_path = texture_dir / filename;
4740 bool same_file =
false;
4742 same_file = std::filesystem::equivalent(source_path, dest_path, ec);
4750 material.texture = filename.string();
4756 std::filesystem::copy_file(source_path, dest_path, std::filesystem::copy_options::overwrite_existing, ec);
4758 material.texture = filename.string();
4767 std::ofstream objfstream;
4768 objfstream.open(objfilename);
4769 std::ofstream mtlfstream;
4770 mtlfstream.open(mtlfilename);
4772 objfstream <<
"# Helios auto-generated OBJ File" << std::endl;
4773 objfstream <<
"# baileylab.ucdavis.edu/software/helios" << std::endl;
4774 objfstream <<
"mtllib " <<
getFileName(mtlfilename) << std::endl;
4777 std::vector<std::string> vertex_chunks;
4778 const int num_threads = std::min(
static_cast<int>(verts.size() / 1000 + 1), std::max(1,
static_cast<int>(std::thread::hardware_concurrency())));
4779 vertex_chunks.resize(num_threads);
4782#pragma omp parallel num_threads(num_threads)
4787 tid = omp_get_thread_num();
4789 std::ostringstream vertex_stream;
4790 vertex_stream.precision(8);
4792 const size_t chunk_size = (verts.size() + num_threads - 1) / num_threads;
4793 const size_t start_idx = tid * chunk_size;
4794 const size_t end_idx = std::min(start_idx + chunk_size, verts.size());
4796 for (
size_t i = start_idx; i < end_idx; i++) {
4797 vertex_stream <<
"v " << verts[i].x <<
" " << verts[i].y <<
" " << verts[i].z <<
"\n";
4800 vertex_chunks[tid] = vertex_stream.str();
4803 for (
const auto &chunk: vertex_chunks) {
4804 objfstream << chunk;
4807 if (write_normals) {
4808 std::vector<std::string> normal_chunks;
4809 normal_chunks.resize(num_threads);
4812#pragma omp parallel num_threads(num_threads)
4817 tid = omp_get_thread_num();
4819 std::ostringstream normal_stream;
4820 normal_stream.precision(8);
4822 const size_t chunk_size = (normals.size() + num_threads - 1) / num_threads;
4823 const size_t start_idx = tid * chunk_size;
4824 const size_t end_idx = std::min(start_idx + chunk_size, normals.size());
4826 const float epsilon = 1e-7;
4827 for (
size_t i = start_idx; i < end_idx; i++) {
4828 vec3 n = normals[i];
4829 if (std::abs(n.
x) < epsilon)
4831 if (std::abs(n.
y) < epsilon)
4833 if (std::abs(n.
z) < epsilon)
4835 normal_stream <<
"vn " << n.
x <<
" " << n.
y <<
" " << n.
z <<
"\n";
4838 normal_chunks[tid] = normal_stream.str();
4841 for (
const auto &chunk: normal_chunks) {
4842 objfstream << chunk;
4847 std::vector<std::string> uv_chunks;
4848 uv_chunks.resize(num_threads);
4851#pragma omp parallel num_threads(num_threads)
4856 tid = omp_get_thread_num();
4858 std::ostringstream uv_stream;
4859 uv_stream.precision(8);
4861 const size_t chunk_size = (uv.size() + num_threads - 1) / num_threads;
4862 const size_t start_idx = tid * chunk_size;
4863 const size_t end_idx = std::min(start_idx + chunk_size, uv.size());
4865 for (
size_t i = start_idx; i < end_idx; i++) {
4866 uv_stream <<
"vt " << uv[i].x <<
" " << uv[i].y <<
"\n";
4869 uv_chunks[tid] = uv_stream.str();
4872 for (
const auto &chunk: uv_chunks) {
4873 objfstream << chunk;
4879 if (object_groups_found) {
4882 for (
const auto &obj_label: object_order) {
4883 objfstream <<
"o " << obj_label <<
"\n";
4885 for (
int mat = 0; mat < materials.size(); mat++) {
4886 auto fit = object_faces[obj_label].find(mat);
4887 if (fit == object_faces[obj_label].end())
4890 objfstream <<
"usemtl material" << mat <<
"\n";
4892 const auto ¤t_faces = fit->second;
4893 if (current_faces.size() > 100) {
4895 std::vector<std::string> face_chunks;
4896 face_chunks.resize(num_threads);
4899#pragma omp parallel num_threads(num_threads)
4904 tid = omp_get_thread_num();
4906 std::ostringstream face_stream;
4908 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4909 const size_t start_idx = tid * chunk_size;
4910 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4912 for (
size_t f = start_idx; f < end_idx; f++) {
4914 if (write_normals) {
4915 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 <<
"//"
4916 << object_normal_inds[obj_label][mat][f] <<
"\n";
4918 face_stream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z <<
"\n";
4920 }
else if (object_uv_inds[obj_label][mat][f].x < 0) {
4921 face_stream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1\n";
4923 if (write_normals) {
4924 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
4925 <<
"/" << 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";
4927 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 <<
"/"
4928 << object_uv_inds[obj_label][mat][f].z <<
"\n";
4933 face_chunks[tid] = face_stream.str();
4937 for (
const auto &chunk: face_chunks) {
4938 objfstream << chunk;
4942 for (
size_t f = 0; f < current_faces.size(); ++f) {
4944 if (write_normals) {
4945 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 <<
"//"
4946 << object_normal_inds[obj_label][mat][f] << std::endl;
4948 objfstream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z << std::endl;
4950 }
else if (object_uv_inds[obj_label][mat][f].x < 0) {
4951 objfstream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1" << std::endl;
4953 if (write_normals) {
4954 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 <<
"/"
4955 << 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;
4957 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 <<
"/"
4958 << object_uv_inds[obj_label][mat][f].z << std::endl;
4967 for (
int mat = 0; mat < materials.size(); mat++) {
4968 assert(materials.at(mat).materialID == mat);
4969 objfstream <<
"usemtl material" << mat <<
"\n";
4971 const auto ¤t_faces = faces.at(mat);
4972 if (current_faces.size() > 100) {
4974 std::vector<std::string> face_chunks;
4975 face_chunks.resize(num_threads);
4978#pragma omp parallel num_threads(num_threads)
4983 tid = omp_get_thread_num();
4985 std::ostringstream face_stream;
4987 const size_t chunk_size = (current_faces.size() + num_threads - 1) / num_threads;
4988 const size_t start_idx = tid * chunk_size;
4989 const size_t end_idx = std::min(start_idx + chunk_size, current_faces.size());
4991 for (
size_t f = start_idx; f < end_idx; f++) {
4993 if (write_normals) {
4994 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";
4996 face_stream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z <<
"\n";
4998 }
else if (uv_inds.at(mat)[f].x < 0) {
4999 face_stream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1\n";
5001 if (write_normals) {
5002 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] <<
" "
5003 << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z <<
"/" << normal_inds.at(mat)[f] <<
"\n";
5005 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";
5010 face_chunks[tid] = face_stream.str();
5014 for (
const auto &chunk: face_chunks) {
5015 objfstream << chunk;
5019 for (
int f = 0; f < current_faces.size(); f++) {
5021 if (write_normals) {
5022 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;
5024 objfstream <<
"f " << current_faces[f].x <<
" " << current_faces[f].y <<
" " << current_faces[f].z << std::endl;
5026 }
else if (uv_inds.at(mat)[f].x < 0) {
5027 objfstream <<
"f " << current_faces[f].x <<
"/1 " << current_faces[f].y <<
"/1 " << current_faces[f].z <<
"/1" << std::endl;
5029 if (write_normals) {
5030 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] <<
" "
5031 << current_faces[f].z <<
"/" << uv_inds.at(mat)[f].z <<
"/" << normal_inds.at(mat)[f] << std::endl;
5033 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;
5041 for (
int mat = 0; mat < materials.size(); mat++) {
5042 if (materials.at(mat).texture.empty()) {
5043 RGBcolor current_color = materials.at(mat).color;
5044 mtlfstream <<
"newmtl material" << mat << std::endl;
5045 mtlfstream <<
"Ka " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5046 mtlfstream <<
"Kd " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5047 mtlfstream <<
"Ks 0.0 0.0 0.0" << std::endl;
5048 mtlfstream <<
"illum 2 " << std::endl;
5050 std::string current_texture = materials.at(mat).texture;
5051 mtlfstream <<
"newmtl material" << mat << std::endl;
5052 if (materials.at(mat).textureColorIsOverridden) {
5053 RGBcolor current_color = materials.at(mat).color;
5054 mtlfstream <<
"Ka " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5055 mtlfstream <<
"Kd " << current_color.
r <<
" " << current_color.
g <<
" " << current_color.
b << std::endl;
5057 mtlfstream <<
"map_Kd " << current_texture << std::endl;
5059 if (materials.at(mat).textureHasTransparency) {
5060 mtlfstream <<
"map_d " << current_texture << std::endl;
5062 mtlfstream <<
"Ks 0.0 0.0 0.0" << std::endl;
5063 mtlfstream <<
"illum 2 " << std::endl;
5070 if (!primitive_dat_fields.empty()) {
5071 bool uuidexistswarning =
false;
5072 bool dataexistswarning =
false;
5073 bool datatypewarning =
false;
5075 for (
const std::string &label: primitive_dat_fields) {
5076 std::filesystem::path dat_path = std::filesystem::path(file_path) / (file_stem +
"_" + std::string(label) +
".dat");
5077 std::string datfilename = dat_path.string();
5078 std::ofstream datout(datfilename);
5080 for (
int mat = 0; mat < materials.size(); mat++) {
5081 for (
uint UUID: UUIDs_write.at(mat)) {
5083 uuidexistswarning =
true;
5094 dataexistswarning =
true;
5095 for (
int i = 0; i < Nprims; i++) {
5096 datout << 0 << std::endl;
5105 for (
int i = 0; i < Nprims; i++) {
5106 datout << data << std::endl;
5111 for (
int i = 0; i < Nprims; i++) {
5112 datout << data << std::endl;
5117 for (
int i = 0; i < Nprims; i++) {
5118 datout << data << std::endl;
5123 for (
int i = 0; i < Nprims; i++) {
5124 datout << data << std::endl;
5129 for (
int i = 0; i < Nprims; i++) {
5130 datout << data << std::endl;
5133 datatypewarning =
true;
5134 for (
int i = 0; i < Nprims; i++) {
5135 datout << 0 << std::endl;
5144 if (uuidexistswarning) {
5145 helios_runtime_error(
"Context::writeOBJ: One or more UUIDs do not exist in the Context. Cannot write OBJ file with invalid primitives.");
5147 if (dataexistswarning) {
5148 helios_runtime_error(
"Context::writeOBJ: Primitive data requested did not exist for one or more primitives. Cannot write incomplete data to OBJ file.");
5150 if (datatypewarning) {
5151 helios_runtime_error(
"Context::writeOBJ: Only scalar primitive data types (uint, int, float, double, and string) are supported for primitive data export.");
5160void Context::writePrimitiveData(
const std::string &filename,
const std::vector<std::string> &column_format,
const std::vector<uint> &UUIDs,
bool print_header)
const {
5161 std::ofstream file(filename);
5164 for (
const auto &label: column_format) {
5165 file << label <<
" ";
5167 file.seekp(-1, std::ios_base::end);
5171 bool uuidexistswarning =
false;
5172 bool dataexistswarning =
false;
5173 bool datatypewarning =
false;
5175 for (
uint UUID: UUIDs) {
5177 uuidexistswarning =
true;
5180 for (
const auto &label: column_format) {
5181 if (label ==
"UUID") {
5182 file << UUID <<
" ";
5186 dataexistswarning =
true;
5194 file << data <<
" ";
5198 file << data <<
" ";
5202 file << data <<
" ";
5206 file << data <<
" ";
5210 file << data <<
" ";
5212 datatypewarning =
true;
5216 file.seekp(-1, std::ios_base::end);
5220 if (uuidexistswarning) {
5221 std::cerr <<
"WARNING (Context::writePrimitiveData): Vector of UUIDs passed to writePrimitiveData() function contained UUIDs that do not exist, which were skipped." << std::endl;
5223 if (dataexistswarning) {
5224 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;
5226 if (datatypewarning) {
5227 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;
5236 Date parseDateString(
const std::string &datestr,
const std::string &date_string_format,
size_t row,
const std::string &data_file) {
5239 if (datestr.find(
'-') == std::string::npos && datestr.find(
'/') == std::string::npos) {
5240 if (datestr.size() == 8) {
5242 int year, month, day;
5243 if (date_string_format ==
"YYYYMMDD" || date_string_format ==
"YYYY-MM-DD") {
5244 year = std::stoi(datestr.substr(0, 4));
5245 month = std::stoi(datestr.substr(4, 2));
5246 day = std::stoi(datestr.substr(6, 2));
5247 }
else if (date_string_format ==
"DDMMYYYY" || date_string_format ==
"DD-MM-YYYY" || date_string_format ==
"DD/MM/YYYY") {
5248 day = std::stoi(datestr.substr(0, 2));
5249 month = std::stoi(datestr.substr(2, 2));
5250 year = std::stoi(datestr.substr(4, 4));
5251 }
else if (date_string_format ==
"MMDDYYYY" || date_string_format ==
"MM-DD-YYYY" || date_string_format ==
"MM/DD/YYYY") {
5252 month = std::stoi(datestr.substr(0, 2));
5253 day = std::stoi(datestr.substr(2, 2));
5254 year = std::stoi(datestr.substr(4, 4));
5255 }
else if (date_string_format ==
"YYYYDDMM") {
5256 year = std::stoi(datestr.substr(0, 4));
5257 day = std::stoi(datestr.substr(4, 2));
5258 month = std::stoi(datestr.substr(6, 2));
5260 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date string format '" + date_string_format +
"' for compact date on line " + std::to_string(row) +
" of file " + data_file +
".");
5262 if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31) {
5263 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse compact date string on line " + std::to_string(row) +
" of file " + data_file +
".");
5267 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5268 ". Expected a delimited date (e.g., YYYY-MM-DD) or an 8-digit compact date (e.g., 20260203).");
5273 if (thisdatestr.size() != 3) {
5276 if (thisdatestr.size() != 3) {
5277 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5278 ". It should be in the format YYYY-MM-DD, delimited by either '-' or '/'.");
5281 std::vector<int> thisdate(3);
5282 for (
int i = 0; i < 3; i++) {
5283 if (!
parse_int(thisdatestr.at(i), thisdate.at(i))) {
5284 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
5285 ". It should be in the format YYYY-MM-DD, delimited by either '-' or '/'.");
5289 int year, month, day;
5290 if (date_string_format ==
"YYYYMMDD" || date_string_format ==
"YYYY-MM-DD") {
5291 year = thisdate.at(0);
5292 month = thisdate.at(1);
5293 day = thisdate.at(2);
5294 }
else if (date_string_format ==
"YYYYDDMM") {
5295 year = thisdate.at(0);
5296 month = thisdate.at(2);
5297 day = thisdate.at(1);
5298 }
else if (date_string_format ==
"DDMMYYYY" || date_string_format ==
"DD-MM-YYYY" || date_string_format ==
"DD/MM/YYYY") {
5299 year = thisdate.at(2);
5300 month = thisdate.at(1);
5301 day = thisdate.at(0);
5302 }
else if (date_string_format ==
"MMDDYYYY" || date_string_format ==
"MM-DD-YYYY" || date_string_format ==
"MM/DD/YYYY") {
5303 year = thisdate.at(2);
5304 month = thisdate.at(0);
5305 day = thisdate.at(1);
5307 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date string format in file " + data_file +
": " + date_string_format +
5308 ". Must be one of YYYYMMDD, YYYYDDMM, DDMMYYYY, MMDDYYYY (or with - or / delimiters, e.g. YYYY-MM-DD, DD/MM/YYYY).");
5311 if (year < 1000 || month < 1 || month > 12 || day < 1 || day > 31) {
5312 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date string on line " + std::to_string(row) +
" of file " + data_file +
".");
5320 Time parseTimeString(
const std::string ×tr,
size_t row,
const std::string &data_file) {
5324 int hour = 0, minute = 0, second = 0;
5326 if (parts.size() == 1) {
5329 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5334 hour = hr_min / 100;
5335 minute = hr_min - hour * 100;
5337 }
else if (parts.size() == 2) {
5339 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5341 }
else if (parts.size() == 3) {
5343 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5346 std::string sec_str = parts.at(2);
5347 size_t dot_pos = sec_str.find(
'.');
5348 if (dot_pos != std::string::npos) {
5349 sec_str = sec_str.substr(0, dot_pos);
5352 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5355 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse time string '" + timestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5371 void parseISO8601(
const std::string &datetimestr,
Date &date,
Time &time,
float &utc_offset,
size_t row,
const std::string &data_file) {
5374 size_t t_pos = datetimestr.find(
'T');
5375 if (t_pos == std::string::npos) {
5376 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): ISO-8601 datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" does not contain 'T' separator.");
5380 std::string date_part = datetimestr.substr(0, t_pos);
5382 if (date_parts.size() != 3) {
5383 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date portion of ISO-8601 string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5385 int year, month, day;
5387 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse date portion of ISO-8601 string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5392 std::string time_tz = datetimestr.substr(t_pos + 1);
5395 std::string time_part;
5396 if (time_tz.back() ==
'Z' || time_tz.back() ==
'z') {
5397 time_part = time_tz.substr(0, time_tz.size() - 1);
5402 size_t tz_pos = std::string::npos;
5403 for (
size_t i = 1; i < time_tz.size(); i++) {
5404 if (time_tz[i] ==
'+' || time_tz[i] ==
'-') {
5410 if (tz_pos != std::string::npos) {
5411 time_part = time_tz.substr(0, tz_pos);
5412 std::string tz_str = time_tz.substr(tz_pos);
5413 char tz_sign = tz_str[0];
5414 std::string tz_num = tz_str.substr(1);
5416 int tz_hours = 0, tz_minutes = 0;
5417 if (!tz_parts.empty())
parse_int(tz_parts.at(0), tz_hours);
5418 if (tz_parts.size() > 1)
parse_int(tz_parts.at(1), tz_minutes);
5419 float iso_offset_hours =
static_cast<float>(tz_hours) +
static_cast<float>(tz_minutes) / 60.0f;
5420 if (tz_sign ==
'-') iso_offset_hours = -iso_offset_hours;
5423 utc_offset = -iso_offset_hours;
5425 time_part = time_tz;
5430 size_t dot_pos = time_part.find(
'.');
5431 if (dot_pos != std::string::npos) {
5432 time_part = time_part.substr(0, dot_pos);
5436 time = parseTimeString(time_part, row, data_file);
5440 void parseDatetimeString(
const std::string &datetimestr,
const std::string &date_string_format,
5441 Date &date,
Time &time,
float &utc_offset,
size_t row,
const std::string &data_file) {
5444 if (date_string_format ==
"ISO8601") {
5445 parseISO8601(datetimestr, date, time, utc_offset, row, data_file);
5449 if (date_string_format ==
"YYYYMMDDHH") {
5450 if (datetimestr.size() < 10) {
5451 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): YYYYMMDDHH datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" is too short.");
5453 int year = std::stoi(datetimestr.substr(0, 4));
5454 int month = std::stoi(datetimestr.substr(4, 2));
5455 int day = std::stoi(datetimestr.substr(6, 2));
5456 int hour = std::stoi(datetimestr.substr(8, 2));
5462 if (date_string_format ==
"YYYYMMDDHHMM") {
5463 if (datetimestr.size() < 12) {
5464 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): YYYYMMDDHHMM datetime string '" + datetimestr +
"' on line " + std::to_string(row) +
" of file " + data_file +
" is too short.");
5466 int year = std::stoi(datetimestr.substr(0, 4));
5467 int month = std::stoi(datetimestr.substr(4, 2));
5468 int day = std::stoi(datetimestr.substr(6, 2));
5469 int hour = std::stoi(datetimestr.substr(8, 2));
5470 int minute = std::stoi(datetimestr.substr(10, 2));
5478 size_t space_pos = datetimestr.find(
' ');
5479 if (space_pos != std::string::npos) {
5480 std::string date_part = datetimestr.substr(0, space_pos);
5481 std::string time_part = datetimestr.substr(space_pos + 1);
5484 std::string date_format;
5485 size_t fmt_space = date_string_format.find(
' ');
5486 if (fmt_space != std::string::npos) {
5487 date_format = date_string_format.substr(0, fmt_space);
5489 date_format = date_string_format;
5494 date = parseDateString(date_part, date_format, row, data_file);
5495 time = parseTimeString(time_part, row, data_file);
5499 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse datetime string '" + datetimestr +
"' with format '" + date_string_format +
"' on line " + std::to_string(row) +
" of file " + data_file +
".");
5503 bool datetimeFormatHasSpace(
const std::string &format) {
5504 return format.find(
' ') != std::string::npos;
5509void 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) {
5512 std::string resolved_filename = resolved_path.string();
5514 std::ifstream datafile(resolved_filename);
5516 if (!datafile.is_open()) {
5517 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Weather data file '" + data_file +
"' does not exist.");
5522 int datestrcol = -1;
5523 int datetimecol = -1;
5528 std::map<std::string, int> datacols;
5530 size_t Ncolumns = 0;
5532 size_t row = headerlines;
5534 std::vector<std::string> column_labels = col_labels;
5535 std::string delimiter = a_delimeter;
5536 std::string date_string_format = a_date_string_format;
5539 if (col_labels.size() == 1 && (col_labels.front() ==
"CIMIS" || col_labels.front() ==
"cimis")) {
5541 "",
"",
"",
"date",
"hour",
"DOY",
"ETo",
"",
"precipitation",
"",
"net_radiation",
"",
"vapor_pressure",
"",
"air_temperature",
"",
"air_humidity",
"",
"dew_point",
"",
"wind_speed",
"",
"wind_direction",
"",
"soil_temperature",
""};
5544 date_string_format =
"MMDDYYYY";
5548 if (!column_labels.empty()) {
5550 for (
auto &label: column_labels) {
5551 if (label ==
"year" || label ==
"Year") {
5553 }
else if (label ==
"DOY" || label ==
"Jul") {
5555 }
else if (label ==
"date" || label ==
"Date") {
5557 }
else if (label ==
"datetime" || label ==
"Datetime" || label ==
"DateTime") {
5559 }
else if (label ==
"hour" || label ==
"Hour") {
5561 }
else if (label ==
"minute" || label ==
"Minute") {
5563 }
else if (label ==
"second" || label ==
"Second") {
5565 }
else if (label ==
"time" || label ==
"Time") {
5567 }
else if (!label.empty()) {
5568 if (datacols.find(label) == datacols.end()) {
5569 datacols[label] = col;
5571 datacols[label +
"_dup"] = col;
5578 Ncolumns = column_labels.size();
5582 if (headerlines == 0) {
5583 std::cerr <<
"WARNING (Context::loadTabularTimeseriesData): "
5585 " 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."
5591 if (std::getline(datafile, line)) {
5594 if (line_parsed.empty()) {
5595 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but it did not contain the specified delimiter.");
5598 Ncolumns = line_parsed.size();
5600 for (
int col = 0; col < Ncolumns; col++) {
5601 const std::string &label = line_parsed.at(col);
5603 if (label ==
"year" || label ==
"Year") {
5605 }
else if (label ==
"DOY" || label ==
"Jul") {
5607 }
else if (label ==
"date" || label ==
"Date") {
5609 }
else if (label ==
"datetime" || label ==
"Datetime" || label ==
"DateTime") {
5611 }
else if (label ==
"hour" || label ==
"Hour") {
5613 }
else if (label ==
"minute" || label ==
"Minute") {
5615 }
else if (label ==
"second" || label ==
"Second") {
5617 }
else if (label ==
"time" || label ==
"Time") {
5619 }
else if (!label.empty()) {
5620 if (datacols.find(label) == datacols.end()) {
5621 datacols[label] = col;
5623 datacols[label +
"_dup"] = col;
5630 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but read failed.");
5633 if (yearcol == -1 && DOYcol == -1 && datestrcol == -1 && datetimecol == -1) {
5634 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Attempted to parse first line of file for column labels, but could not find valid label information.");
5639 bool has_date = (datestrcol >= 0) || (yearcol >= 0 && DOYcol >= 0);
5640 bool has_time = (hourcol >= 0) || (timecol >= 0);
5641 bool has_datetime = (datetimecol >= 0);
5643 if (has_datetime && datestrcol >= 0) {
5644 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'date' columns. Use 'datetime' for combined date+time, or 'date' + 'hour'/'time' for separate columns.");
5646 if (has_datetime && hourcol >= 0) {
5647 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'hour' columns. Use 'datetime' for combined date+time, or 'date' + 'hour' for separate columns.");
5649 if (has_datetime && timecol >= 0) {
5650 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Cannot specify both 'datetime' and 'time' columns. The 'datetime' column already includes time information.");
5652 if (!has_datetime && !has_date) {
5653 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): The date must be specified by a column labeled 'datetime', 'date', or by two columns labeled 'year' and 'DOY'.");
5655 if (!has_datetime && !has_time) {
5656 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): The time must be specified by a column labeled 'datetime', 'hour', or 'time'.");
5658 if (datacols.empty()) {
5659 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): No columns were found containing data variables (e.g., temperature, humidity, wind speed).");
5663 bool datetime_format_has_space = has_datetime && datetimeFormatHasSpace(date_string_format);
5669 for (
int i = 0; i < headerlines; i++) {
5670 std::getline(datafile, line);
5673 bool utc_offset_set =
false;
5677 while (std::getline(datafile, line)) {
5690 if (datetime_format_has_space && datetimecol >= 0 && line_separated.size() == Ncolumns + 1 && datetimecol + 1 <
static_cast<int>(line_separated.size())) {
5691 line_separated[datetimecol] = line_separated[datetimecol] +
" " + line_separated[datetimecol + 1];
5692 line_separated.erase(line_separated.begin() + datetimecol + 1);
5695 if (line_separated.size() != Ncolumns) {
5696 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));
5701 float parsed_utc_offset = NAN;
5703 if (datetimecol >= 0) {
5705 parseDatetimeString(line_separated.at(datetimecol), date_string_format,
5706 date, time, parsed_utc_offset, row, data_file);
5709 if (yearcol >= 0 && DOYcol >= 0) {
5711 parse_int(line_separated.at(DOYcol), DOY);
5712 if (DOY < 1 || DOY > 366) {
5713 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid date specified on line " + std::to_string(row) +
".");
5716 parse_int(line_separated.at(yearcol), year);
5718 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Invalid year specified on line " + std::to_string(row) +
".");
5721 }
else if (datestrcol >= 0) {
5722 date = parseDateString(line_separated.at(datestrcol), date_string_format, row, data_file);
5726 time = parseTimeString(line_separated.at(timecol), row, data_file);
5727 }
else if (hourcol >= 0) {
5732 if (!
parse_int(line_separated.at(hourcol), hour)) {
5733 helios_runtime_error(
"ERROR (Context::loadTabularTimeseriesData): Could not parse hour string on line " + std::to_string(row) +
" of file " + data_file +
".");
5735 if (hour > 24 && minutecol < 0 && secondcol < 0) {
5737 hour = hr_min / 100;
5738 minute = hr_min - hour * 100;
5742 date.incrementDay();
5744 if (minutecol >= 0) {
5745 if (!
parse_int(line_separated.at(minutecol), minute)) {
5747 csv_warnings.
addWarning(
"parse_minute_failed",
"Could not parse minute string on line " + std::to_string(row) +
" of file " + data_file +
". Setting minute equal to 0.");
5750 if (secondcol >= 0) {
5751 if (!
parse_int(line_separated.at(secondcol), second)) {
5753 csv_warnings.
addWarning(
"parse_second_failed",
"Could not parse second string on line " + std::to_string(row) +
" of file " + data_file +
". Setting second equal to 0.");
5761 if (time.hour == 24) {
5762 time =
make_Time(0, time.minute, time.second);
5763 date.incrementDay();
5767 if (!std::isnan(parsed_utc_offset)) {
5768 if (!utc_offset_set) {
5772 utc_offset_set =
true;
5777 for (
auto &dat: datacols) {
5778 std::string label = dat.first;
5779 int col = dat.second;
5782 if (!
parse_float(line_separated.at(col), dataval)) {
5783 csv_warnings.
addWarning(
"parse_data_value_failed",
"Failed to parse data value as float on line " + std::to_string(row) +
", column " + std::to_string(col + 1) +
" of file " + data_file +
". Skipping this value...");
5787 if (label ==
"air_humidity" && col_labels.size() == 1 && (col_labels.front() ==
"CIMIS" || col_labels.front() ==
"cimis")) {
5788 dataval = dataval / 100.f;
5795 csv_warnings.
report(std::cerr);