1.3.72
 
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
69// pugi XML parser
70#include "pugixml.hpp"
71
72// Standard library for file path resolution
73#include <filesystem>
74
75// Thread synchronization
76#include <mutex>
77
78// *** Groups *** //
79
81
86
91
95namespace helios {
96
98
101 void helios_runtime_error(const std::string &error_message);
102
103 //--------------------- HELPER FUNCTIONS -----------------------------------//
104
106
112 void makeRotationMatrix(float rotation, const char *axis, float (&transform)[16]);
113
115
121 void makeRotationMatrix(float rotation, const helios::vec3 &axis, float (&transform)[16]);
122
124
131 void makeRotationMatrix(float rotation, const helios::vec3 &origin, const helios::vec3 &axis, float (&transform)[16]);
132
134
139 void makeTranslationMatrix(const helios::vec3 &translation, float (&transform)[16]);
140
142
147 void makeScaleMatrix(const helios::vec3 &scale, float (&transform)[16]);
148
150
156 void makeScaleMatrix(const helios::vec3 &scale, const helios::vec3 &point, float (&transform)[16]);
157
159
169 void matmult(const float ML[16], const float MR[16], float (&T)[16]);
170
172
177 void vecmult(const float M[16], const float v[3], float (&result)[3]);
178
180
185 void vecmult(const float M[16], const helios::vec3 &v3, helios::vec3 &result);
186
188
191 void makeIdentityMatrix(float (&T)[16]);
192
194
200 [[nodiscard]] float deg2rad(float deg);
201
203
209 [[nodiscard]] float rad2deg(float rad);
210
212
216 [[nodiscard]] float atan2_2pi(float y, float x);
217
219
225 [[nodiscard]] SphericalCoord cart2sphere(const vec3 &Cartesian);
226
228
234 [[nodiscard]] vec3 sphere2cart(const SphericalCoord &Spherical);
235
237
240 [[nodiscard]] vec2 string2vec2(const char *str);
241
243
246 [[nodiscard]] vec3 string2vec3(const char *str);
247
249
252 [[nodiscard]] vec4 string2vec4(const char *str);
253
255
258 [[nodiscard]] int2 string2int2(const char *str);
259
261
264 [[nodiscard]] int3 string2int3(const char *str);
265
267
270 [[nodiscard]] int4 string2int4(const char *str);
271
273
278 bool parse_float(const std::string &input_string, float &converted_float);
279
281
286 bool parse_double(const std::string &input_string, double &converted_double);
287
289
294 bool parse_int(const std::string &input_string, int &converted_int);
295
297
302 bool parse_int2(const std::string &input_string, int2 &converted_int2);
303
305
310 bool parse_int3(const std::string &input_string, int3 &converted_int3);
311
313
318 bool parse_uint(const std::string &input_string, uint &converted_uint);
319
321
326 bool parse_vec2(const std::string &input_string, vec2 &converted_vec2);
327
329
334 bool parse_vec3(const std::string &input_string, vec3 &converted_vec3);
335
337
342 bool parse_RGBcolor(const std::string &input_string, RGBcolor &converted_rgb);
343
345
351 bool open_xml_file(const std::string &xml_file, pugi::xml_document &xmldoc, std::string &error_string);
352
354
360 [[nodiscard]] int parse_xml_tag_int(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
361
363
369 [[nodiscard]] float parse_xml_tag_float(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
370
372
378 [[nodiscard]] vec2 parse_xml_tag_vec2(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
379
381
387 [[nodiscard]] vec3 parse_xml_tag_vec3(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
388
390
396 [[nodiscard]] std::string parse_xml_tag_string(const pugi::xml_node &node, const std::string &tag, const std::string &calling_function);
397
399
402 [[nodiscard]] RGBAcolor string2RGBcolor(const char *str);
403
405
408 [[nodiscard]] std::string deblank(const char *input);
409
411
414 [[nodiscard]] std::string deblank(const std::string &input);
415
417
420 [[nodiscard]] std::string trim_whitespace(const std::string &input);
421
423
429 [[nodiscard]] std::vector<std::string> separate_string_by_delimiter(const std::string &inputstring, const std::string &delimiter);
430
432
438 template<typename anytype>
439 [[nodiscard]] anytype clamp(anytype value, anytype min, anytype max) {
440 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>,
441 "helios::clamp() was called with an unsupported type.");
442 if (value < min) {
443 value = min;
444 } else if (value > max) {
445 value = max;
446 }
447 return value;
448 }
449
451
455 [[nodiscard]] float sum(const std::vector<float> &vect);
456
458
462 [[nodiscard]] float mean(const std::vector<float> &vect);
463
465
469 [[nodiscard]] float min(const std::vector<float> &vect);
470
472
476 [[nodiscard]] int min(const std::vector<int> &vect);
477
479
483 [[nodiscard]] vec3 min(const std::vector<vec3> &vect);
484
486
490 [[nodiscard]] float max(const std::vector<float> &vect);
491
493
497 [[nodiscard]] int max(const std::vector<int> &vect);
498
500
504 [[nodiscard]] vec3 max(const std::vector<vec3> &vect);
505
507
511 [[nodiscard]] float stdev(const std::vector<float> &vect);
512
514
518 [[nodiscard]] float median(std::vector<float> vect);
519
521
527 template<typename anytype>
528 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) {
529 vec.assign(Ny, std::vector<anytype>(Nx));
530 }
531
533
540 template<typename anytype>
541 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) {
542 vec.assign(Nz, std::vector<std::vector<anytype>>(Ny, std::vector<anytype>(Nx)));
543 }
544
546
554 template<typename anytype>
555 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) {
556 vec.assign(Nw, std::vector<std::vector<std::vector<anytype>>>(Nz, std::vector<std::vector<anytype>>(Ny, std::vector<anytype>(Nx))));
557 }
558
560
565 [[nodiscard]] RGBcolor blend(const RGBcolor &color0, const RGBcolor &color1, float weight);
566
568
573 [[nodiscard]] RGBAcolor blend(const RGBAcolor &color0, const RGBAcolor &color1, float weight);
574
576
581 [[nodiscard]] vec3 rotatePoint(const vec3 &position, const SphericalCoord &rotation);
582
584
590 [[nodiscard]] vec3 rotatePoint(const vec3 &position, float theta, float phi);
591
593
599 [[nodiscard]] vec3 rotatePointAboutLine(const vec3 &point, const vec3 &line_base, const vec3 &line_direction, float theta);
600
602
609 [[nodiscard]] float calculateTriangleArea(const vec3 &v0, const vec3 &v1, const vec3 &v2);
610
612
618 [[nodiscard]] Date CalendarDay(int Julian_day, int year);
619
621
628 [[nodiscard]] int JulianDay(int day, int month, int year);
629
631
637 [[nodiscard]] int JulianDay(const Date &date);
638
640
641 [[nodiscard]] float randu();
642
644
647 [[nodiscard]] int randu(int imin, int imax);
648
650
653 [[nodiscard]] float acos_safe(float x);
654
656
659 [[nodiscard]] float asin_safe(float x);
660
662
666 template<typename T>
667 T powi(T base, std::size_t exp) {
668 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.");
669 T result = static_cast<T>(1);
670 while (exp > 0) {
671 // If the low bit is set, multiply result by current base
672 if (exp & 1) {
673 result *= base;
674 }
675 // Square the base for the next bit
676 base *= base;
677 // Shift off the processed bit
678 exp >>= 1;
679 }
680 return result;
681 }
682
684
687 [[nodiscard]] bool lineIntersection(const vec2 &p1, const vec2 &q1, const vec2 &p2, const vec2 &q2);
688
690
696 [[nodiscard]] bool pointOnSegment(const vec2 &point, const vec2 &seg_start, const vec2 &seg_end);
697
699
702 [[nodiscard]] bool pointInPolygon(const vec2 &point, const std::vector<vec2> &polygon_verts);
703
705
708 struct Timer {
709 public:
710 Timer() {
711 running = false;
712 }
713
715 void tic() {
716 timer_start = std::chrono::high_resolution_clock::now();
717 running = true;
718 };
719
721 double toc() const {
722 return toc("");
723 }
724
726
730 double toc(const char *message) const {
731 if (!running) {
732 std::cerr << "ERROR (Timer): You must call `tic' before calling `toc'. Ignoring call to `toc'..." << std::endl;
733 return 0;
734 }
735
736 auto timer_end = std::chrono::high_resolution_clock::now();
737 ;
738 double duration = std::chrono::duration<double>(timer_end - timer_start).count();
739 if (strcmp(message, "mute") != 0) {
740 std::cout << "Elapsed time is " << duration << " seconds: " << message << std::endl;
741 }
742 return duration;
743 }
744
745 private:
746 bool running;
747 std::chrono::high_resolution_clock::time_point timer_start;
748 };
749
751
755 private:
756 size_t total_steps;
757 size_t current_step;
758 int bar_width;
759 bool enabled;
760 std::string message;
761 std::function<void(float, const std::string&)> callback;
762
763 public:
765
771 ProgressBar(size_t total, int width = 50, bool enable = true, const std::string &progress_message = "Progress");
772
774 void update();
775
777
780 void update(size_t step_number);
781
783 void finish();
784
786
789 void setEnabled(bool enable);
790
792
795 [[nodiscard]] bool isEnabled() const;
796
799
801
804 void setCallback(std::function<void(float, const std::string&)> cb);
805 };
806
808
812 void wait(float seconds);
813
815
818 [[nodiscard]] bool PNGHasAlpha(const char *filename);
819
821
825 [[nodiscard]] std::vector<std::vector<bool>> readPNGAlpha(const std::string &filename);
826
828
834 void readPNG(const std::string &filename, uint &width, uint &height, std::vector<helios::RGBAcolor> &pixel_data);
835
837
843 void writePNG(const std::string &filename, uint width, uint height, const std::vector<helios::RGBAcolor> &pixel_data);
844
846
853 void writePNG(const std::string &filename, uint width, uint height, const std::vector<unsigned char> &pixel_data);
854
856
862 void readJPEG(const std::string &filename, uint &width, uint &height, std::vector<helios::RGBcolor> &pixel_data);
863
865
868 [[nodiscard]] helios::int2 getImageResolutionJPEG(const std::string &filename);
869
871
877 void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<helios::RGBcolor> &pixel_data);
878
880
887 void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<unsigned char> &pixel_data);
888
890
897 void writeEXR(const std::string &filename, uint width, uint height, const std::vector<float> &pixel_data, const std::string &channel_name = "Y");
898
900
907 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);
908
910
914 template<typename T>
915 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<T>> &vec) {
916 std::vector<T> result;
917 for (const auto &row: vec) {
918 result.insert(result.end(), row.begin(), row.end());
919 }
920 return result;
921 }
922
924
928 template<typename T>
929 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<std::vector<T>>> &vec) {
930 std::vector<T> result;
931 for (const auto &matrix: vec) {
932 for (const auto &row: matrix) {
933 result.insert(result.end(), row.begin(), row.end());
934 }
935 }
936 return result;
937 }
938
940
944 template<typename T>
945 [[nodiscard]] std::vector<T> flatten(const std::vector<std::vector<std::vector<std::vector<T>>>> &vec) {
946 std::vector<T> result;
947 for (const auto &tensor: vec) {
948 for (const auto &matrix: tensor) {
949 for (const auto &row: matrix) {
950 result.insert(result.end(), row.begin(), row.end());
951 }
952 }
953 }
954 return result;
955 }
956
958
967 [[nodiscard]] helios::vec3 spline_interp3(float u, const vec3 &x_start, const vec3 &tan_start, const vec3 &x_end, const vec3 &tan_end);
968
970
974 [[nodiscard]] float XMLloadfloat(pugi::xml_node node, const char *field);
975
977
981 [[nodiscard]] int XMLloadint(pugi::xml_node node, const char *field);
982
984
988 [[nodiscard]] std::string XMLloadstring(pugi::xml_node node, const char *field);
989
991
995 [[nodiscard]] helios::vec2 XMLloadvec2(pugi::xml_node node, const char *field);
996
998
1002 [[nodiscard]] helios::vec3 XMLloadvec3(pugi::xml_node node, const char *field);
1003
1005
1009 [[nodiscard]] helios::vec4 XMLloadvec4(pugi::xml_node node, const char *field);
1010
1012
1016 [[nodiscard]] helios::int2 XMLloadint2(pugi::xml_node node, const char *field);
1017
1019
1023 [[nodiscard]] helios::int3 XMLloadint3(pugi::xml_node node, const char *field);
1024
1026
1030 [[nodiscard]] helios::int4 XMLloadint4(pugi::xml_node node, const char *field);
1031
1033
1037 [[nodiscard]] helios::RGBcolor XMLloadrgb(pugi::xml_node node, const char *field);
1038
1040
1044 [[nodiscard]] helios::RGBAcolor XMLloadrgba(pugi::xml_node node, const char *field);
1045
1046 // Forward declaration for fzero()
1047 class WarningAggregator;
1048
1050
1059 [[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,
1060 WarningAggregator *warnings = nullptr);
1061
1063
1073 [[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,
1074 int max_iterations = 100);
1075
1077
1082 [[nodiscard]] float interp1(const std::vector<helios::vec2> &points, float x);
1083
1085
1090 [[nodiscard]] float point_distance(const helios::vec3 &p1, const helios::vec3 &p2);
1091
1093
1100 [[nodiscard]] std::vector<float> linspace(float start, float end, int num);
1101
1103
1110 [[nodiscard]] std::vector<vec2> linspace(const vec2 &start, const vec2 &end, int num);
1111
1113
1120 [[nodiscard]] std::vector<vec3> linspace(const vec3 &start, const vec3 &end, int num);
1121
1123
1130 [[nodiscard]] std::vector<vec4> linspace(const vec4 &start, const vec4 &end, int num);
1131
1133
1139 [[nodiscard]] std::string getFileExtension(const std::string &filepath);
1140
1142
1148 [[nodiscard]] std::string getFileStem(const std::string &filepath);
1149
1151
1157 [[nodiscard]] std::string getFileName(const std::string &filepath);
1158
1160
1167 [[nodiscard]] std::string getFilePath(const std::string &filepath, bool trailingslash = true);
1168
1170
1175 [[nodiscard]] bool validateOutputPath(std::string &output_directory, const std::vector<std::string> &allowable_file_extensions = {});
1176
1178
1183 [[nodiscard]] bool isDirectoryPath(const std::string &path);
1184
1185 //--------------------- ASSET PATH RESOLUTION -----------------------------------//
1186
1188
1194 [[nodiscard]] std::filesystem::path resolveAssetPath(const std::string &relativePath);
1195
1197
1203 [[nodiscard]] std::filesystem::path resolvePluginAsset(const std::string &pluginName, const std::string &assetPath);
1204
1206
1215 [[nodiscard]] std::filesystem::path tryResolvePluginAsset(const std::string &pluginName, const std::string &assetPath);
1216
1218
1229 [[nodiscard]] std::filesystem::path resolveFilePath(const std::string &filename);
1230
1232
1240 [[nodiscard]] std::filesystem::path tryResolveFilePath(const std::string &filename);
1241
1243
1248 [[nodiscard]] std::filesystem::path resolveSpectraPath(const std::string &spectraFile);
1249
1251
1256 [[nodiscard]] bool validateAssetPath(const std::filesystem::path &assetPath);
1257
1259
1265 [[nodiscard]] std::filesystem::path findProjectRoot(const std::filesystem::path &startPath = std::filesystem::current_path());
1266
1268
1275 [[nodiscard]] std::filesystem::path resolveProjectFile(const std::string &relativePath);
1276
1278
1282 [[nodiscard]] std::vector<float> importVectorFromFile(const std::string &filepath);
1283
1285
1291 [[nodiscard]] float sample_Beta_distribution(float mu, float nu, std::minstd_rand0 *generator);
1292
1301 [[nodiscard]] float sample_ellipsoidal_azimuth(float e, float phi0_degrees, std::minstd_rand0 *generator);
1302
1303 inline std::vector<float> &operator+=(std::vector<float> &lhs, const std::vector<float> &rhs) {
1304 // Make sure vectors have the same size
1305 if (lhs.size() != rhs.size()) {
1306 throw std::invalid_argument("Vector sizes must match for element-wise addition");
1307 }
1308
1309 // Perform element-by-element addition
1310 for (size_t i = 0; i < lhs.size(); ++i) {
1311 lhs[i] += rhs[i];
1312 }
1313
1314 return lhs;
1315 }
1316
1317 inline std::vector<float> operator+(const std::vector<float> &vector1, const std::vector<float> &vector2) {
1318 if (vector1.size() != vector2.size()) {
1319 throw std::invalid_argument("Vector sizes must match for element-wise addition");
1320 }
1321
1322 std::vector<float> result(vector1.size());
1323 for (std::size_t i = 0; i < vector1.size(); ++i) {
1324 result[i] = vector1[i] + vector2[i];
1325 }
1326 return result;
1327 }
1328
1330
1336 inline std::vector<float> operator+(const std::vector<float> &vec, float scalar) {
1337 std::vector<float> result(vec.size());
1338 for (std::size_t i = 0; i < vec.size(); ++i) {
1339 result[i] = vec[i] + scalar;
1340 }
1341 return result;
1342 }
1343
1345
1351 inline std::vector<float> operator+(float scalar, const std::vector<float> &vec) {
1352 return vec + scalar;
1353 }
1354
1356
1362 inline std::vector<float> operator-(const std::vector<float> &vec, float scalar) {
1363 std::vector<float> result(vec.size());
1364 for (std::size_t i = 0; i < vec.size(); ++i) {
1365 result[i] = vec[i] - scalar;
1366 }
1367 return result;
1368 }
1369
1371
1377 inline std::vector<float> operator-(float scalar, const std::vector<float> &vec) {
1378 std::vector<float> result(vec.size());
1379 for (std::size_t i = 0; i < vec.size(); ++i) {
1380 result[i] = scalar - vec[i];
1381 }
1382 return result;
1383 }
1384
1386
1392 inline std::vector<float> operator*(const std::vector<float> &vec, float scalar) {
1393 std::vector<float> result(vec.size());
1394 for (std::size_t i = 0; i < vec.size(); ++i) {
1395 result[i] = vec[i] * scalar;
1396 }
1397 return result;
1398 }
1399
1401
1407 inline std::vector<float> operator*(float scalar, const std::vector<float> &vec) {
1408 return vec * scalar;
1409 }
1410
1412
1418 inline std::vector<float> operator/(const std::vector<float> &vec, float scalar) {
1419 std::vector<float> result(vec.size());
1420 for (std::size_t i = 0; i < vec.size(); ++i) {
1421 result[i] = vec[i] / scalar;
1422 }
1423 return result;
1424 }
1425
1427
1433 inline std::vector<float> operator/(float scalar, const std::vector<float> &vec) {
1434 std::vector<float> result(vec.size());
1435 for (std::size_t i = 0; i < vec.size(); ++i) {
1436 result[i] = scalar / vec[i];
1437 }
1438 return result;
1439 }
1440
1447 struct PixelUVKey {
1448 std::vector<int> coords; // {x0,y0, x1,y1, …}
1449 bool operator==(PixelUVKey const &o) const noexcept {
1450 return coords == o.coords;
1451 }
1452 };
1453
1460 size_t operator()(PixelUVKey const &k) const noexcept {
1461 uint64_t h = 146527; // arbitrary seed
1462 for (int v: k.coords) {
1463 // mix in v
1464 h ^= uint64_t(v) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
1465 }
1466 return size_t(h);
1467 }
1468 };
1469
1471
1478 std::streambuf *old_buf;
1479 std::ostringstream captured_stream;
1480
1481 capture_cerr() {
1482 old_buf = std::cerr.rdbuf(captured_stream.rdbuf());
1483 }
1484
1485 ~capture_cerr() {
1486 std::cerr.rdbuf(old_buf);
1487 }
1488
1490 std::string get_captured_output() const {
1491 return captured_stream.str();
1492 }
1493
1495 bool has_output() const {
1496 return !captured_stream.str().empty();
1497 }
1498
1500 void clear() {
1501 captured_stream.str("");
1502 captured_stream.clear();
1503 }
1504
1506 size_t size() const {
1507 return captured_stream.str().size();
1508 }
1509 };
1510
1512
1519 std::streambuf *old_buf;
1520 std::ostringstream captured_stream;
1521
1522 capture_cout() {
1523 old_buf = std::cout.rdbuf(captured_stream.rdbuf());
1524 }
1525
1526 ~capture_cout() {
1527 std::cout.rdbuf(old_buf);
1528 }
1529
1531 std::string get_captured_output() const {
1532 return captured_stream.str();
1533 }
1534
1536 bool has_output() const {
1537 return !captured_stream.str().empty();
1538 }
1539
1541 void clear() {
1542 captured_stream.str("");
1543 captured_stream.clear();
1544 }
1545
1547 size_t size() const {
1548 return captured_stream.str().size();
1549 }
1550 };
1551
1552
1554
1575 public:
1578
1580
1585 void addWarning(const std::string &category, const std::string &message);
1586
1588
1594 void report(std::ostream &stream = std::cerr, bool compact = false);
1595
1597
1601 [[nodiscard]] size_t getCount(const std::string &category) const;
1602
1604 void clear();
1605
1607
1611 void setEnabled(bool enabled);
1612
1614
1617 [[nodiscard]] bool isEnabled() const;
1618
1619 private:
1620 mutable std::mutex mutex_;
1621 std::unordered_map<std::string, std::vector<std::string>> warnings_;
1622 std::unordered_map<std::string, size_t> counts_;
1623 bool enabled_ = true;
1624 static constexpr size_t MAX_EXAMPLES = 100;
1625 };
1626
1630 extern vec3 nullorigin;
1631} // namespace helios
1632
1633#endif