1.3.77
 
Loading...
Searching...
No Matches
global.h
Go to the documentation of this file.
1
16#ifndef HELIOS_GLOBAL
17#define HELIOS_GLOBAL
18
20#ifndef M_PI
21#define M_PI 3.14159265358979323846
22#endif
23
25constexpr float PI_F = 3.14159265358979323846f;
26
27#include <algorithm>
28#include <array>
29#include <cassert>
30#include <chrono>
31#include <cmath>
32#include <cstdio>
33#include <cstdlib>
34#include <cstring>
35#include <ctime>
36#include <exception>
37#include <filesystem>
38#include <fstream>
39#include <functional>
40#include <iomanip>
41#include <iostream>
42#include <map>
43#include <memory>
44#include <random>
45#include <set>
46#include <sstream>
47#include <stdexcept>
48#include <thread>
49#include <type_traits>
50#include <unordered_map>
51#include <vector>
52
53#ifdef USE_OPENMP
54#include <omp.h>
55#endif
56
58typedef unsigned int uint;
59
61template<typename To, typename From>
62constexpr To scast(From &&v) noexcept {
63 return static_cast<To>(std::forward<From>(v));
64}
65
66
67#include "helios_vector_types.h"
68#include "image_metadata.h"
69
70// pugi XML parser
71#include "pugixml.hpp"
72
73// Standard library for file path resolution
74#include <filesystem>
75
76// Thread synchronization
77#include <mutex>
78
79// *** Groups *** //
80
82
87
92
96namespace helios {
97
99
102 void helios_runtime_error(const std::string &error_message);
103
104 //--------------------- HELPER FUNCTIONS -----------------------------------//
105
107
113 void makeRotationMatrix(float rotation, const char *axis, float (&transform)[16]);
114
116
122 void makeRotationMatrix(float rotation, const helios::vec3 &axis, float (&transform)[16]);
123
125
132 void makeRotationMatrix(float rotation, const helios::vec3 &origin, const helios::vec3 &axis, float (&transform)[16]);
133
135
140 void makeTranslationMatrix(const helios::vec3 &translation, float (&transform)[16]);
141
143
148 void makeScaleMatrix(const helios::vec3 &scale, float (&transform)[16]);
149
151
157 void makeScaleMatrix(const helios::vec3 &scale, const helios::vec3 &point, float (&transform)[16]);
158
160
170 void matmult(const float ML[16], const float MR[16], float (&T)[16]);
171
173
178 void vecmult(const float M[16], const float v[3], float (&result)[3]);
179
181
186 void vecmult(const float M[16], const helios::vec3 &v3, helios::vec3 &result);
187
189
192 void makeIdentityMatrix(float (&T)[16]);
193
195
201 [[nodiscard]] float deg2rad(float deg);
202
204
210 [[nodiscard]] float rad2deg(float rad);
211
213
217 [[nodiscard]] float atan2_2pi(float y, float x);
218
220
226 [[nodiscard]] SphericalCoord cart2sphere(const vec3 &Cartesian);
227
229
235 [[nodiscard]] vec3 sphere2cart(const SphericalCoord &Spherical);
236
238
241 [[nodiscard]] vec2 string2vec2(const char *str);
242
244
247 [[nodiscard]] vec3 string2vec3(const char *str);
248
250
253 [[nodiscard]] vec4 string2vec4(const char *str);
254
256
259 [[nodiscard]] int2 string2int2(const char *str);
260
262
265 [[nodiscard]] int3 string2int3(const char *str);
266
268
271 [[nodiscard]] int4 string2int4(const char *str);
272
274
279 bool parse_float(const std::string &input_string, float &converted_float);
280
282
287 bool parse_double(const std::string &input_string, double &converted_double);
288
290
295 bool parse_int(const std::string &input_string, int &converted_int);
296
298
303 bool parse_int2(const std::string &input_string, int2 &converted_int2);
304
306
311 bool parse_int3(const std::string &input_string, int3 &converted_int3);
312
314
319 bool parse_uint(const std::string &input_string, uint &converted_uint);
320
322
327 bool parse_vec2(const std::string &input_string, vec2 &converted_vec2);
328
330
335 bool parse_vec3(const std::string &input_string, vec3 &converted_vec3);
336
338
343 bool parse_RGBcolor(const std::string &input_string, RGBcolor &converted_rgb);
344
346
352 bool open_xml_file(const std::string &xml_file, pugi::xml_document &xmldoc, std::string &error_string);
353
355
361 [[nodiscard]] int parse_xml_tag_int(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
362
364
370 [[nodiscard]] float parse_xml_tag_float(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
371
373
379 [[nodiscard]] vec2 parse_xml_tag_vec2(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
380
382
388 [[nodiscard]] vec3 parse_xml_tag_vec3(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
389
391
397 [[nodiscard]] std::string parse_xml_tag_string(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
398
400
403 [[nodiscard]] RGBAcolor string2RGBcolor(const char *str);
404
406
409 [[nodiscard]] std::string deblank(const char *input);
410
412
415 [[nodiscard]] std::string deblank(const std::string &input);
416
418
421 [[nodiscard]] std::string trim_whitespace(const std::string &input);
422
424
430 [[nodiscard]] std::vector<std::string> separate_string_by_delimiter(const std::string &inputstring, const std::string &delimiter);
431
433
439 template<typename anytype>
440 [[nodiscard]] anytype clamp(anytype value, anytype min, anytype max) {
441 static_assert(std::is_same_v<anytype, int> || std::is_same_v<anytype, uint> || std::is_same_v<anytype, float> || std::is_same_v<anytype, double> || std::is_same_v<anytype, char> || std::is_same_v<anytype, unsigned char>,
442 "helios::clamp() was called with an unsupported type.");
443 if (value < min) {
444 value = min;
445 } else if (value > max) {
446 value = max;
447 }
448 return value;
449 }
450
452
456 [[nodiscard]] float sum(const std::vector<float> &vect);
457
459
463 [[nodiscard]] float mean(const std::vector<float> &vect);
464
466
470 [[nodiscard]] float min(const std::vector<float> &vect);
471
473
477 [[nodiscard]] int min(const std::vector<int> &vect);
478
480
484 [[nodiscard]] vec3 min(const std::vector<vec3> &vect);
485
487
491 [[nodiscard]] float max(const std::vector<float> &vect);
492
494
498 [[nodiscard]] int max(const std::vector<int> &vect);
499
501
505 [[nodiscard]] vec3 max(const std::vector<vec3> &vect);
506
508
512 [[nodiscard]] float stdev(const std::vector<float> &vect);
513
515
519 [[nodiscard]] float median(std::vector<float> vect);
520
522
528 template<typename anytype>
529 typename std::enable_if<std::is_default_constructible<anytype>::value>::type resize_vector(std::vector<std::vector<anytype>> &vec, size_t Nx, size_t Ny) {
530 vec.assign(Ny, std::vector<anytype>(Nx));
531 }
532
534
541 template<typename anytype>
542 typename std::enable_if<std::is_default_constructible<anytype>::value>::type resize_vector(std::vector<std::vector<std::vector<anytype>>> &vec, size_t Nx, size_t Ny, size_t Nz) {
543 vec.assign(Nz, std::vector<std::vector<anytype>>(Ny, std::vector<anytype>(Nx)));
544 }
545
547
555 template<typename anytype>
556 typename std::enable_if<std::is_default_constructible<anytype>::value>::type resize_vector(std::vector<std::vector<std::vector<std::vector<anytype>>>> &vec, size_t Nx, size_t Ny, size_t Nz, size_t Nw) {
557 vec.assign(Nw, std::vector<std::vector<std::vector<anytype>>>(Nz, std::vector<std::vector<anytype>>(Ny, std::vector<anytype>(Nx))));
558 }
559
561
566 [[nodiscard]] RGBcolor blend(const RGBcolor &color0, const RGBcolor &color1, float weight);
567
569
574 [[nodiscard]] RGBAcolor blend(const RGBAcolor &color0, const RGBAcolor &color1, float weight);
575
577
582 [[nodiscard]] vec3 rotatePoint(const vec3 &position, const SphericalCoord &rotation);
583
585
591 [[nodiscard]] vec3 rotatePoint(const vec3 &position, float theta, float phi);
592
594
600 [[nodiscard]] vec3 rotatePointAboutLine(const vec3 &point, const vec3 &line_base, const vec3 &line_direction, float theta);
601
603
610 [[nodiscard]] float calculateTriangleArea(const vec3 &v0, const vec3 &v1, const vec3 &v2);
611
613
619 [[nodiscard]] Date CalendarDay(int Julian_day, int year);
620
622
629 [[nodiscard]] int JulianDay(int day, int month, int year);
630
632
638 [[nodiscard]] int JulianDay(const Date &date);
639
641
642 [[nodiscard]] float randu();
643
645
648 [[nodiscard]] int randu(int imin, int imax);
649
651
654 [[nodiscard]] float acos_safe(float x);
655
657
660 [[nodiscard]] float asin_safe(float x);
661
663
667 template<typename T>
668 T powi(T base, std::size_t exp) {
669 static_assert(std::is_same_v<T, uint> || std::is_same_v<T, int> || std::is_same_v<T, float> || std::is_same_v<T, double> || std::is_same_v<T, char> || std::is_same_v<T, size_t>, "helios::powi() was called with an unsupported type.");
670 T result = static_cast<T>(1);
671 while (exp > 0) {
672 // If the low bit is set, multiply result by current base
673 if (exp & 1) {
674 result *= base;
675 }
676 // Square the base for the next bit
677 base *= base;
678 // Shift off the processed bit
679 exp >>= 1;
680 }
681 return result;
682 }
683
685
688 [[nodiscard]] bool lineIntersection(const vec2 &p1, const vec2 &q1, const vec2 &p2, const vec2 &q2);
689
691
697 [[nodiscard]] bool pointOnSegment(const vec2 &point, const vec2 &seg_start, const vec2 &seg_end);
698
700
703 [[nodiscard]] bool pointInPolygon(const vec2 &point, const std::vector<vec2> &polygon_verts);
704
706
709 struct Timer {
710 public:
711 Timer() {
712 running = false;
713 }
714
716 void tic() {
717 timer_start = std::chrono::high_resolution_clock::now();
718 running = true;
719 };
720
722 double toc() const {
723 return toc("");
724 }
725
727
731 double toc(const char *message) const {
732 if (!running) {
733 std::cerr << "ERROR (Timer): You must call `tic' before calling `toc'. Ignoring call to `toc'..." << std::endl;
734 return 0;
735 }
736
737 auto timer_end = std::chrono::high_resolution_clock::now();
738 ;
739 double duration = std::chrono::duration<double>(timer_end - timer_start).count();
740 if (strcmp(message, "mute") != 0) {
741 std::cout << "Elapsed time is " << duration << " seconds: " << message << std::endl;
742 }
743 return duration;
744 }
745
746 private:
747 bool running;
748 std::chrono::high_resolution_clock::time_point timer_start;
749 };
750
752
756 private:
757 size_t total_steps;
758 size_t current_step;
759 int bar_width;
760 bool enabled;
761 std::string message;
762 std::function<void(float, const std::string&)> callback;
763
764 public:
766
772 ProgressBar(size_t total, int width = 50, bool enable = true, const std::string &progress_message = "Progress");
773
775 void update();
776
778
781 void update(size_t step_number);
782
784 void finish();
785
787
790 void setEnabled(bool enable);
791
793
796 [[nodiscard]] bool isEnabled() const;
797
800
802
805 void setCallback(std::function<void(float, const std::string&)> cb);
806 };
807
809
813 void wait(float seconds);
814
816
819 [[nodiscard]] bool PNGHasAlpha(const char *filename);
820
822
826 [[nodiscard]] std::vector<std::vector<bool>> readPNGAlpha(const std::string &filename);
827
829
835 void readPNG(const std::string &filename, uint &width, uint &height, std::vector<helios::RGBAcolor> &pixel_data);
836
838
844 void writePNG(const std::string &filename, uint width, uint height, const std::vector<helios::RGBAcolor> &pixel_data);
845
847
854 void writePNG(const std::string &filename, uint width, uint height, const std::vector<unsigned char> &pixel_data);
855
857
863 void readJPEG(const std::string &filename, uint &width, uint &height, std::vector<helios::RGBcolor> &pixel_data);
864
866
869 [[nodiscard]] helios::int2 getImageResolutionJPEG(const std::string &filename);
870
872
878 void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<helios::RGBcolor> &pixel_data);
879
881
888 void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<unsigned char> &pixel_data);
889
891
903 void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<helios::RGBcolor> &pixel_data, const helios::ImageEXIFData &metadata);
904
906
913 void writeEXR(const std::string &filename, uint width, uint height, const std::vector<float> &pixel_data, const std::string &channel_name = "Y");
914
916
923 void writeEXR(const std::string &filename, uint width, uint height, const std::vector<std::vector<float>> &channel_data, const std::vector<std::string> &channel_names);
924
926
930 template<typename T>
931 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<T>> &vec) {
932 std::vector<T> result;
933 for (const auto &row: vec) {
934 result.insert(result.end(), row.begin(), row.end());
935 }
936 return result;
937 }
938
940
944 template<typename T>
945 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<std::vector<T>>> &vec) {
946 std::vector<T> result;
947 for (const auto &matrix: vec) {
948 for (const auto &row: matrix) {
949 result.insert(result.end(), row.begin(), row.end());
950 }
951 }
952 return result;
953 }
954
956
960 template<typename T>
961 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<std::vector<std::vector<T>>>> &vec) {
962 std::vector<T> result;
963 for (const auto &tensor: vec) {
964 for (const auto &matrix: tensor) {
965 for (const auto &row: matrix) {
966 result.insert(result.end(), row.begin(), row.end());
967 }
968 }
969 }
970 return result;
971 }
972
974
983 [[nodiscard]] helios::vec3 spline_interp3(float u, const vec3 &x_start, const vec3 &tan_start, const vec3 &x_end, const vec3 &tan_end);
984
986
990 [[nodiscard]] float XMLloadfloat(pugi::xml_node node, const char *field);
991
993
997 [[nodiscard]] int XMLloadint(pugi::xml_node node, const char *field);
998
1000
1004 [[nodiscard]] std::string XMLloadstring(pugi::xml_node node, const char *field);
1005
1007
1011 [[nodiscard]] helios::vec2 XMLloadvec2(pugi::xml_node node, const char *field);
1012
1014
1018 [[nodiscard]] helios::vec3 XMLloadvec3(pugi::xml_node node, const char *field);
1019
1021
1025 [[nodiscard]] helios::vec4 XMLloadvec4(pugi::xml_node node, const char *field);
1026
1028
1032 [[nodiscard]] helios::int2 XMLloadint2(pugi::xml_node node, const char *field);
1033
1035
1039 [[nodiscard]] helios::int3 XMLloadint3(pugi::xml_node node, const char *field);
1040
1042
1046 [[nodiscard]] helios::int4 XMLloadint4(pugi::xml_node node, const char *field);
1047
1049
1053 [[nodiscard]] helios::RGBcolor XMLloadrgb(pugi::xml_node node, const char *field);
1054
1056
1060 [[nodiscard]] helios::RGBAcolor XMLloadrgba(pugi::xml_node node, const char *field);
1061
1062 // Forward declaration for fzero()
1063 class WarningAggregator;
1064
1066
1075 [[nodiscard]] float fzero(float (*function)(float value, std::vector<float> &variables, const void *parameters), std::vector<float> &variables, const void *parameters, float init_guess, float err_tol = 0.0001f, int max_iterations = 100,
1076 WarningAggregator *warnings = nullptr);
1077
1079
1089 [[nodiscard]] float fzero(float (*function)(float value, std::vector<float> &variables, const void *parameters), std::vector<float> &variables, const void *parameters, float init_guess, bool &converged, float err_tol = 0.0001f,
1090 int max_iterations = 100);
1091
1093
1098 [[nodiscard]] float interp1(const std::vector<helios::vec2> &points, float x);
1099
1101
1106 [[nodiscard]] float point_distance(const helios::vec3 &p1, const helios::vec3 &p2);
1107
1109
1116 [[nodiscard]] std::vector<float> linspace(float start, float end, int num);
1117
1119
1126 [[nodiscard]] std::vector<vec2> linspace(const vec2 &start, const vec2 &end, int num);
1127
1129
1136 [[nodiscard]] std::vector<vec3> linspace(const vec3 &start, const vec3 &end, int num);
1137
1139
1146 [[nodiscard]] std::vector<vec4> linspace(const vec4 &start, const vec4 &end, int num);
1147
1149
1155 [[nodiscard]] std::string getFileExtension(const std::string &filepath);
1156
1158
1164 [[nodiscard]] std::string getFileStem(const std::string &filepath);
1165
1167
1173 [[nodiscard]] std::string getFileName(const std::string &filepath);
1174
1176
1183 [[nodiscard]] std::string getFilePath(const std::string &filepath, bool trailingslash = true);
1184
1186
1191 [[nodiscard]] bool validateOutputPath(std::string &output_directory, const std::vector<std::string> &allowable_file_extensions = {});
1192
1194
1199 [[nodiscard]] bool isDirectoryPath(const std::string &path);
1200
1201 //--------------------- ASSET PATH RESOLUTION -----------------------------------//
1202
1204
1210 [[nodiscard]] std::filesystem::path resolveAssetPath(const std::string &relativePath);
1211
1213
1219 [[nodiscard]] std::filesystem::path resolvePluginAsset(const std::string &pluginName, const std::string &assetPath);
1220
1222
1231 [[nodiscard]] std::filesystem::path tryResolvePluginAsset(const std::string &pluginName, const std::string &assetPath);
1232
1234
1245 [[nodiscard]] std::filesystem::path resolveFilePath(const std::string &filename);
1246
1248
1256 [[nodiscard]] std::filesystem::path tryResolveFilePath(const std::string &filename);
1257
1259
1264 [[nodiscard]] std::filesystem::path resolveSpectraPath(const std::string &spectraFile);
1265
1267
1272 [[nodiscard]] bool validateAssetPath(const std::filesystem::path &assetPath);
1273
1275
1281 [[nodiscard]] std::filesystem::path findProjectRoot(const std::filesystem::path &startPath = std::filesystem::current_path());
1282
1284
1291 [[nodiscard]] std::filesystem::path resolveProjectFile(const std::string &relativePath);
1292
1294
1298 [[nodiscard]] std::vector<float> importVectorFromFile(const std::string &filepath);
1299
1301
1307 [[nodiscard]] float sample_Beta_distribution(float mu, float nu, std::minstd_rand0 *generator);
1308
1317 [[nodiscard]] float sample_ellipsoidal_azimuth(float e, float phi0_degrees, std::minstd_rand0 *generator);
1318
1319 inline std::vector<float> &operator+=(std::vector<float> &lhs, const std::vector<float> &rhs) {
1320 // Make sure vectors have the same size
1321 if (lhs.size() != rhs.size()) {
1322 throw std::invalid_argument("Vector sizes must match for element-wise addition");
1323 }
1324
1325 // Perform element-by-element addition
1326 for (size_t i = 0; i < lhs.size(); ++i) {
1327 lhs[i] += rhs[i];
1328 }
1329
1330 return lhs;
1331 }
1332
1333 inline std::vector<float> operator+(const std::vector<float> &vector1, const std::vector<float> &vector2) {
1334 if (vector1.size() != vector2.size()) {
1335 throw std::invalid_argument("Vector sizes must match for element-wise addition");
1336 }
1337
1338 std::vector<float> result(vector1.size());
1339 for (std::size_t i = 0; i < vector1.size(); ++i) {
1340 result[i] = vector1[i] + vector2[i];
1341 }
1342 return result;
1343 }
1344
1346
1352 inline std::vector<float> operator+(const std::vector<float> &vec, float scalar) {
1353 std::vector<float> result(vec.size());
1354 for (std::size_t i = 0; i < vec.size(); ++i) {
1355 result[i] = vec[i] + scalar;
1356 }
1357 return result;
1358 }
1359
1361
1367 inline std::vector<float> operator+(float scalar, const std::vector<float> &vec) {
1368 return vec + scalar;
1369 }
1370
1372
1378 inline std::vector<float> operator-(const std::vector<float> &vec, float scalar) {
1379 std::vector<float> result(vec.size());
1380 for (std::size_t i = 0; i < vec.size(); ++i) {
1381 result[i] = vec[i] - scalar;
1382 }
1383 return result;
1384 }
1385
1387
1393 inline std::vector<float> operator-(float scalar, const std::vector<float> &vec) {
1394 std::vector<float> result(vec.size());
1395 for (std::size_t i = 0; i < vec.size(); ++i) {
1396 result[i] = scalar - vec[i];
1397 }
1398 return result;
1399 }
1400
1402
1408 inline std::vector<float> operator*(const std::vector<float> &vec, float scalar) {
1409 std::vector<float> result(vec.size());
1410 for (std::size_t i = 0; i < vec.size(); ++i) {
1411 result[i] = vec[i] * scalar;
1412 }
1413 return result;
1414 }
1415
1417
1423 inline std::vector<float> operator*(float scalar, const std::vector<float> &vec) {
1424 return vec * scalar;
1425 }
1426
1428
1434 inline std::vector<float> operator/(const std::vector<float> &vec, float scalar) {
1435 std::vector<float> result(vec.size());
1436 for (std::size_t i = 0; i < vec.size(); ++i) {
1437 result[i] = vec[i] / scalar;
1438 }
1439 return result;
1440 }
1441
1443
1449 inline std::vector<float> operator/(float scalar, const std::vector<float> &vec) {
1450 std::vector<float> result(vec.size());
1451 for (std::size_t i = 0; i < vec.size(); ++i) {
1452 result[i] = scalar / vec[i];
1453 }
1454 return result;
1455 }
1456
1463 struct PixelUVKey {
1464 std::vector<int> coords; // {x0,y0, x1,y1, …}
1465 bool operator==(PixelUVKey const &o) const noexcept {
1466 return coords == o.coords;
1467 }
1468 };
1469
1476 size_t operator()(PixelUVKey const &k) const noexcept {
1477 uint64_t h = 146527; // arbitrary seed
1478 for (int v: k.coords) {
1479 // mix in v
1480 h ^= uint64_t(v) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
1481 }
1482 return size_t(h);
1483 }
1484 };
1485
1487
1494 std::streambuf *old_buf;
1495 std::ostringstream captured_stream;
1496
1497 capture_cerr() {
1498 old_buf = std::cerr.rdbuf(captured_stream.rdbuf());
1499 }
1500
1501 ~capture_cerr() {
1502 std::cerr.rdbuf(old_buf);
1503 }
1504
1506 std::string get_captured_output() const {
1507 return captured_stream.str();
1508 }
1509
1511 bool has_output() const {
1512 return !captured_stream.str().empty();
1513 }
1514
1516 void clear() {
1517 captured_stream.str("");
1518 captured_stream.clear();
1519 }
1520
1522 size_t size() const {
1523 return captured_stream.str().size();
1524 }
1525 };
1526
1528
1535 std::streambuf *old_buf;
1536 std::ostringstream captured_stream;
1537
1538 capture_cout() {
1539 old_buf = std::cout.rdbuf(captured_stream.rdbuf());
1540 }
1541
1542 ~capture_cout() {
1543 std::cout.rdbuf(old_buf);
1544 }
1545
1547 std::string get_captured_output() const {
1548 return captured_stream.str();
1549 }
1550
1552 bool has_output() const {
1553 return !captured_stream.str().empty();
1554 }
1555
1557 void clear() {
1558 captured_stream.str("");
1559 captured_stream.clear();
1560 }
1561
1563 size_t size() const {
1564 return captured_stream.str().size();
1565 }
1566 };
1567
1568
1570
1591 public:
1594
1596
1601 void addWarning(const std::string &category, const std::string &message);
1602
1604
1610 void report(std::ostream &stream = std::cerr, bool compact = false);
1611
1613
1617 [[nodiscard]] size_t getCount(const std::string &category) const;
1618
1620 void clear();
1621
1623
1627 void setEnabled(bool enabled);
1628
1630
1633 [[nodiscard]] bool isEnabled() const;
1634
1635 private:
1636 mutable std::mutex mutex_;
1637 std::unordered_map<std::string, std::vector<std::string>> warnings_;
1638 std::unordered_map<std::string, size_t> counts_;
1639 bool enabled_ = true;
1640 static constexpr size_t MAX_EXAMPLES = 100;
1641 };
1642
1646 extern vec3 nullorigin;
1647} // namespace helios
1648
1649#endif