8TEST_CASE(
"Mathematical and Geometric Helpers") {
9 SUBCASE(
"global.h utilities") {
10 SUBCASE(
"deg2rad and rad2deg") {
11 DOCTEST_CHECK(
deg2rad(180.f) == doctest::Approx(
PI_F));
12 DOCTEST_CHECK(
rad2deg(
PI_F) == doctest::Approx(180.f));
16 DOCTEST_CHECK(
clamp(5, 0, 10) == 5);
17 DOCTEST_CHECK(
clamp(-5, 0, 10) == 0);
18 DOCTEST_CHECK(
clamp(15, 0, 10) == 10);
19 DOCTEST_CHECK(
clamp(5.5f, 0.f, 10.f) == doctest::Approx(5.5f));
22 SUBCASE(
"Safe trigonometry") {
23 DOCTEST_CHECK(
acos_safe(1.000001f) == doctest::Approx(0.f));
24 DOCTEST_CHECK(
acos_safe(-1.000001f) == doctest::Approx(
PI_F));
25 DOCTEST_CHECK(
asin_safe(1.000001f) == doctest::Approx(
PI_F / 2.f));
26 DOCTEST_CHECK(
asin_safe(-1.000001f) == doctest::Approx(-
PI_F / 2.f));
29 SUBCASE(
"Integer power") {
30 DOCTEST_CHECK(
powi(2, 10) == 1024);
31 DOCTEST_CHECK(
powi(3.f, 3) == doctest::Approx(27.f));
34 SUBCASE(
"calculateTriangleArea") {
35 vec3 v0(0, 0, 0), v1(1, 0, 0), v2(0, 1, 0);
39 SUBCASE(
"calculateTriangleArea with degenerate triangle") {
40 vec3 v0(0, 0, 0), v1(1, 1, 1), v2(2, 2, 2);
44 SUBCASE(
"blend colors") {
45 RGBcolor c1(1, 0, 0), c2(0, 1, 0);
46 RGBcolor blended =
blend(c1, c2, 0.5f);
47 DOCTEST_CHECK(blended.r == doctest::Approx(0.5f));
48 DOCTEST_CHECK(blended.g == doctest::Approx(0.5f));
49 DOCTEST_CHECK(blended.b == doctest::Approx(0.f));
51 RGBAcolor ca1(1, 0, 0, 0), ca2(0, 1, 0, 1);
52 RGBAcolor blended_a =
blend(ca1, ca2, 0.5f);
53 DOCTEST_CHECK(blended_a.r == doctest::Approx(0.5f));
54 DOCTEST_CHECK(blended_a.g == doctest::Approx(0.5f));
55 DOCTEST_CHECK(blended_a.b == doctest::Approx(0.f));
56 DOCTEST_CHECK(blended_a.a == doctest::Approx(0.5f));
59 SUBCASE(
"global.h geometric functions") {
60 SUBCASE(
"atan2_2pi") {
61 DOCTEST_CHECK(
atan2_2pi(0, 1) == doctest::Approx(0));
62 DOCTEST_CHECK(
atan2_2pi(1, 0) == doctest::Approx(
PI_F / 2.0));
64 DOCTEST_CHECK(
atan2_2pi(-1, 0) == doctest::Approx(3.0 *
PI_F / 2.0));
68 SUBCASE(
"atan2_2pi edge cases") {
70 DOCTEST_CHECK(res == doctest::Approx(0.f));
73 SUBCASE(
"rotatePoint") {
76 DOCTEST_CHECK(rotated.x == doctest::Approx(0.f));
77 DOCTEST_CHECK(rotated.y == doctest::Approx(0.f));
78 DOCTEST_CHECK(rotated.z == doctest::Approx(-1.f));
81 SUBCASE(
"rotatePointAboutLine") {
83 vec3 line_base(0, 0, 0);
84 vec3 line_dir(0, 0, 1);
86 DOCTEST_CHECK(rotated.x == doctest::Approx(0.f));
87 DOCTEST_CHECK(rotated.y == doctest::Approx(1.f));
88 DOCTEST_CHECK(rotated.z == doctest::Approx(0.f));
91 SUBCASE(
"lineIntersection") {
92 vec2 p1(0, 0), q1(2, 2);
93 vec2 p2(0, 2), q2(2, 0);
96 vec2 p3(0, 0), q3(1, 1);
97 vec2 p4(2, 2), q4(3, 3);
100 SUBCASE(
"lineIntersection with collinear and overlapping segments") {
102 vec2 p1(0, 0), q1(1, 1);
103 vec2 p2(2, 2), q2(3, 3);
107 vec2 p3(0, 0), q3(2, 2);
108 vec2 p4(1, 1), q4(3, 3);
112 vec2 p5(0, 0), q5(3, 3);
113 vec2 p6(1, 1), q6(2, 2);
117 vec2 p7(0, 0), q7(1, 1);
118 vec2 p8(1, 1), q8(2, 2);
121 SUBCASE(
"lineIntersection parallel") {
122 vec2 p1(0, 0), q1(1, 0);
123 vec2 p2(0, 1), q2(1, 1);
126 SUBCASE(
"pointInPolygon") {
127 std::vector<vec2> square = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
132 SUBCASE(
"pointInPolygon with concave polygon") {
134 std::vector<vec2> concave_poly = {{0, 0}, {5, 0}, {5, 5}, {3, 3}, {2, 5}, {0, 5}};
140 SUBCASE(
"Spline Interpolation") {
146 vec3 ref =
make_vec3(0.9360f, 0.8280f, 0.2592f);
147 DOCTEST_CHECK(xi.x == doctest::Approx(ref.x).epsilon(errtol));
148 DOCTEST_CHECK(xi.y == doctest::Approx(ref.y).epsilon(errtol));
149 DOCTEST_CHECK(xi.z == doctest::Approx(ref.z).epsilon(errtol));
151 SUBCASE(
"spline_interp3 edge cases") {
158 DOCTEST_CHECK(res_start.x == doctest::Approx(p0.x));
159 DOCTEST_CHECK(res_start.y == doctest::Approx(p0.y));
160 DOCTEST_CHECK(res_start.z == doctest::Approx(p0.z));
163 DOCTEST_CHECK(res_end.x == doctest::Approx(p1.x));
164 DOCTEST_CHECK(res_end.y == doctest::Approx(p1.y));
165 DOCTEST_CHECK(res_end.z == doctest::Approx(p1.z));
168 capture_cerr cerr_buffer;
170 DOCTEST_CHECK(res_low.x == doctest::Approx(p0.x));
172 DOCTEST_CHECK(res_high.x == doctest::Approx(p1.x));
174 SUBCASE(
"Angle conversion helpers") {
175 CHECK(
deg2rad(180.f) == doctest::Approx(
PI_F).epsilon(errtol));
176 CHECK(
rad2deg(
PI_F) == doctest::Approx(180.f).epsilon(errtol));
178 CHECK(
atan2_2pi(0.f, 1.f) == doctest::Approx(0.f).epsilon(errtol));
179 CHECK(
atan2_2pi(1.f, 0.f) == doctest::Approx(0.5f *
PI_F * 2.f / 2.f).epsilon(errtol));
180 CHECK(
atan2_2pi(0.f, -1.f) == doctest::Approx(
PI_F).epsilon(errtol));
181 CHECK(
atan2_2pi(-1.f, 0.f) == doctest::Approx(1.5f *
PI_F).epsilon(errtol));
184 SUBCASE(
"clamp template for common types") {
185 CHECK(
clamp(5, 1, 4) == 4);
186 CHECK(
clamp(-1, 0, 10) == 0);
187 CHECK(
clamp(3.5f, 0.f, 10.f) == doctest::Approx(3.5f).epsilon(errtol));
188 CHECK(
clamp(12.0, -5.0, 11.0) == doctest::Approx(11.0).epsilon(errtol));
192TEST_CASE(
"Matrix and Transformation Helpers") {
193 float M[16], T[16],
R[16];
195 SUBCASE(
"makeIdentityMatrix") {
197 DOCTEST_CHECK(T[0] == 1.f);
198 DOCTEST_CHECK(T[1] == 0.f);
199 DOCTEST_CHECK(T[2] == 0.f);
200 DOCTEST_CHECK(T[3] == 0.f);
201 DOCTEST_CHECK(T[4] == 0.f);
202 DOCTEST_CHECK(T[5] == 1.f);
203 DOCTEST_CHECK(T[6] == 0.f);
204 DOCTEST_CHECK(T[7] == 0.f);
205 DOCTEST_CHECK(T[8] == 0.f);
206 DOCTEST_CHECK(T[9] == 0.f);
207 DOCTEST_CHECK(T[10] == 1.f);
208 DOCTEST_CHECK(T[11] == 0.f);
209 DOCTEST_CHECK(T[12] == 0.f);
210 DOCTEST_CHECK(T[13] == 0.f);
211 DOCTEST_CHECK(T[14] == 0.f);
212 DOCTEST_CHECK(T[15] == 1.f);
215 SUBCASE(
"makeRotationMatrix") {
218 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
219 DOCTEST_CHECK(T[6] == doctest::Approx(-1.f));
220 DOCTEST_CHECK(T[9] == doctest::Approx(1.f));
221 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
225 DOCTEST_CHECK(T[0] == doctest::Approx(0.f));
226 DOCTEST_CHECK(T[2] == doctest::Approx(1.f));
227 DOCTEST_CHECK(T[8] == doctest::Approx(-1.f));
228 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
232 DOCTEST_CHECK(T[0] == doctest::Approx(0.f));
233 DOCTEST_CHECK(T[1] == doctest::Approx(-1.f));
234 DOCTEST_CHECK(T[4] == doctest::Approx(1.f));
235 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
240 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
241 DOCTEST_CHECK(T[6] == doctest::Approx(-1.f));
242 DOCTEST_CHECK(T[9] == doctest::Approx(1.f));
243 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
246 SUBCASE(
"makeRotationMatrix invalid axis") {
247 capture_cerr cerr_buffer;
252 SUBCASE(
"makeTranslationMatrix") {
254 DOCTEST_CHECK(T[3] == 1.f);
255 DOCTEST_CHECK(T[7] == 2.f);
256 DOCTEST_CHECK(T[11] == 3.f);
259 SUBCASE(
"makeScaleMatrix") {
262 DOCTEST_CHECK(T[0] == 2.f);
263 DOCTEST_CHECK(T[5] == 3.f);
264 DOCTEST_CHECK(T[10] == 4.f);
271 DOCTEST_CHECK(res.x == doctest::Approx(3.f));
272 DOCTEST_CHECK(res.y == doctest::Approx(3.f));
273 DOCTEST_CHECK(res.z == doctest::Approx(3.f));
280 DOCTEST_CHECK(
R[0] == doctest::Approx(1.f));
281 DOCTEST_CHECK(
R[5] == doctest::Approx(1.f));
282 DOCTEST_CHECK(
R[10] == doctest::Approx(1.f));
283 DOCTEST_CHECK(
R[15] == doctest::Approx(1.f));
291 DOCTEST_CHECK(res.x == doctest::Approx(0.f));
292 DOCTEST_CHECK(res.y == doctest::Approx(1.f));
293 DOCTEST_CHECK(res.z == doctest::Approx(0.f));
295 float v_arr[3] = {1, 0, 0};
298 DOCTEST_CHECK(res_arr[0] == doctest::Approx(0.f));
299 DOCTEST_CHECK(res_arr[1] == doctest::Approx(1.f));
300 DOCTEST_CHECK(res_arr[2] == doctest::Approx(0.f));
303 SUBCASE(
"makeIdentityMatrix / matmult / vecmult") {
306 for (
int r = 0; r < 4; ++r) {
307 for (
int c = 0; c < 4; ++c) {
308 CHECK(I[4 * r + c] == doctest::Approx(r == c ? 1.f : 0.f).epsilon(errtol));
316 for (
int i = 0; i < 16; ++i) {
317 CHECK(
R[i] == doctest::Approx(T[i]).epsilon(errtol));
320 float v[3] = {4.f, 5.f, 6.f}, out[3] = {};
322 CHECK(out[0] == doctest::Approx(4.f).epsilon(errtol));
323 CHECK(out[1] == doctest::Approx(5.f).epsilon(errtol));
324 CHECK(out[2] == doctest::Approx(6.f).epsilon(errtol));
327 SUBCASE(
"rotatePoint & rotatePointAboutLine") {
331 CHECK(r.x == doctest::Approx(0.f).epsilon(errtol));
332 CHECK(r.y == doctest::Approx(1.f).epsilon(errtol));
336 CHECK(q.x == doctest::Approx(0.f).epsilon(errtol));
337 CHECK(q.y == doctest::Approx(0.f).epsilon(errtol));
341TEST_CASE(
"String, File Path, and Parsing Utilities") {
342 SUBCASE(
"String Manipulation") {
343 DOCTEST_CHECK(
deblank(
" hello world ") ==
"helloworld");
346 SUBCASE(
"String Delimiting") {
348 DOCTEST_CHECK(result.size() == 3);
349 if (result.size() == 3) {
350 DOCTEST_CHECK(result[0] ==
"a");
351 DOCTEST_CHECK(result[1] ==
"b");
352 DOCTEST_CHECK(result[2] ==
"c");
355 SUBCASE(
"separate_string_by_delimiter edge cases") {
356 std::vector<std::string> result;
358 DOCTEST_CHECK(result.size() == 1);
359 DOCTEST_CHECK(result[0] ==
"a,b,c");
362 DOCTEST_CHECK(result.size() == 3);
363 DOCTEST_CHECK(result[0] ==
"a");
364 DOCTEST_CHECK(result[1] ==
"b");
365 DOCTEST_CHECK(result[2] ==
"c");
368 DOCTEST_CHECK(result.size() == 1);
370 capture_cerr cerr_buffer;
374 DOCTEST_CHECK(result.size() == 2);
375 if (result.size() == 2) {
376 DOCTEST_CHECK(result[0] ==
"");
377 DOCTEST_CHECK(result[1] ==
"");
380 SUBCASE(
"String to Vector Conversions") {
381 DOCTEST_CHECK(
string2vec2(
"1.5 2.5") == vec2(1.5f, 2.5f));
382 DOCTEST_CHECK(
string2vec3(
"1.5 2.5 3.5") == vec3(1.5f, 2.5f, 3.5f));
383 DOCTEST_CHECK(
string2vec4(
"1.5 2.5 3.5 4.5") == vec4(1.5f, 2.5f, 3.5f, 4.5f));
385 DOCTEST_CHECK(
string2int3(
"1 2 3") == int3(1, 2, 3));
386 DOCTEST_CHECK(
string2int4(
"1 2 3 4") == int4(1, 2, 3, 4));
388 SUBCASE(
"string to vector conversions with invalid input") {
389 capture_cerr cerr_buffer;
396 DOCTEST_CHECK_THROWS(result_vec2 =
string2vec2(
"1.5"));
397 DOCTEST_CHECK_THROWS(result_vec3 =
string2vec3(
"1.5 2.5"));
398 DOCTEST_CHECK_THROWS(result_vec4 =
string2vec4(
"1.5 2.5 3.5"));
399 DOCTEST_CHECK_THROWS(result_int2 =
string2int2(
"1"));
400 DOCTEST_CHECK_THROWS(result_int3 =
string2int3(
"1 2"));
401 DOCTEST_CHECK_THROWS(result_int4 =
string2int4(
"1 2 3"));
402 DOCTEST_CHECK_THROWS(result_vec2 =
string2vec2(
"1.5 abc"));
405 SUBCASE(
"String to Color Conversion") {
407 DOCTEST_CHECK(color.r == doctest::Approx(0.1f));
408 DOCTEST_CHECK(color.g == doctest::Approx(0.2f));
409 DOCTEST_CHECK(color.b == doctest::Approx(0.3f));
410 DOCTEST_CHECK(color.a == doctest::Approx(0.4f));
413 DOCTEST_CHECK(color.r == doctest::Approx(0.5f));
414 DOCTEST_CHECK(color.g == doctest::Approx(0.6f));
415 DOCTEST_CHECK(color.b == doctest::Approx(0.7f));
416 DOCTEST_CHECK(color.a == doctest::Approx(1.0f));
418 SUBCASE(
"string2RGBcolor with invalid input") {
419 capture_cerr cerr_buffer;
425 SUBCASE(
"File Path Parsing") {
426 std::string filepath =
"/path/to/file/filename.ext";
428 DOCTEST_CHECK(
getFileName(filepath) ==
"filename.ext");
429 DOCTEST_CHECK(
getFileStem(filepath) ==
"filename");
431 DOCTEST_CHECK(
getFilePath(filepath,
true) ==
"/path/to/file/");
432 DOCTEST_CHECK(
getFilePath(filepath,
false) ==
"/path/to/file");
435 std::string filepath_noext =
"/path/to/file/filename";
437 DOCTEST_CHECK(
getFileName(filepath_noext) ==
"filename");
438 DOCTEST_CHECK(
getFileStem(filepath_noext) ==
"filename");
440 std::string filepath_nodir =
"filename.ext";
442 DOCTEST_CHECK(
getFileName(filepath_nodir) ==
"filename.ext");
443 DOCTEST_CHECK(
getFileStem(filepath_nodir) ==
"filename");
444 DOCTEST_CHECK(
getFilePath(filepath_nodir,
true).empty());
446 SUBCASE(
"File path parsing edge cases") {
448 DOCTEST_CHECK(
getFileName(
".bashrc") ==
".bashrc");
449 DOCTEST_CHECK(
getFileStem(
".bashrc") ==
".bashrc");
451 DOCTEST_CHECK(
getFilePath(
"/path/to/file/",
true) ==
"/path/to/file/");
452 DOCTEST_CHECK(
getFilePath(
"/path/to/file/",
false) ==
"/path/to/file");
458 SUBCASE(
"Primitive Type Parsing") {
461 DOCTEST_CHECK(f == doctest::Approx(1.23f));
466 DOCTEST_CHECK(d == doctest::Approx(1.23));
471 DOCTEST_CHECK(i == 123);
477 DOCTEST_CHECK(u == 123u);
480 SUBCASE(
"Compound Type Parsing") {
483 DOCTEST_CHECK(i2 == int2(1, 2));
488 DOCTEST_CHECK(i3 == int3(1, 2, 3));
493 DOCTEST_CHECK(v2.x == doctest::Approx(1.1f));
494 DOCTEST_CHECK(v2.y == doctest::Approx(2.2f));
499 DOCTEST_CHECK(v3.x == doctest::Approx(1.1f));
504 DOCTEST_CHECK(rgb.r == doctest::Approx(0.1f));
507 SUBCASE(
"parse functions with whitespace") {
510 DOCTEST_CHECK(i == 123);
513 DOCTEST_CHECK(f == doctest::Approx(1.23f));
515 SUBCASE(
"parse_* invalid input") {
516 capture_cerr cerr_buffer;
532 DOCTEST_CHECK(!
parse_vec3(
"1.1 2.2 abc", v3));
539TEST_CASE(
"Vector Statistics and Manipulation") {
540 SUBCASE(
"Vector Statistics") {
541 std::vector<float> v = {1.f, 2.f, 3.f, 4.f, 5.f};
542 DOCTEST_CHECK(
sum(v) == doctest::Approx(15.f));
543 DOCTEST_CHECK(
mean(v) == doctest::Approx(3.f));
544 DOCTEST_CHECK(
min(v) == doctest::Approx(1.f));
545 DOCTEST_CHECK(
max(v) == doctest::Approx(5.f));
546 DOCTEST_CHECK(
stdev(v) == doctest::Approx(sqrtf(2.f)));
547 DOCTEST_CHECK(
median(v) == doctest::Approx(3.f));
548 std::vector<float> v2 = {1.f, 2.f, 3.f, 4.f};
549 DOCTEST_CHECK(
median(v2) == doctest::Approx(2.5f));
551 SUBCASE(
"sum / mean / min / max / stdev / median") {
552 std::vector<float> vf{1.f, 2.f, 3.f, 4.f, 5.f};
553 CHECK(
sum(vf) == doctest::Approx(15.f).epsilon(errtol));
554 CHECK(
mean(vf) == doctest::Approx(3.f).epsilon(errtol));
555 CHECK(
min(vf) == doctest::Approx(1.f).epsilon(errtol));
556 CHECK(
max(vf) == doctest::Approx(5.f).epsilon(errtol));
557 CHECK(
median(vf) == doctest::Approx(3.f).epsilon(errtol));
558 CHECK(
stdev(vf) == doctest::Approx(std::sqrt(2.f)).epsilon(1e-4));
560 std::vector<int> vi{9, 4, -3, 10, 2};
561 CHECK(
min(vi) == -3);
562 CHECK(
max(vi) == 10);
568 SUBCASE(
"Vector statistics with single element") {
569 std::vector<float> v = {5.f};
570 DOCTEST_CHECK(
stdev(v) == doctest::Approx(0.f));
572 SUBCASE(
"Vector Manipulation") {
573 SUBCASE(
"resize_vector") {
574 std::vector<std::vector<int>> vec2d;
576 DOCTEST_CHECK(vec2d.size() == 3);
577 DOCTEST_CHECK(vec2d[0].size() == 2);
579 std::vector<std::vector<std::vector<int>>> vec3d;
581 DOCTEST_CHECK(vec3d.size() == 4);
582 DOCTEST_CHECK(vec3d[0].size() == 3);
583 DOCTEST_CHECK(vec3d[0][0].size() == 2);
585 std::vector<std::vector<std::vector<std::vector<int>>>> vec4d;
587 DOCTEST_CHECK(vec4d.size() == 5);
588 DOCTEST_CHECK(vec4d[0].size() == 4);
589 DOCTEST_CHECK(vec4d[0][0].size() == 3);
590 DOCTEST_CHECK(vec4d[0][0][0].size() == 2);
594 std::vector<std::vector<int>> vec2d = {{1, 2}, {3, 4}};
595 std::vector<int> flat =
flatten(vec2d);
596 DOCTEST_CHECK(flat.size() == 4);
597 DOCTEST_CHECK(flat[3] == 4);
599 std::vector<std::vector<std::vector<int>>> vec3d = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}};
601 DOCTEST_CHECK(flat.size() == 8);
602 DOCTEST_CHECK(flat[7] == 8);
604 std::vector<std::vector<std::vector<std::vector<int>>>> vec4d = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}};
606 DOCTEST_CHECK(flat.size() == 16);
607 DOCTEST_CHECK(flat[15] == 16);
610 SUBCASE(
"vector operators") {
611 std::vector<float> v1 = {1, 2, 3};
612 std::vector<float> v2 = {4, 5, 6};
613 std::vector<float> v_sum = v1 + v2;
614 DOCTEST_CHECK(v_sum[0] == 5.f);
615 DOCTEST_CHECK(v_sum[2] == 9.f);
618 DOCTEST_CHECK(v1[0] == 5.f);
619 DOCTEST_CHECK(v1[2] == 9.f);
621 std::vector<float> v3 = {1};
622 DOCTEST_CHECK_THROWS(v1 + v3);
627TEST_CASE(
"Miscellaneous Utilities") {
628 SUBCASE(
"1D Interpolation") {
629 std::vector<vec2> points = {{0, 0}, {1, 1}, {2, 0}};
630 float res =
interp1(points, 0.5f);
631 DOCTEST_CHECK(res == doctest::Approx(0.5f));
633 DOCTEST_CHECK(res == doctest::Approx(0.5f));
635 DOCTEST_CHECK(res == doctest::Approx(0.f));
637 DOCTEST_CHECK(res == doctest::Approx(0.f));
639 SUBCASE(
"interp1 edge cases") {
640 capture_cerr cerr_buffer;
642 std::vector<vec2> empty_points;
643 DOCTEST_CHECK_THROWS(result =
interp1(empty_points, 0.5f));
645 std::vector<vec2> single_point = {{1, 5}};
646 result =
interp1(single_point, 0.5f);
647 DOCTEST_CHECK(result == doctest::Approx(5.f));
648 result =
interp1(single_point, 2.f);
649 DOCTEST_CHECK(result == doctest::Approx(5.f));
655 double elapsed = t.toc(
"mute");
656 DOCTEST_CHECK(elapsed >= 0);
658 SUBCASE(
"Custom Error") {
659 capture_cerr cerr_buffer;
662 SUBCASE(
"Random Number Generation") {
664 DOCTEST_CHECK(r >= 0.f);
665 DOCTEST_CHECK(r <= 1.f);
667 int ri =
randu(1, 10);
668 DOCTEST_CHECK(ri >= 1);
669 DOCTEST_CHECK(ri <= 10);
671 SUBCASE(
"randu(int, int)") {
673 DOCTEST_CHECK(r == 5);
674 for (
int i = 0; i < 100; ++i) {
676 DOCTEST_CHECK(r >= 1);
677 DOCTEST_CHECK(r <= 100);
680 SUBCASE(
"acos_safe & asin_safe clamp inputs") {
681 CHECK(
acos_safe(1.5f) == doctest::Approx(0.f).epsilon(errtol));
682 CHECK(
asin_safe(-2.f) == doctest::Approx(-0.5f *
PI_F).epsilon(errtol));
685 SUBCASE(
"randu range checks") {
686 for (
int i = 0; i < 100; ++i) {
691 int ri =
randu(5, 10);
694 CHECK(
randu(3, 3) == 3);
699static float quadratic(
float x, std::vector<float> &,
const void *) {
702static float linear(
float x, std::vector<float> &,
const void *) {
705static float flat(
float, std::vector<float> &,
const void *) {
708static float cubic(
float x, std::vector<float> &,
const void *) {
709 return (x - 1.0f) * (x + 2.0f) * (x - 4.0f);
711static float near_singular(
float x, std::vector<float> &,
const void *) {
712 return (x - 1e-3f) * (x - 1e-3f);
715 SUBCASE(
"fzero finds positive quadratic root") {
716 std::vector<float> v;
717 float root =
helios::fzero(quadratic, v,
nullptr, 1.0f, 1e-5f, 50);
718 DOCTEST_CHECK(root == doctest::Approx(2.0f).epsilon(errtol));
721 SUBCASE(
"fzero finds root far from initial guess") {
722 std::vector<float> v;
723 float root =
helios::fzero(linear, v,
nullptr, -10.0f, 1e-6f, 50);
724 DOCTEST_CHECK(root == doctest::Approx(3.5f).epsilon(errtol));
727 SUBCASE(
"fzero handles function without zero") {
728 std::vector<float> v;
729 capture_cerr cerr_buffer;
730 float root =
helios::fzero(flat, v,
nullptr, 0.0f, 1e-6f, 10);
731 DOCTEST_CHECK(std::isfinite(root));
732 DOCTEST_CHECK(cerr_buffer.has_output());
735 SUBCASE(
"fzero returns exact root at initial guess") {
736 std::vector<float> v;
737 float root =
helios::fzero(quadratic, v,
nullptr, 2.0f, 1e-6f, 5);
738 DOCTEST_CHECK(root == doctest::Approx(2.0f).epsilon(errtol));
741 SUBCASE(
"fzero finds a cubic root") {
742 std::vector<float> v;
743 float root =
helios::fzero(cubic, v,
nullptr, 3.5f, 1e-5f, 80);
744 DOCTEST_CHECK(root == doctest::Approx(4.0f).epsilon(errtol));
747 SUBCASE(
"fzero copes with near-singular derivative") {
748 std::vector<float> v;
749 float root =
helios::fzero(near_singular, v,
nullptr, 0.01f, 1e-4f, 50);
750 DOCTEST_CHECK(std::fabs(near_singular(root, v,
nullptr)) < 1e-4f);
754TEST_CASE(
"linspace - Linearly Spaced Values") {
755 SUBCASE(
"linspace float basic functionality") {
756 std::vector<float> result =
linspace(0.f, 10.f, 11);
757 DOCTEST_CHECK(result.size() == 11);
758 DOCTEST_CHECK(result[0] == doctest::Approx(0.f));
759 DOCTEST_CHECK(result[5] == doctest::Approx(5.f));
760 DOCTEST_CHECK(result[10] == doctest::Approx(10.f));
763 for (
size_t i = 1; i < result.size(); ++i) {
764 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(1.f));
768 SUBCASE(
"linspace float with negative range") {
769 std::vector<float> result =
linspace(-5.f, 5.f, 6);
770 DOCTEST_CHECK(result.size() == 6);
771 DOCTEST_CHECK(result[0] == doctest::Approx(-5.f));
772 DOCTEST_CHECK(result[2] == doctest::Approx(-1.f));
773 DOCTEST_CHECK(result[5] == doctest::Approx(5.f));
776 for (
size_t i = 1; i < result.size(); ++i) {
777 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(2.f));
781 SUBCASE(
"linspace float single point") {
782 std::vector<float> result =
linspace(5.f, 10.f, 1);
783 DOCTEST_CHECK(result.size() == 1);
784 DOCTEST_CHECK(result[0] == doctest::Approx(5.f));
787 SUBCASE(
"linspace float two points") {
788 std::vector<float> result =
linspace(1.f, 3.f, 2);
789 DOCTEST_CHECK(result.size() == 2);
790 DOCTEST_CHECK(result[0] == doctest::Approx(1.f));
791 DOCTEST_CHECK(result[1] == doctest::Approx(3.f));
794 SUBCASE(
"linspace float reversed range") {
795 std::vector<float> result =
linspace(10.f, 0.f, 6);
796 DOCTEST_CHECK(result.size() == 6);
797 DOCTEST_CHECK(result[0] == doctest::Approx(10.f));
798 DOCTEST_CHECK(result[2] == doctest::Approx(6.f));
799 DOCTEST_CHECK(result[5] == doctest::Approx(0.f));
802 for (
size_t i = 1; i < result.size(); ++i) {
803 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(-2.f));
807 SUBCASE(
"linspace float error handling") {
808 capture_cerr cerr_buffer;
809 std::vector<float> result;
810 DOCTEST_CHECK_THROWS(result =
linspace(0.f, 1.f, 0));
811 DOCTEST_CHECK_THROWS(result =
linspace(0.f, 1.f, -1));
814 SUBCASE(
"linspace vec2 basic functionality") {
815 vec2 start(0.f, 1.f);
817 std::vector<vec2> result =
linspace(start, end, 5);
819 DOCTEST_CHECK(result.size() == 5);
820 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
821 DOCTEST_CHECK(result[0].y == doctest::Approx(1.f));
822 DOCTEST_CHECK(result[2].x == doctest::Approx(2.f));
823 DOCTEST_CHECK(result[2].y == doctest::Approx(3.f));
824 DOCTEST_CHECK(result[4].x == doctest::Approx(4.f));
825 DOCTEST_CHECK(result[4].y == doctest::Approx(5.f));
828 SUBCASE(
"linspace vec2 single point") {
829 vec2 start(1.f, 2.f);
831 std::vector<vec2> result =
linspace(start, end, 1);
833 DOCTEST_CHECK(result.size() == 1);
834 DOCTEST_CHECK(result[0].x == doctest::Approx(start.x));
835 DOCTEST_CHECK(result[0].y == doctest::Approx(start.y));
838 SUBCASE(
"linspace vec2 error handling") {
839 capture_cerr cerr_buffer;
840 std::vector<vec2> result;
841 vec2 start(0.f, 0.f);
843 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
844 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -5));
847 SUBCASE(
"linspace vec3 basic functionality") {
848 vec3 start(0.f, 0.f, 0.f);
849 vec3 end(3.f, 6.f, 9.f);
850 std::vector<vec3> result =
linspace(start, end, 4);
852 DOCTEST_CHECK(result.size() == 4);
853 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
854 DOCTEST_CHECK(result[0].y == doctest::Approx(0.f));
855 DOCTEST_CHECK(result[0].z == doctest::Approx(0.f));
856 DOCTEST_CHECK(result[1].x == doctest::Approx(1.f));
857 DOCTEST_CHECK(result[1].y == doctest::Approx(2.f));
858 DOCTEST_CHECK(result[1].z == doctest::Approx(3.f));
859 DOCTEST_CHECK(result[3].x == doctest::Approx(3.f));
860 DOCTEST_CHECK(result[3].y == doctest::Approx(6.f));
861 DOCTEST_CHECK(result[3].z == doctest::Approx(9.f));
864 SUBCASE(
"linspace vec3 with mixed positive/negative components") {
865 vec3 start(-1.f, 2.f, -3.f);
866 vec3 end(1.f, -2.f, 3.f);
867 std::vector<vec3> result =
linspace(start, end, 3);
869 DOCTEST_CHECK(result.size() == 3);
870 DOCTEST_CHECK(result[0].x == doctest::Approx(-1.f));
871 DOCTEST_CHECK(result[0].y == doctest::Approx(2.f));
872 DOCTEST_CHECK(result[0].z == doctest::Approx(-3.f));
873 DOCTEST_CHECK(result[1].x == doctest::Approx(0.f));
874 DOCTEST_CHECK(result[1].y == doctest::Approx(0.f));
875 DOCTEST_CHECK(result[1].z == doctest::Approx(0.f));
876 DOCTEST_CHECK(result[2].x == doctest::Approx(1.f));
877 DOCTEST_CHECK(result[2].y == doctest::Approx(-2.f));
878 DOCTEST_CHECK(result[2].z == doctest::Approx(3.f));
881 SUBCASE(
"linspace vec3 error handling") {
882 capture_cerr cerr_buffer;
883 std::vector<vec3> result;
884 vec3 start(0.f, 0.f, 0.f);
885 vec3 end(1.f, 1.f, 1.f);
886 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
887 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -10));
890 SUBCASE(
"linspace vec4 basic functionality") {
891 vec4 start(0.f, 1.f, 2.f, 3.f);
892 vec4 end(4.f, 9.f, 14.f, 19.f);
893 std::vector<vec4> result =
linspace(start, end, 5);
895 DOCTEST_CHECK(result.size() == 5);
896 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
897 DOCTEST_CHECK(result[0].y == doctest::Approx(1.f));
898 DOCTEST_CHECK(result[0].z == doctest::Approx(2.f));
899 DOCTEST_CHECK(result[0].w == doctest::Approx(3.f));
900 DOCTEST_CHECK(result[2].x == doctest::Approx(2.f));
901 DOCTEST_CHECK(result[2].y == doctest::Approx(5.f));
902 DOCTEST_CHECK(result[2].z == doctest::Approx(8.f));
903 DOCTEST_CHECK(result[2].w == doctest::Approx(11.f));
904 DOCTEST_CHECK(result[4].x == doctest::Approx(4.f));
905 DOCTEST_CHECK(result[4].y == doctest::Approx(9.f));
906 DOCTEST_CHECK(result[4].z == doctest::Approx(14.f));
907 DOCTEST_CHECK(result[4].w == doctest::Approx(19.f));
910 SUBCASE(
"linspace vec4 two points") {
911 vec4 start(1.f, 2.f, 3.f, 4.f);
912 vec4 end(5.f, 6.f, 7.f, 8.f);
913 std::vector<vec4> result =
linspace(start, end, 2);
915 DOCTEST_CHECK(result.size() == 2);
916 DOCTEST_CHECK(result[0] == start);
917 DOCTEST_CHECK(result[1] == end);
920 SUBCASE(
"linspace vec4 error handling") {
921 capture_cerr cerr_buffer;
922 std::vector<vec4> result;
923 vec4 start(0.f, 0.f, 0.f, 0.f);
924 vec4 end(1.f, 1.f, 1.f, 1.f);
925 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
926 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -1));
929 SUBCASE(
"linspace precision and endpoint accuracy") {
931 std::vector<float> result =
linspace(0.1f, 0.9f, 9);
932 DOCTEST_CHECK(result[0] == doctest::Approx(0.1f));
933 DOCTEST_CHECK(result[8] == doctest::Approx(0.9f));
936 result =
linspace(1000000.f, 2000000.f, 11);
937 DOCTEST_CHECK(result[0] == doctest::Approx(1000000.f));
938 DOCTEST_CHECK(result[10] == doctest::Approx(2000000.f));
942 DOCTEST_CHECK(result[0] == doctest::Approx(1e-6f));
943 DOCTEST_CHECK(result[2] == doctest::Approx(2e-6f));
946 SUBCASE(
"linspace zero-length intervals") {
948 std::vector<float> result =
linspace(5.f, 5.f, 5);
949 DOCTEST_CHECK(result.size() == 5);
950 for (
const auto &val: result) {
951 DOCTEST_CHECK(val == doctest::Approx(5.f));
955 vec3 point(1.f, 2.f, 3.f);
956 std::vector<vec3> vec_result =
linspace(point, point, 3);
957 DOCTEST_CHECK(vec_result.size() == 3);
958 for (
const auto &v: vec_result) {
959 DOCTEST_CHECK(v.x == doctest::Approx(point.x));
960 DOCTEST_CHECK(v.y == doctest::Approx(point.y));
961 DOCTEST_CHECK(v.z == doctest::Approx(point.z));
966TEST_CASE(
"Asset Resolution Functions") {
967 SUBCASE(
"resolveAssetPath basic functionality") {
969 bool exception_thrown =
false;
972 }
catch (
const std::runtime_error &) {
973 exception_thrown =
true;
975 DOCTEST_CHECK(exception_thrown);
978 SUBCASE(
"resolvePluginAsset") {
980 bool exception_thrown =
false;
982 [[maybe_unused]]
auto path =
resolvePluginAsset(
"visualizer",
"nonexistent_font.ttf");
983 }
catch (
const std::runtime_error &) {
984 exception_thrown =
true;
986 DOCTEST_CHECK(exception_thrown);
990 SUBCASE(
"resolveSpectraPath") {
992 bool exception_thrown =
false;
995 }
catch (
const std::runtime_error &) {
996 exception_thrown =
true;
998 DOCTEST_CHECK(exception_thrown);
1001 SUBCASE(
"validateAssetPath with valid path") {
1003 std::filesystem::path temp_path = std::filesystem::temp_directory_path() /
"helios_test_asset.txt";
1004 std::ofstream temp_file(temp_path);
1005 temp_file <<
"test content";
1012 std::filesystem::remove(temp_path);
1015 SUBCASE(
"validateAssetPath with invalid path") {
1017 std::filesystem::path non_existent =
"/this/path/does/not/exist.txt";
1021 SUBCASE(
"resolveAssetPath with empty string") {
1024 DOCTEST_CHECK(!path.empty());
1025 DOCTEST_CHECK(path.is_absolute());
1028 SUBCASE(
"error message content") {
1032 DOCTEST_FAIL(
"Expected exception was not thrown");
1033 }
catch (
const std::runtime_error &e) {
1034 std::string error_msg = e.what();
1035 DOCTEST_CHECK(error_msg.find(
"Could not locate asset file") != std::string::npos);
1036 DOCTEST_CHECK(error_msg.find(
"nonexistent_file.txt") != std::string::npos);
1040 SUBCASE(
"asset resolution consistency") {
1042 std::string error1, error2;
1045 }
catch (
const std::runtime_error &e) {
1050 }
catch (
const std::runtime_error &e) {
1053 DOCTEST_CHECK(error1 == error2);
1056 SUBCASE(
"different plugin error messages") {
1058 std::string vis_error, rad_error;
1061 }
catch (
const std::runtime_error &e) {
1062 vis_error = e.what();
1066 }
catch (
const std::runtime_error &e) {
1067 rad_error = e.what();
1070 DOCTEST_CHECK(vis_error != rad_error);
1071 DOCTEST_CHECK(vis_error.find(
"visualizer") != std::string::npos);
1072 DOCTEST_CHECK(rad_error.find(
"radiation") != std::string::npos);
1076TEST_CASE(
"Project-based File Resolution") {
1077 SUBCASE(
"findProjectRoot basic functionality") {
1079 std::filesystem::path cwd = std::filesystem::current_path();
1083 DOCTEST_CHECK(!project_root.empty());
1086 auto cmake_file = project_root /
"CMakeLists.txt";
1087 DOCTEST_CHECK(std::filesystem::exists(cmake_file));
1090 SUBCASE(
"findProjectRoot with non-existent path") {
1092 std::filesystem::path fake_path =
"/this/path/does/not/exist";
1096 DOCTEST_CHECK(project_root.empty());
1099 SUBCASE(
"findProjectRoot from root directory") {
1101 std::filesystem::path root_path =
"/";
1105 DOCTEST_CHECK(project_root.empty());
1108 SUBCASE(
"resolveProjectFile with existing file in cwd") {
1110 std::string test_filename =
"test_project_resolve.tmp";
1111 std::ofstream test_file(test_filename);
1112 test_file <<
"test content";
1118 DOCTEST_CHECK(!resolved_path.empty());
1119 DOCTEST_CHECK(std::filesystem::exists(resolved_path));
1122 std::filesystem::remove(test_filename);
1127 std::filesystem::remove(test_filename);
1130 SUBCASE(
"resolveProjectFile with non-existent file") {
1132 std::string fake_filename =
"this_file_does_not_exist_anywhere.tmp";
1134 std::string error_message;
1137 }
catch (
const std::runtime_error &e) {
1138 error_message = e.what();
1142 DOCTEST_CHECK(!error_message.empty());
1143 DOCTEST_CHECK(error_message.find(
"Could not locate file") != std::string::npos);
1144 DOCTEST_CHECK(error_message.find(fake_filename) != std::string::npos);
1147 SUBCASE(
"resolveProjectFile with empty filename") {
1149 std::string error_message;
1152 }
catch (
const std::runtime_error &e) {
1153 error_message = e.what();
1157 DOCTEST_CHECK(!error_message.empty());
1160 SUBCASE(
"resolveProjectFile project directory fallback") {
1164 if (!project_root.empty()) {
1165 std::string test_filename =
"test_project_fallback.tmp";
1166 auto test_file_path = project_root / test_filename;
1169 std::ofstream test_file(test_file_path);
1170 test_file <<
"fallback test content";
1176 DOCTEST_CHECK(!resolved_path.empty());
1177 DOCTEST_CHECK(std::filesystem::exists(resolved_path));
1178 DOCTEST_CHECK(resolved_path == test_file_path);
1181 std::filesystem::remove(test_file_path);
1186 std::filesystem::remove(test_file_path);