8TEST_CASE(
"Core Context State and Configuration") {
9 SUBCASE(
"Constructor and basic setup") {
11 DOCTEST_CHECK(ctx.getPrimitiveCount() == 0);
12 DOCTEST_CHECK(ctx.getObjectCount() == 0);
13 DOCTEST_CHECK(!ctx.isGeometryDirty());
15 Date d = ctx.getDate();
16 DOCTEST_CHECK(d.day == 1);
17 DOCTEST_CHECK(d.month == 6);
18 DOCTEST_CHECK(d.year == 2000);
20 Time t = ctx.getTime();
21 DOCTEST_CHECK(t.hour == 12);
22 DOCTEST_CHECK(t.minute == 0);
23 DOCTEST_CHECK(t.second == 0);
25 Location l = ctx.getLocation();
26 DOCTEST_CHECK(l.latitude_deg == doctest::Approx(38.55));
27 DOCTEST_CHECK(l.longitude_deg == doctest::Approx(121.76));
28 DOCTEST_CHECK(l.UTC_offset == doctest::Approx(8));
31 SUBCASE(
"Random number generator") {
33 ctx.seedRandomGenerator(12345);
34 std::minstd_rand0 *gen1 = ctx.getRandomGenerator();
35 float rand1 = (*gen1)();
37 ctx.seedRandomGenerator(12345);
38 std::minstd_rand0 *gen2 = ctx.getRandomGenerator();
39 float rand2 = (*gen2)();
41 DOCTEST_CHECK(rand1 == rand2);
43 float r_uniform = ctx.randu();
44 DOCTEST_CHECK(r_uniform >= 0.f);
45 DOCTEST_CHECK(r_uniform <= 1.f);
47 float r_norm = ctx.randn();
49 DOCTEST_CHECK(!std::isnan(r_norm));
52 SUBCASE(
"Random number ranges") {
54 ctx.seedRandomGenerator(6789);
55 float r = ctx.randu(-1.f, 1.f);
56 DOCTEST_CHECK(r >= -1.f);
57 DOCTEST_CHECK(r <= 1.f);
58 int ri = ctx.randu(0, 5);
59 DOCTEST_CHECK(ri >= 0);
60 DOCTEST_CHECK(ri <= 5);
61 float rn = ctx.randn(2.f, 0.5f);
62 DOCTEST_CHECK(!std::isnan(rn));
65 SUBCASE(
"Texture utility methods") {
68 capture_cerr cerr_buffer;
69 DOCTEST_CHECK_NOTHROW(ctx.addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1), nullrotation,
"lib/images/solid.jpg"));
70 DOCTEST_CHECK_THROWS(ctx.addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1), nullrotation,
"lib/images/missing.png"));
71 DOCTEST_CHECK_THROWS(ctx.addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1), nullrotation,
"lib/images/invalid.txt"));
74 Texture tex(
"lib/images/solid.jpg");
75 DOCTEST_CHECK(tex.getTextureFile() ==
"lib/images/solid.jpg");
76 int2 res = tex.getImageResolution();
77 DOCTEST_CHECK(res.x == 5);
78 DOCTEST_CHECK(res.y == 5);
79 DOCTEST_CHECK(!tex.hasTransparencyChannel());
80 const auto *alpha = tex.getTransparencyData();
81 DOCTEST_CHECK(alpha->empty());
82 std::vector<vec2> uv{{0.f, 0.f}, {1.f, 0.f}, {1.f, 1.f}};
83 float sf = tex.getSolidFraction(uv);
84 DOCTEST_CHECK(sf == doctest::Approx(1.f));
87 SUBCASE(
"Geometry dirty flags") {
89 uint p = ctx.addPatch();
90 DOCTEST_CHECK(ctx.isGeometryDirty());
91 DOCTEST_CHECK(ctx.isPrimitiveDirty(p));
93 ctx.markGeometryClean();
94 DOCTEST_CHECK(!ctx.isGeometryDirty());
95 DOCTEST_CHECK(!ctx.isPrimitiveDirty(p));
97 ctx.markPrimitiveDirty(p);
98 DOCTEST_CHECK(ctx.isGeometryDirty());
99 DOCTEST_CHECK(ctx.isPrimitiveDirty(p));
101 ctx.markPrimitiveClean(p);
102 DOCTEST_CHECK(!ctx.isGeometryDirty());
103 DOCTEST_CHECK(!ctx.isPrimitiveDirty(p));
105 ctx.markGeometryDirty();
106 DOCTEST_CHECK(ctx.isGeometryDirty());
109 SUBCASE(
"Geometry dirty flags vector") {
111 std::vector<uint> ids{ctx.addPatch(), ctx.addPatch()};
112 ctx.markGeometryClean();
113 ctx.markPrimitiveDirty(ids);
115 DOCTEST_CHECK(ctx.isPrimitiveDirty(
id));
117 ctx.markPrimitiveClean(ids);
119 DOCTEST_CHECK(!ctx.isPrimitiveDirty(
id));
123 ctx.translatePrimitive(ids, shift);
125 vec3 c = ctx.getPatchCenter(
id);
126 DOCTEST_CHECK(c.x == doctest::Approx(shift.x).epsilon(errtol));
130 SUBCASE(
"Date and Time Manipulation") {
132 ctx.setDate(15, 7, 2025);
133 Date d = ctx.getDate();
134 DOCTEST_CHECK(d.day == 15);
135 DOCTEST_CHECK(d.month == 7);
136 DOCTEST_CHECK(d.year == 2025);
137 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"JUL") == 0);
138 DOCTEST_CHECK(ctx.getJulianDate() == 196);
140 ctx.setTime(45, 30, 10);
141 Time t = ctx.getTime();
142 DOCTEST_CHECK(t.hour == 10);
143 DOCTEST_CHECK(t.minute == 30);
144 DOCTEST_CHECK(t.second == 45);
146 capture_cerr cerr_buffer;
147 DOCTEST_CHECK_THROWS(ctx.setDate(32, 1, 2025));
148 DOCTEST_CHECK_THROWS(ctx.setTime(60, 0, 0));
151 SUBCASE(
"Location Manipulation") {
153 Location loc = {40.7128, -74.0060, 10.0};
154 ctx.setLocation(loc);
155 Location l = ctx.getLocation();
156 DOCTEST_CHECK(l.latitude_deg == doctest::Approx(40.7128));
157 DOCTEST_CHECK(l.longitude_deg == doctest::Approx(-74.0060));
158 DOCTEST_CHECK(l.UTC_offset == doctest::Approx(10.0));
161 SUBCASE(
"primitive orientation and transforms") {
164 ctx.markGeometryClean();
166 vec3 n = ctx.getPrimitiveNormal(
id);
167 DOCTEST_CHECK(n == vec3(0.f, 0.f, 1.f));
169 ctx.setPrimitiveElevation(
id,
make_vec3(0, 0, 0), 0.f);
170 n = ctx.getPrimitiveNormal(
id);
171 DOCTEST_CHECK(n.x == doctest::Approx(0.f).epsilon(errtol));
172 DOCTEST_CHECK(n.y == doctest::Approx(1.f).epsilon(errtol));
173 DOCTEST_CHECK(n.z == doctest::Approx(0.f).epsilon(errtol));
175 ctx.setPrimitiveAzimuth(
id,
make_vec3(0, 0, 0), 0.5f *
PI_F);
176 n = ctx.getPrimitiveNormal(
id);
177 DOCTEST_CHECK(n.x == doctest::Approx(1.f).epsilon(errtol));
178 DOCTEST_CHECK(n.y == doctest::Approx(0.f).epsilon(errtol));
181 n = ctx.getPrimitiveNormal(
id);
182 DOCTEST_CHECK(n.z == doctest::Approx(1.f).epsilon(errtol));
186 ctx.setPrimitiveTransformationMatrix(
id, M);
188 ctx.getPrimitiveTransformationMatrix(
id, out);
189 for (
int i = 0; i < 16; ++i) {
190 DOCTEST_CHECK(out[i] == doctest::Approx(M[i]));
192 DOCTEST_CHECK(ctx.isPrimitiveDirty(
id));
196TEST_CASE(
"Primitive Management: Creation, Properties, and Operations") {
197 SUBCASE(
"addPatch") {
198 vec3 center, center_r;
200 std::vector<vec3> vertices, vertices_r;
201 SphericalCoord rotation, rotation_r;
202 vec3 normal, normal_r;
203 RGBcolor color, color_r;
205 std::vector<uint> UUIDs;
210 Context context_test;
216 vertices.at(0) = center +
make_vec3(-0.5f * size.x, -0.5f * size.y, 0.f);
217 vertices.at(1) = center +
make_vec3(0.5f * size.x, -0.5f * size.y, 0.f);
218 vertices.at(2) = center +
make_vec3(0.5f * size.x, 0.5f * size.y, 0.f);
219 vertices.at(3) = center +
make_vec3(-0.5f * size.x, 0.5f * size.y, 0.f);
221 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
222 DOCTEST_CHECK_NOTHROW(type = context_test.getPrimitiveType(UUID));
223 DOCTEST_CHECK_NOTHROW(center_r = context_test.getPatchCenter(UUID));
224 DOCTEST_CHECK_NOTHROW(size_r = context_test.getPatchSize(UUID));
225 DOCTEST_CHECK_NOTHROW(normal_r = context_test.getPrimitiveNormal(UUID));
226 DOCTEST_CHECK_NOTHROW(vertices_r = context_test.getPrimitiveVertices(UUID));
227 DOCTEST_CHECK_NOTHROW(area_r = context_test.getPrimitiveArea(UUID));
228 DOCTEST_CHECK_NOTHROW(color_r = context_test.getPrimitiveColor(UUID));
230 DOCTEST_CHECK(type == PRIMITIVE_TYPE_PATCH);
231 DOCTEST_CHECK(center_r.x == center.x);
232 DOCTEST_CHECK(center_r.y == center.y);
233 DOCTEST_CHECK(center_r.z == center.z);
234 DOCTEST_CHECK(size_r.x == size.x);
235 DOCTEST_CHECK(size_r.y == size.y);
236 DOCTEST_CHECK(normal_r.x == 0.f);
237 DOCTEST_CHECK(normal_r.y == 0.f);
238 DOCTEST_CHECK(normal_r.z == 1.f);
239 DOCTEST_CHECK(vertices_r.size() == 4);
240 DOCTEST_CHECK(vertices_r.at(0).x == vertices.at(0).x);
241 DOCTEST_CHECK(vertices_r.at(0).y == vertices.at(0).y);
242 DOCTEST_CHECK(vertices_r.at(0).z == vertices.at(0).z);
243 DOCTEST_CHECK(vertices_r.at(1).x == vertices.at(1).x);
244 DOCTEST_CHECK(vertices_r.at(1).y == vertices.at(1).y);
245 DOCTEST_CHECK(vertices_r.at(1).z == vertices.at(1).z);
246 DOCTEST_CHECK(vertices_r.at(2).x == vertices.at(2).x);
247 DOCTEST_CHECK(vertices_r.at(2).y == vertices.at(2).y);
248 DOCTEST_CHECK(vertices_r.at(2).z == vertices.at(2).z);
249 DOCTEST_CHECK(vertices_r.at(3).x == vertices.at(3).x);
250 DOCTEST_CHECK(vertices_r.at(3).y == vertices.at(3).y);
251 DOCTEST_CHECK(vertices_r.at(3).z == vertices.at(3).z);
252 CHECK(area_r == doctest::Approx(size.x * size.y).epsilon(errtol));
253 DOCTEST_CHECK(color_r.r == 0.f);
254 DOCTEST_CHECK(color_r.g == 0.f);
255 DOCTEST_CHECK(color_r.b == 0.f);
256 DOCTEST_CHECK(context_test.getPrimitiveTextureFile(UUID).empty());
258 SUBCASE(
"rotated patch") {
259 Context context_test;
264 rotation.azimuth = 0.5f *
PI_F;
267 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size, rotation));
270 DOCTEST_CHECK_NOTHROW(normal_r = context_test.getPrimitiveNormal(UUID));
272 SphericalCoord rotation_r;
273 DOCTEST_CHECK_NOTHROW(rotation_r =
make_SphericalCoord(0.5f *
PI_F - asinf(normal_r.z), atan2f(normal_r.x, normal_r.y)));
275 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
277 DOCTEST_CHECK(rotation_r.elevation == doctest::Approx(rotation.elevation).epsilon(errtol));
278 DOCTEST_CHECK(rotation_r.azimuth == doctest::Approx(rotation.azimuth).epsilon(errtol));
280 SUBCASE(
"addTriangle") {
281 Context context_test;
292 std::vector<vec3> vertices{v0, v1, v2};
293 RGBcolor color = RGB::red;
295 DOCTEST_CHECK_NOTHROW(UUID = context_test.addTriangle(v0, v1, v2, color));
296 DOCTEST_CHECK(context_test.getPrimitiveType(UUID) == PRIMITIVE_TYPE_TRIANGLE);
298 vec3 normal = normalize(
cross(v1 - v0, v2 - v1));
299 vec3 normal_r = context_test.getPrimitiveNormal(UUID);
300 DOCTEST_CHECK(normal_r.x == doctest::Approx(normal.x).epsilon(errtol));
301 DOCTEST_CHECK(normal_r.y == doctest::Approx(normal.y).epsilon(errtol));
302 DOCTEST_CHECK(normal_r.z == doctest::Approx(normal.z).epsilon(errtol));
304 std::vector<vec3> vertices_r;
305 DOCTEST_CHECK_NOTHROW(vertices_r = context_test.getPrimitiveVertices(UUID));
306 DOCTEST_CHECK(vertices_r.size() == 3);
307 DOCTEST_CHECK(vertices_r.at(0).x == v0.x);
308 DOCTEST_CHECK(vertices_r.at(0).y == v0.y);
309 DOCTEST_CHECK(vertices_r.at(0).z == v0.z);
312 DOCTEST_CHECK_NOTHROW(color_r = context_test.getPrimitiveColor(UUID));
313 DOCTEST_CHECK(color_r.r == color.r);
314 DOCTEST_CHECK(color_r.g == color.g);
315 DOCTEST_CHECK(color_r.b == color.b);
316 DOCTEST_CHECK(context_test.getPrimitiveTextureFile(UUID).empty());
318 float a = (v1 - v0).magnitude();
319 float b = (v2 - v0).magnitude();
320 float c = (v2 - v1).magnitude();
321 float s = 0.5f * (a + b + c);
322 float area = sqrtf(s * (s - a) * (s - b) * (s - c));
324 DOCTEST_CHECK_NOTHROW(area_r = context_test.getPrimitiveArea(UUID));
325 DOCTEST_CHECK(area_r == doctest::Approx(area).epsilon(errtol));
327 SUBCASE(
"copyPrimitive (patch)") {
328 Context context_test;
331 std::vector<float> cpdata{5.2f, 2.5f, 3.1f};
336 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
338 DOCTEST_CHECK_NOTHROW(context_test.setPrimitiveData(UUID,
"somedata", cpdata));
340 DOCTEST_CHECK_NOTHROW(UUID_cpy = context_test.copyPrimitive(UUID));
343 DOCTEST_CHECK_NOTHROW(center_cpy = context_test.getPatchCenter(UUID_cpy));
345 DOCTEST_CHECK_NOTHROW(size_cpy = context_test.getPatchSize(UUID_cpy));
347 DOCTEST_CHECK(UUID_cpy == 1);
348 DOCTEST_CHECK(center_cpy.x == center.x);
349 DOCTEST_CHECK(center_cpy.y == center.y);
350 DOCTEST_CHECK(center_cpy.z == center.z);
351 DOCTEST_CHECK(size_cpy.x == size.x);
352 DOCTEST_CHECK(size_cpy.y == size.y);
354 std::vector<float> cpdata_copy;
355 context_test.getPrimitiveData(UUID_cpy,
"somedata", cpdata_copy);
357 DOCTEST_CHECK(cpdata.size() == cpdata_copy.size());
358 for (
uint i = 0; i < cpdata.size(); i++) {
359 DOCTEST_CHECK(cpdata.at(i) == cpdata_copy.at(i));
364 DOCTEST_CHECK_NOTHROW(context_test.translatePrimitive(UUID_cpy, shift));
365 DOCTEST_CHECK_NOTHROW(center_cpy = context_test.getPatchCenter(UUID_cpy));
367 DOCTEST_CHECK_NOTHROW(center_r = context_test.getPatchCenter(UUID));
369 DOCTEST_CHECK(center_cpy.x == doctest::Approx(center.x + shift.x).epsilon(errtol));
370 DOCTEST_CHECK(center_cpy.y == doctest::Approx(center.y + shift.y).epsilon(errtol));
371 DOCTEST_CHECK(center_cpy.z == doctest::Approx(center.z + shift.z).epsilon(errtol));
372 DOCTEST_CHECK(center_r.x == center.x);
373 DOCTEST_CHECK(center_r.y == center.y);
374 DOCTEST_CHECK(center_r.z == center.z);
376 SUBCASE(
"copyPrimitive (triangle)") {
377 Context context_test;
384 DOCTEST_CHECK_NOTHROW(UUID = context_test.addTriangle(v0, v1, v2, RGB::blue));
385 DOCTEST_CHECK_NOTHROW(UUID_cpy = context_test.copyPrimitive(UUID));
387 std::vector<vec3> verts_org, verts_cpy;
388 DOCTEST_CHECK_NOTHROW(verts_org = context_test.getPrimitiveVertices(UUID));
389 DOCTEST_CHECK_NOTHROW(verts_cpy = context_test.getPrimitiveVertices(UUID_cpy));
390 DOCTEST_CHECK(verts_org == verts_cpy);
393 DOCTEST_CHECK_NOTHROW(context_test.translatePrimitive(UUID_cpy, shift));
394 DOCTEST_CHECK_NOTHROW(verts_cpy = context_test.getPrimitiveVertices(UUID_cpy));
395 DOCTEST_CHECK(verts_cpy.at(0) == verts_org.at(0) + shift);
396 DOCTEST_CHECK(verts_cpy.at(1) == verts_org.at(1) + shift);
397 DOCTEST_CHECK(verts_cpy.at(2) == verts_org.at(2) + shift);
399 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
400 DOCTEST_CHECK(!context_test.doesPrimitiveExist(UUID));
402 SUBCASE(
"deletePrimitive") {
403 Context context_test;
408 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
410 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
412 uint primitive_count;
413 DOCTEST_CHECK_NOTHROW(primitive_count = context_test.getPrimitiveCount(UUID));
414 DOCTEST_CHECK(primitive_count == 0);
415 DOCTEST_CHECK(!context_test.doesPrimitiveExist(UUID));
417 SUBCASE(
"primitive bounding box") {
418 Context context_test;
419 std::vector<uint> UUIDs;
424 DOCTEST_CHECK_NOTHROW(context_test.getPrimitiveBoundingBox(UUIDs, bmin, bmax));
425 DOCTEST_CHECK(bmin ==
make_vec3(-1.25f, -0.25f, 0.f));
426 DOCTEST_CHECK(bmax ==
make_vec3(1.25f, 0.25f, 0.f));
428 SUBCASE(
"primitive scale and data") {
429 Context context_test;
431 float area0 = sz_0.x * sz_0.y;
434 context_test.scalePrimitive(UUID,
make_vec3(scale, scale, scale));
435 float area1 = context_test.getPrimitiveArea(UUID);
436 DOCTEST_CHECK(area1 == doctest::Approx(scale * scale * area0).epsilon(1e-5));
439 context_test.setPrimitiveData(UUID,
"some_data", data);
440 DOCTEST_CHECK(context_test.doesPrimitiveDataExist(UUID,
"some_data"));
442 context_test.getPrimitiveData(UUID,
"some_data", data_r);
443 DOCTEST_CHECK(data_r == data);
445 std::vector<float> vec = {0, 1, 2, 3, 4};
446 context_test.setPrimitiveData(UUID,
"vec_data", vec);
447 std::vector<float> vec_r;
448 context_test.getPrimitiveData(UUID,
"vec_data", vec_r);
449 DOCTEST_CHECK(vec_r == vec);
451 std::vector<uint> UUIDs_filter;
452 std::vector<uint> UUIDs_multi;
453 for (
uint i = 0; i < 4; i++) {
454 UUIDs_multi.push_back(context_test.addPatch());
456 context_test.setPrimitiveData(UUIDs_multi[0],
"val", 4.f);
457 context_test.setPrimitiveData(UUIDs_multi[0],
"str",
"cat");
458 context_test.setPrimitiveData(UUIDs_multi[1],
"val", 3.f);
459 context_test.setPrimitiveData(UUIDs_multi[1],
"str",
"cat");
460 context_test.setPrimitiveData(UUIDs_multi[2],
"val", 2.f);
461 context_test.setPrimitiveData(UUIDs_multi[2],
"str",
"dog");
462 context_test.setPrimitiveData(UUIDs_multi[3],
"val", 1.f);
463 context_test.setPrimitiveData(UUIDs_multi[3],
"str",
"dog");
465 UUIDs_filter = context_test.filterPrimitivesByData(UUIDs_multi,
"val", 2.f,
"<=");
466 DOCTEST_CHECK(UUIDs_filter.size() == 2);
467 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[2]) != UUIDs_filter.end());
468 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[3]) != UUIDs_filter.end());
470 UUIDs_filter = context_test.filterPrimitivesByData(UUIDs_multi,
"str",
"cat");
471 DOCTEST_CHECK(UUIDs_filter.size() == 2);
472 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[0]) != UUIDs_filter.end());
473 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[1]) != UUIDs_filter.end());
475 SUBCASE(
"texture uv and solid fraction") {
476 Context context_test;
479 const char *texture =
"lib/images/disk_texture.png";
484 uint UUIDp = context_test.addPatch(
make_vec3(2, 3, 4), sizep, nullrotation, texture, 0.5f * (uv0 + uv2), uv2 - uv0);
485 DOCTEST_CHECK(!context_test.getPrimitiveTextureFile(UUIDp).empty());
486 float Ap = context_test.getPrimitiveArea(UUIDp);
487 DOCTEST_CHECK(Ap == doctest::Approx(0.25f *
PI_F * sizep.x * sizep.y).epsilon(0.01));
488 std::vector<vec2> uv = context_test.getPrimitiveTextureUV(UUIDp);
489 DOCTEST_CHECK(uv.size() == 4);
490 DOCTEST_CHECK(uv.at(0) == uv0);
491 DOCTEST_CHECK(uv.at(1) == uv1);
492 DOCTEST_CHECK(uv.at(2) == uv2);
493 DOCTEST_CHECK(uv.at(3) == uv3);
495 uint UUIDt = context_test.addTriangle(
make_vec3(0, 0, 0),
make_vec3(1, 0, 0),
make_vec3(1, 1, 0),
"lib/images/diamond_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
make_vec2(1, 1));
496 float solid_fraction = context_test.getPrimitiveSolidFraction(UUIDt);
497 DOCTEST_CHECK(solid_fraction == doctest::Approx(0.5f).epsilon(errtol));
500 SUBCASE(
"advanced primitive transforms") {
504 std::vector<uint> ids{p1, p2};
505 ctx.markGeometryClean();
507 ctx.rotatePrimitive(p1, 0.5f *
PI_F,
"x");
508 vec3 n = ctx.getPrimitiveNormal(p1);
509 DOCTEST_CHECK(n.y == doctest::Approx(-1.f).epsilon(errtol));
510 DOCTEST_CHECK(n.z == doctest::Approx(0.f).epsilon(errtol));
513 vec3 c = ctx.getPatchCenter(p2);
514 DOCTEST_CHECK(c.x == doctest::Approx(-1.f).epsilon(errtol));
519 vec2 sz = ctx.getPatchSize(p2);
520 DOCTEST_CHECK(sz.x == doctest::Approx(2.f).epsilon(errtol));
523 sz = ctx.getPatchSize(p2);
524 DOCTEST_CHECK(sz.x == doctest::Approx(1.f).epsilon(errtol));
528TEST_CASE(
"Triangle Scaling") {
530 const float errtol = 0.0001f;
532 SUBCASE(
"scalePrimitive basic test") {
537 uint tri = ctx.addTriangle(v0, v1, v2);
540 std::vector<vec3> verts_before = ctx.getPrimitiveVertices(tri);
541 float area_before = ctx.getPrimitiveArea(tri);
544 ctx.scalePrimitive(tri,
make_vec3(2, 2, 2));
547 std::vector<vec3> verts_after = ctx.getPrimitiveVertices(tri);
548 float area_after = ctx.getPrimitiveArea(tri);
556 DOCTEST_CHECK(verts_after[0].x == doctest::Approx(0.0f).epsilon(errtol));
557 DOCTEST_CHECK(verts_after[0].y == doctest::Approx(0.0f).epsilon(errtol));
558 DOCTEST_CHECK(verts_after[1].x == doctest::Approx(2.0f).epsilon(errtol));
559 DOCTEST_CHECK(verts_after[1].y == doctest::Approx(0.0f).epsilon(errtol));
560 DOCTEST_CHECK(verts_after[2].x == doctest::Approx(0.0f).epsilon(errtol));
561 DOCTEST_CHECK(verts_after[2].y == doctest::Approx(2.0f).epsilon(errtol));
563 DOCTEST_CHECK(area_after == doctest::Approx(4.0f * area_before).epsilon(errtol));
566 SUBCASE(
"scalePrimitiveAboutPoint test") {
571 uint tri = ctx.addTriangle(v0, v1, v2);
574 float area_before = ctx.getPrimitiveArea(tri);
580 float area_after = ctx.getPrimitiveArea(tri);
583 DOCTEST_CHECK(area_after == doctest::Approx(4.0f * area_before).epsilon(errtol));
586 SUBCASE(
"scalePrimitiveAboutPoint - scale about centroid") {
591 uint tri = ctx.addTriangle(v0, v1, v2);
594 std::vector<vec3> verts_before = ctx.getPrimitiveVertices(tri);
596 for (
const auto &v: verts_before) {
599 center = center / float(verts_before.size());
601 float area_before = ctx.getPrimitiveArea(tri);
604 ctx.scalePrimitiveAboutPoint(tri,
make_vec3(0.5f, 0.5f, 0.5f), center);
607 float area_after = ctx.getPrimitiveArea(tri);
610 DOCTEST_CHECK(area_after == doctest::Approx(0.25f * area_before).epsilon(errtol));
613 SUBCASE(
"triangle in compound object") {
615 std::vector<uint> UUIDs;
617 uint objID = ctx.addPolymeshObject(UUIDs);
620 float area_before = ctx.getPrimitiveArea(tri);
625 capture_cerr cerr_buffer;
627 has_warning = cerr_buffer.has_output();
629 DOCTEST_CHECK(has_warning);
631 float area_after = ctx.getPrimitiveArea(tri);
634 DOCTEST_CHECK(area_after == doctest::Approx(area_before).epsilon(errtol));
638TEST_CASE(
"Object Management") {
639 SUBCASE(
"addBoxObject") {
640 Context context_test;
644 int3 subdiv(1, 1, 1);
647 DOCTEST_CHECK_NOTHROW(objID = context_test.addBoxObject(center, size, subdiv));
648 std::vector<uint> UUIDs = context_test.getObjectPrimitiveUUIDs(objID);
650 DOCTEST_CHECK(UUIDs.size() == 6);
651 vec3 normal_r = context_test.getPrimitiveNormal(UUIDs.at(0));
652 DOCTEST_CHECK(doctest::Approx(normal_r.magnitude()).epsilon(errtol) == 1.f);
653 normal_r = context_test.getPrimitiveNormal(UUIDs.at(2));
654 DOCTEST_CHECK(doctest::Approx(normal_r.magnitude()).epsilon(errtol) == 1.f);
656 vec2 size_r = context_test.getPatchSize(UUIDs.at(0));
657 DOCTEST_CHECK(size_r.x == doctest::Approx(size.x).epsilon(errtol));
658 DOCTEST_CHECK(size_r.y == doctest::Approx(size.z).epsilon(errtol));
660 size_r = context_test.getPatchSize(UUIDs.at(2));
661 DOCTEST_CHECK(size_r.x == doctest::Approx(size.y).epsilon(errtol));
662 DOCTEST_CHECK(size_r.y == doctest::Approx(size.z).epsilon(errtol));
664 float volume = context_test.getBoxObjectVolume(objID);
665 DOCTEST_CHECK(volume == doctest::Approx(size.x * size.y * size.z).epsilon(errtol));
667 SUBCASE(
"addTileObject rotated") {
668 Context context_test;
674 uint objID = context_test.addTileObject(center, size, rotation, subdiv);
676 std::vector<uint> UUIDs = context_test.getObjectPrimitiveUUIDs(objID);
677 for (
uint UUIDp: UUIDs) {
678 vec3 n = context_test.getPrimitiveNormal(UUIDp);
680 DOCTEST_CHECK(rot.zenith == doctest::Approx(rotation.zenith).epsilon(errtol));
681 DOCTEST_CHECK(rot.azimuth == doctest::Approx(rotation.azimuth).epsilon(errtol));
684 SUBCASE(
"textured tile area") {
685 Context context_test;
692 uint objID = context_test.addTileObject(center, size, rotation, subdiv,
"lib/images/disk_texture.png");
693 std::vector<uint> UUIDs = context_test.getObjectPrimitiveUUIDs(objID);
694 float area_sum = 0.f;
695 for (
uint UUID: UUIDs) {
696 area_sum += context_test.getPrimitiveArea(UUID);
698 float area_exact = 0.25f *
PI_F * size.x * size.y;
699 DOCTEST_CHECK(area_sum == doctest::Approx(area_exact).epsilon(5e-3));
701 SUBCASE(
"cone object transforms") {
702 Context context_test;
703 float r0 = 0.5f, r1 = 1.f, len = 2.f;
706 uint cone = context_test.addConeObject(50, node0, node1, r0, r1);
707 context_test.translateObject(cone,
make_vec3(1, 1, 1));
708 std::vector<vec3> nodes = context_test.getConeObjectNodes(cone);
709 DOCTEST_CHECK(nodes.at(0) ==
make_vec3(1, 1, 1));
710 DOCTEST_CHECK(nodes.at(1) ==
make_vec3(1, 1, 1 + len));
713 context_test.translateObject(cone, -nodes.at(0));
714 context_test.rotateObject(cone, ang, axis);
715 context_test.translateObject(cone, nodes.at(0));
716 nodes = context_test.getConeObjectNodes(cone);
717 DOCTEST_CHECK(nodes.at(1).x == doctest::Approx(nodes.at(0).x + len).epsilon(errtol));
718 context_test.scaleConeObjectLength(cone, 2.0);
719 nodes = context_test.getConeObjectNodes(cone);
720 DOCTEST_CHECK(nodes.at(1).x == doctest::Approx(nodes.at(0).x + 2 * len).epsilon(errtol));
721 context_test.scaleConeObjectGirth(cone, 2.0);
722 std::vector<float> radii = context_test.getConeObjectNodeRadii(cone);
723 DOCTEST_CHECK(radii.at(0) == doctest::Approx(2 * r0).epsilon(errtol));
724 DOCTEST_CHECK(radii.at(1) == doctest::Approx(2 * r1).epsilon(errtol));
727 SUBCASE(
"rotate and scale objects") {
730 ctx.rotateObject(
obj, 0.5f *
PI_F,
"z");
732 ctx.getObjectBoundingBox(
obj, bmin, bmax);
733 DOCTEST_CHECK(bmax.x == doctest::Approx(0.5f).epsilon(errtol));
736 ctx.getObjectBoundingBox(
obj, bmin, bmax);
737 DOCTEST_CHECK(bmax.x > 0.5f);
740 SUBCASE(
"domain bounding sphere") {
742 std::vector<uint> ids;
747 ctx.getDomainBoundingSphere(ids, c, r);
748 DOCTEST_CHECK(c.x == doctest::Approx(0.f).epsilon(errtol));
749 DOCTEST_CHECK(r > 1.f);
752 SUBCASE(
"copy and delete objects") {
755 uint obj2 = ctx.copyObject(obj1);
756 DOCTEST_CHECK(ctx.doesObjectExist(obj1));
757 DOCTEST_CHECK(ctx.doesObjectExist(obj2));
758 ctx.deleteObject(obj2);
759 DOCTEST_CHECK(!ctx.doesObjectExist(obj2));
762 SUBCASE(
"copy object with texture override preserves color") {
763 capture_cerr cerr_buffer;
767 std::vector<uint> UUIDs = ctx.addTile(nullorigin,
make_vec2(1, 1), nullrotation,
make_int2(2, 2),
"lib/images/disk_texture.png");
771 ctx.setPrimitiveColor(UUIDs, green_color);
772 ctx.overridePrimitiveTextureColor(UUIDs);
775 uint objID = ctx.addPolymeshObject(UUIDs);
778 DOCTEST_CHECK(ctx.getPrimitiveColor(UUIDs[0]) == green_color);
779 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(UUIDs[0]));
782 uint objID_copy = ctx.copyObject(objID);
783 std::vector<uint> UUIDs_copy = ctx.getObjectPrimitiveUUIDs(objID_copy);
786 DOCTEST_CHECK(ctx.getPrimitiveColor(UUIDs_copy[0]) == green_color);
787 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(UUIDs_copy[0]));
790 uint triangle = ctx.addTriangle(
make_vec3(0, 0, 0),
make_vec3(1, 0, 0),
make_vec3(0, 1, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
make_vec2(0, 1));
792 ctx.setPrimitiveColor(triangle, blue_color);
793 ctx.overridePrimitiveTextureColor(triangle);
795 std::vector<uint> triangle_UUIDs = {triangle};
796 uint triangle_obj = ctx.addPolymeshObject(triangle_UUIDs);
797 uint triangle_obj_copy = ctx.copyObject(triangle_obj);
798 std::vector<uint> triangle_UUIDs_copy = ctx.getObjectPrimitiveUUIDs(triangle_obj_copy);
800 DOCTEST_CHECK(ctx.getPrimitiveColor(triangle_UUIDs_copy[0]) == blue_color);
801 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(triangle_UUIDs_copy[0]));
804 SUBCASE(
"domain cropping") {
811 bool has_output1, has_output2;
813 capture_cerr cerr_buffer;
815 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p1));
817 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p3));
819 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p4));
820 has_output1 = cerr_buffer.has_output();
822 DOCTEST_CHECK(has_output1);
825 capture_cerr cerr_buffer;
826 std::vector<uint> ids_rem = ctx.getAllUUIDs();
828 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p2));
829 has_output2 = cerr_buffer.has_output();
831 DOCTEST_CHECK(has_output2);
835TEST_CASE(
"Data Management") {
836 SUBCASE(
"global and object data") {
837 Context context_test;
839 context_test.setGlobalData(
"some_data", gdata);
841 DOCTEST_CHECK(context_test.doesGlobalDataExist(
"some_data"));
842 context_test.getGlobalData(
"some_data", gdata_r);
843 DOCTEST_CHECK(gdata_r == gdata);
845 std::vector<float> gvec{0, 1, 2, 3, 4};
846 context_test.setGlobalData(
"vec", gvec);
847 std::vector<float> gvec_r;
848 context_test.getGlobalData(
"vec", gvec_r);
849 DOCTEST_CHECK(gvec_r == gvec);
853 context_test.setObjectData(objID,
"obj", objdata);
855 context_test.getObjectData(objID,
"obj", objdata_r);
856 DOCTEST_CHECK(objdata_r == objdata);
858 SUBCASE(
"timeseries") {
864 Time time1 =
make_Time(time0.hour, 49, 14);
865 ctx.addTimeseriesData(
"ts", 302.3f, date, time0);
866 ctx.addTimeseriesData(
"ts", 305.3f, date, time1);
867 ctx.setCurrentTimeseriesPoint(
"ts", 0);
868 DOCTEST_CHECK(ctx.getTimeseriesLength(
"ts") == 2);
869 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", 0) == doctest::Approx(302.3f));
870 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", 1) == doctest::Approx(305.3f));
871 float val = ctx.queryTimeseriesData(
"ts", date, time1);
872 DOCTEST_CHECK(val == doctest::Approx(305.3f));
873 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"ts"));
874 std::vector<std::string> labels = ctx.listTimeseriesVariables();
875 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"ts") != labels.end());
876 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", ctx.getTimeseriesLength(
"ts") - 1) == doctest::Approx(305.3f));
877 Time t1_r = ctx.queryTimeseriesTime(
"ts", 1);
878 Date d1_r = ctx.queryTimeseriesDate(
"ts", 1);
879 DOCTEST_CHECK(t1_r.minute == time1.minute);
880 DOCTEST_CHECK(d1_r.day == date.day);
881 ctx.setCurrentTimeseriesPoint(
"ts", 1);
882 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts") == doctest::Approx(305.3f));
885 ctx.updateTimeseriesData(
"ts", date, time0, 999.9f);
886 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", 0) == doctest::Approx(999.9f));
887 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", date, time0) == doctest::Approx(999.9f));
889 DOCTEST_CHECK(ctx.queryTimeseriesData(
"ts", 1) == doctest::Approx(305.3f));
891 DOCTEST_CHECK(ctx.getTimeseriesLength(
"ts") == 2);
895 capture_cerr cerr_buffer;
896 DOCTEST_CHECK_THROWS(ctx.updateTimeseriesData(
"nonexistent", date, time0, 0.f));
901 Time time_missing =
make_Time(time0.hour, 0, 0);
902 capture_cerr cerr_buffer;
903 DOCTEST_CHECK_THROWS(ctx.updateTimeseriesData(
"ts", date, time_missing, 0.f));
907 SUBCASE(
"Primitive data") {
908 capture_cerr cerr_buffer;
911 uint p = ctx.addPatch();
912 ctx.setPrimitiveData(p,
"test_int", 5);
913 ctx.setPrimitiveData(p,
"test_float", 3.14f);
916 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_int") == HELIOS_TYPE_INT);
917 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_float") == HELIOS_TYPE_FLOAT);
920 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_int") == 1);
923 ctx.clearPrimitiveData(p,
"test_int");
924 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p,
"test_int"));
927 std::vector<std::string> data_labels = ctx.listPrimitiveData(p);
928 DOCTEST_CHECK(std::find(data_labels.begin(), data_labels.end(),
"test_float") != data_labels.end());
931 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataSize(p,
"test_int"));
934 ctx.clearPrimitiveData(p,
"test_int");
935 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p,
"test_int"));
938 ctx.setPrimitiveData(p,
"test_int", 5);
939 ctx.setPrimitiveData(p,
"test_float", 3.14f);
940 std::vector<std::string> labels = ctx.listPrimitiveData(p);
941 DOCTEST_CHECK(labels.size() == 2);
942 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"test_int") != labels.end());
943 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"test_float") != labels.end());
944 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_float") == HELIOS_TYPE_FLOAT);
948TEST_CASE(
"Data and Object Management") {
950 SUBCASE(
"Global data management") {
952 ctx.setGlobalData(
"test_double", 1.23);
953 DOCTEST_CHECK(ctx.getGlobalDataSize(
"test_double") == 1);
954 DOCTEST_CHECK(ctx.getGlobalDataType(
"test_double") == HELIOS_TYPE_DOUBLE);
955 ctx.clearGlobalData(
"test_double");
956 DOCTEST_CHECK(!ctx.doesGlobalDataExist(
"test_double"));
957 ctx.setGlobalData(
"test_string",
"hello");
958 std::vector<std::string> global_data_labels = ctx.listGlobalData();
959 DOCTEST_CHECK(std::find(global_data_labels.begin(), global_data_labels.end(),
"test_string") != global_data_labels.end());
962 SUBCASE(
"Object data management") {
965 ctx.setObjectData(
obj,
"test_vec", vec3(1, 2, 3));
966 DOCTEST_CHECK(ctx.getObjectDataSize(
obj,
"test_vec") == 1);
967 DOCTEST_CHECK(ctx.getObjectDataType(
"test_vec") == HELIOS_TYPE_VEC3);
968 ctx.clearObjectData(
obj,
"test_vec");
969 DOCTEST_CHECK(!ctx.doesObjectDataExist(
obj,
"test_vec"));
970 ctx.setObjectData(
obj,
"test_int", 42);
971 std::vector<std::string> object_data_labels = ctx.listObjectData(
obj);
972 DOCTEST_CHECK(std::find(object_data_labels.begin(), object_data_labels.end(),
"test_int") != object_data_labels.end());
975 SUBCASE(
"Object creation and manipulation") {
978 DOCTEST_CHECK(ctx.getObjectType(disk) == OBJECT_TYPE_DISK);
979 DOCTEST_CHECK(ctx.getObjectArea(disk) > 0);
980 DOCTEST_CHECK(ctx.getDiskObjectCenter(disk) ==
make_vec3(0, 0, 0));
981 DOCTEST_CHECK(ctx.getDiskObjectSubdivisionCount(disk) == 10);
982 DOCTEST_CHECK(ctx.getDiskObjectSize(disk).x == doctest::Approx(1.f));
984 uint sphere = ctx.addSphereObject(10,
make_vec3(1, 1, 1), 0.5f);
985 DOCTEST_CHECK(ctx.getObjectType(sphere) == OBJECT_TYPE_SPHERE);
986 DOCTEST_CHECK(ctx.getObjectArea(sphere) > 0);
987 DOCTEST_CHECK(ctx.getSphereObjectCenter(sphere) ==
make_vec3(1, 1, 1));
988 DOCTEST_CHECK(ctx.getSphereObjectSubdivisionCount(sphere) == 10);
989 DOCTEST_CHECK(ctx.getSphereObjectRadius(sphere).x == doctest::Approx(0.5f));
991 std::vector<uint> p_uuids;
993 uint polymesh = ctx.addPolymeshObject(p_uuids);
994 DOCTEST_CHECK(ctx.getObjectType(polymesh) == OBJECT_TYPE_POLYMESH);
995 DOCTEST_CHECK(ctx.getObjectArea(polymesh) > 0);
996 DOCTEST_CHECK(ctx.getObjectCenter(polymesh).z == doctest::Approx(0.f));
999 std::vector<float> radii = {0.2f, 0.1f};
1000 uint tube = ctx.addTubeObject(10, nodes, radii);
1001 DOCTEST_CHECK(ctx.getObjectType(tube) == OBJECT_TYPE_TUBE);
1002 DOCTEST_CHECK(ctx.getObjectArea(tube) > 0);
1003 DOCTEST_CHECK(ctx.getObjectCenter(tube).z == doctest::Approx(0.5f));
1004 DOCTEST_CHECK(ctx.getTubeObjectSubdivisionCount(tube) == 10);
1005 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 2);
1006 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube).size() == 2);
1007 DOCTEST_CHECK(ctx.getTubeObjectNodeColors(tube).size() == 2);
1008 DOCTEST_CHECK(ctx.getTubeObjectVolume(tube) > 0);
1009 ctx.appendTubeSegment(tube,
make_vec3(0, 0, 2), 0.05f, RGB::red);
1010 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1011 ctx.scaleTubeGirth(tube, 2.f);
1012 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube)[0] == doctest::Approx(0.4f));
1013 std::vector<float> new_radii = {0.3f, 0.2f, 0.1f};
1014 ctx.setTubeRadii(tube, new_radii);
1015 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube)[0] == doctest::Approx(0.3f));
1016 ctx.scaleTubeLength(tube, 2.f);
1018 ctx.setTubeNodes(tube, new_nodes);
1019 ctx.pruneTubeNodes(tube, 1);
1020 DOCTEST_CHECK_FALSE(ctx.doesObjectExist(tube));
1023 SUBCASE(
"Object appearance and visibility") {
1026 ctx.overrideObjectTextureColor(box);
1028 ctx.useObjectTextureColor(box);
1030 ctx.hideObject(box);
1031 DOCTEST_CHECK(ctx.isObjectHidden(box));
1032 ctx.showObject(box);
1033 DOCTEST_CHECK(!ctx.isObjectHidden(box));
1035 std::vector<uint> prims = ctx.getObjectPrimitiveUUIDs(box);
1036 ctx.hidePrimitive(prims);
1037 DOCTEST_CHECK(ctx.isPrimitiveHidden(prims[0]));
1038 ctx.showPrimitive(prims);
1039 DOCTEST_CHECK(!ctx.isPrimitiveHidden(prims[0]));
1042 SUBCASE(
"Primitive color and parent object") {
1043 capture_cerr cerr_buffer;
1045 uint p = ctx.addPatch();
1046 ctx.setPrimitiveColor(p, RGB::red);
1047 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::red);
1048 ctx.overridePrimitiveTextureColor(p);
1049 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(p));
1050 ctx.usePrimitiveTextureColor(p);
1051 DOCTEST_CHECK(!ctx.isPrimitiveTextureColorOverridden(p));
1054 ctx.setPrimitiveParentObjectID(p,
obj);
1055 DOCTEST_CHECK(ctx.getPrimitiveParentObjectID(p) ==
obj);
1058TEST_CASE(
"Object Management: Creation and Properties") {
1060 SUBCASE(
"addSphereObject") {
1062 uint objID = ctx.addSphereObject(10,
make_vec3(1, 2, 3), 5.f);
1063 DOCTEST_CHECK(ctx.doesObjectExist(objID));
1064 DOCTEST_CHECK(ctx.getSphereObjectCenter(objID) ==
make_vec3(1, 2, 3));
1065 DOCTEST_CHECK(ctx.getSphereObjectRadius(objID) ==
make_vec3(5.f, 5.f, 5.f));
1066 DOCTEST_CHECK(ctx.getSphereObjectSubdivisionCount(objID) == 10);
1069 SUBCASE(
"addDiskObject") {
1072 DOCTEST_CHECK(ctx.doesObjectExist(objID));
1073 DOCTEST_CHECK(ctx.getDiskObjectCenter(objID) ==
make_vec3(1, 2, 3));
1074 DOCTEST_CHECK(ctx.getDiskObjectSize(objID) ==
make_vec2(4, 5));
1075 DOCTEST_CHECK(ctx.getDiskObjectSubdivisionCount(objID) == 8u);
1078 SUBCASE(
"addConeObject") {
1081 DOCTEST_CHECK(ctx.doesObjectExist(objID));
1082 DOCTEST_CHECK(ctx.getConeObjectNode(objID, 0) ==
make_vec3(0, 0, 0));
1083 DOCTEST_CHECK(ctx.getConeObjectNode(objID, 1) ==
make_vec3(0, 0, 5));
1084 DOCTEST_CHECK(ctx.getConeObjectNodeRadius(objID, 0) == 2.f);
1085 DOCTEST_CHECK(ctx.getConeObjectNodeRadius(objID, 1) == 1.f);
1086 DOCTEST_CHECK(ctx.getConeObjectSubdivisionCount(objID) == 10);
1090TEST_CASE(
"Global Data Management") {
1091 SUBCASE(
"Integer Data") {
1093 ctx.setGlobalData(
"test_int", 123);
1094 DOCTEST_CHECK(ctx.doesGlobalDataExist(
"test_int"));
1096 ctx.getGlobalData(
"test_int", val);
1097 DOCTEST_CHECK(val == 123);
1098 DOCTEST_CHECK(ctx.getGlobalDataSize(
"test_int") == 1);
1099 DOCTEST_CHECK(ctx.getGlobalDataType(
"test_int") == HELIOS_TYPE_INT);
1100 ctx.clearGlobalData(
"test_int");
1101 DOCTEST_CHECK(!ctx.doesGlobalDataExist(
"test_int"));
1104 SUBCASE(
"Vector Data") {
1106 std::vector<vec3> vec_data = {{1, 2, 3}, {4, 5, 6}};
1107 ctx.setGlobalData(
"test_vec", vec_data);
1108 DOCTEST_CHECK(ctx.doesGlobalDataExist(
"test_vec"));
1109 std::vector<vec3> read_vec;
1110 ctx.getGlobalData(
"test_vec", read_vec);
1111 DOCTEST_CHECK(read_vec.size() == 2);
1112 DOCTEST_CHECK(read_vec[1] ==
make_vec3(4, 5, 6));
1113 DOCTEST_CHECK(ctx.getGlobalDataSize(
"test_vec") == 2);
1114 DOCTEST_CHECK(ctx.getGlobalDataType(
"test_vec") == HELIOS_TYPE_VEC3);
1117 SUBCASE(
"List Data") {
1119 ctx.setGlobalData(
"d1", 1);
1120 ctx.setGlobalData(
"d2", 2.f);
1121 std::vector<std::string> labels = ctx.listGlobalData();
1122 DOCTEST_CHECK(labels.size() == 2);
1123 DOCTEST_CHECK(std::find(labels.begin(), labels.end(),
"d1") != labels.end());
1127TEST_CASE(
"Context primitive data management") {
1129 uint p1 = ctx.addPatch();
1130 uint p2 = ctx.addPatch();
1131 ctx.setPrimitiveData(p1,
"my_data", 10);
1134 ctx.copyPrimitiveData(p1, p2);
1135 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2,
"my_data"));
1137 ctx.getPrimitiveData(p2,
"my_data", val);
1138 DOCTEST_CHECK(val == 10);
1141 ctx.renamePrimitiveData(p1,
"my_data",
"new_data_name");
1142 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p1,
"my_data"));
1143 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1,
"new_data_name"));
1146 ctx.duplicatePrimitiveData(p2,
"my_data",
"my_data_copy");
1147 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2,
"my_data_copy"));
1148 ctx.getPrimitiveData(p2,
"my_data_copy", val);
1149 DOCTEST_CHECK(val == 10);
1152 ctx.setPrimitiveData(p1,
"global_copy_test", 5.5f);
1153 ctx.duplicatePrimitiveData(
"global_copy_test",
"global_copy_test_new");
1154 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1,
"global_copy_test_new"));
1155 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p2,
"global_copy_test_new"));
1157 ctx.clearPrimitiveData(p1,
"new_data_name");
1158 ctx.setPrimitiveData(p2,
"my_data_copy", 15);
1159 ctx.setPrimitiveData(p2,
"my_data_copy", 20);
1160 ctx.clearPrimitiveData(p2,
"my_data_copy");
1161 std::vector<std::string> all_labels = ctx.listAllPrimitiveDataLabels();
1162 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"my_data") != all_labels.end());
1163 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"my_data_copy") == all_labels.end());
1164 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(),
"new_data_name") == all_labels.end());
1167TEST_CASE(
"Context primitive data calculations") {
1169 std::vector<uint> uuids;
1170 for (
int i = 0; i < 5; ++i) {
1172 ctx.setPrimitiveData(p,
"float_val", (
float) i);
1173 ctx.setPrimitiveData(p,
"double_val", (
double) i);
1174 ctx.setPrimitiveData(p,
"vec2_val",
make_vec2((
float) i, (
float) i));
1180 ctx.calculatePrimitiveDataMean(uuids,
"float_val", float_mean);
1181 DOCTEST_CHECK(float_mean == doctest::Approx(2.0f));
1183 ctx.calculatePrimitiveDataMean(uuids,
"double_val", double_mean);
1184 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
1186 ctx.calculatePrimitiveDataMean(uuids,
"vec2_val", vec2_mean);
1187 DOCTEST_CHECK(vec2_mean.x == doctest::Approx(2.0f));
1191 ctx.calculatePrimitiveDataAreaWeightedMean(uuids,
"float_val", awt_mean_f);
1192 DOCTEST_CHECK(awt_mean_f == doctest::Approx(2.0f));
1196 ctx.calculatePrimitiveDataSum(uuids,
"float_val", float_sum);
1197 DOCTEST_CHECK(float_sum == doctest::Approx(10.0f));
1201 ctx.calculatePrimitiveDataAreaWeightedSum(uuids,
"float_val", awt_sum_f);
1202 DOCTEST_CHECK(awt_sum_f == doctest::Approx(10.0f));
1205 ctx.scalePrimitiveData(uuids,
"float_val", 2.0f);
1206 ctx.getPrimitiveData(uuids[2],
"float_val", float_mean);
1207 DOCTEST_CHECK(float_mean == doctest::Approx(4.0f));
1208 ctx.scalePrimitiveData(
"double_val", 0.5f);
1209 ctx.getPrimitiveData(uuids[4],
"double_val", double_mean);
1210 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
1213 ctx.setPrimitiveData(uuids,
"int_val", 10);
1214 ctx.incrementPrimitiveData(uuids,
"int_val", 5);
1216 ctx.getPrimitiveData(uuids[0],
"int_val", int_val);
1217 DOCTEST_CHECK(int_val == 15);
1220 capture_cerr cerr_buffer;
1221 ctx.incrementPrimitiveData(uuids,
"float_val", 1);
1222 has_warning = cerr_buffer.has_output();
1224 DOCTEST_CHECK(has_warning);
1227TEST_CASE(
"Context primitive data aggregation and filtering") {
1229 std::vector<uint> uuids;
1230 for (
int i = 0; i < 3; ++i) {
1231 uint p = ctx.addPatch();
1232 ctx.setPrimitiveData(p,
"d1", (
float) i);
1233 ctx.setPrimitiveData(p,
"d2", (
float) i * 2.0f);
1234 ctx.setPrimitiveData(p,
"d3", (
float) i * 3.0f);
1235 ctx.setPrimitiveData(p,
"filter_me", i);
1240 std::vector<std::string> labels = {
"d1",
"d2",
"d3"};
1241 ctx.aggregatePrimitiveDataSum(uuids, labels,
"sum_data");
1243 ctx.getPrimitiveData(uuids[1],
"sum_data", sum_val);
1244 DOCTEST_CHECK(sum_val == doctest::Approx(1.f + 2.f + 3.f));
1247 ctx.aggregatePrimitiveDataProduct(uuids, labels,
"prod_data");
1249 ctx.getPrimitiveData(uuids[2],
"prod_data", prod_val);
1250 DOCTEST_CHECK(prod_val == doctest::Approx(2.f * 4.f * 6.f));
1253 std::vector<uint> filtered = ctx.filterPrimitivesByData(uuids,
"filter_me", 1,
">=");
1254 DOCTEST_CHECK(filtered.size() == 2);
1255 filtered = ctx.filterPrimitivesByData(uuids,
"filter_me", 1,
"==");
1256 DOCTEST_CHECK(filtered.size() == 1);
1257 DOCTEST_CHECK(filtered[0] == uuids[1]);
1258 capture_cerr cerr_buffer;
1259 DOCTEST_CHECK_THROWS_AS(filtered = ctx.filterPrimitivesByData(uuids,
"filter_me", 1,
"!!"), std::runtime_error);
1262TEST_CASE(
"Object data") {
1265 ctx.setObjectData(o,
"test_int", 5);
1266 ctx.setObjectData(o,
"test_float", 3.14f);
1269 DOCTEST_CHECK(ctx.getObjectDataType(
"test_int") == HELIOS_TYPE_INT);
1271 capture_cerr cerr_buffer;
1272 DOCTEST_CHECK_THROWS_AS(ctx.getObjectDataType(
"non_existent"), std::runtime_error);
1276 DOCTEST_CHECK(ctx.getObjectDataSize(o,
"test_int") == 1);
1279 ctx.clearObjectData(o,
"test_int");
1280 DOCTEST_CHECK(!ctx.doesObjectDataExist(o,
"test_int"));
1283 std::vector<std::string> data_labels = ctx.listObjectData(o);
1284 DOCTEST_CHECK(std::find(data_labels.begin(), data_labels.end(),
"test_float") != data_labels.end());
1287TEST_CASE(
"Context object data management") {
1291 ctx.setObjectData(o1,
"my_data", 10);
1294 ctx.copyObjectData(o1, o2);
1295 DOCTEST_CHECK(ctx.doesObjectDataExist(o2,
"my_data"));
1298 ctx.renameObjectData(o1,
"my_data",
"new_name");
1299 DOCTEST_CHECK(!ctx.doesObjectDataExist(o1,
"my_data"));
1300 DOCTEST_CHECK(ctx.doesObjectDataExist(o1,
"new_name"));
1303 ctx.duplicateObjectData(o2,
"my_data",
"my_data_copy");
1304 DOCTEST_CHECK(ctx.doesObjectDataExist(o2,
"my_data_copy"));
1306 std::vector<std::string> all_obj_labels = ctx.listAllObjectDataLabels();
1307 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(),
"my_data") != all_obj_labels.end());
1308 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(),
"my_data_copy") != all_obj_labels.end());
1309 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(),
"new_name") != all_obj_labels.end());
1312TEST_CASE(
"Global data") {
1314 ctx.setGlobalData(
"g_int", 5);
1315 ctx.setGlobalData(
"g_float", 3.14f);
1318 DOCTEST_CHECK(ctx.doesGlobalDataExist(
"g_int"));
1319 DOCTEST_CHECK(ctx.getGlobalDataType(
"g_int") == HELIOS_TYPE_INT);
1320 DOCTEST_CHECK(ctx.getGlobalDataSize(
"g_int") == 1);
1323 ctx.duplicateGlobalData(
"g_int",
"g_int_copy");
1324 DOCTEST_CHECK(ctx.doesGlobalDataExist(
"g_int_copy"));
1325 ctx.renameGlobalData(
"g_int",
"g_int_new");
1326 DOCTEST_CHECK(!ctx.doesGlobalDataExist(
"g_int"));
1327 DOCTEST_CHECK(ctx.doesGlobalDataExist(
"g_int_new"));
1328 ctx.clearGlobalData(
"g_int_new");
1329 DOCTEST_CHECK(!ctx.doesGlobalDataExist(
"g_int_new"));
1332 std::vector<std::string> g_labels = ctx.listGlobalData();
1333 DOCTEST_CHECK(g_labels.size() > 0);
1336 ctx.setGlobalData(
"inc_me", 10);
1337 ctx.incrementGlobalData(
"inc_me", 5);
1339 ctx.getGlobalData(
"inc_me", val);
1340 DOCTEST_CHECK(val == 15);
1343 capture_cerr cerr_buffer;
1344 ctx.incrementGlobalData(
"g_float", 1);
1345 has_warning = cerr_buffer.has_output();
1347 DOCTEST_CHECK(has_warning);
1350TEST_CASE(
"Voxel Management") {
1351 SUBCASE(
"addVoxel and voxel properties") {
1356 float rotation = 0.5f *
PI_F;
1358 uint vox1 = ctx.addVoxel(center, size);
1359 DOCTEST_CHECK(ctx.getPrimitiveType(vox1) == PRIMITIVE_TYPE_VOXEL);
1360 DOCTEST_CHECK(ctx.getVoxelCenter(vox1) == center);
1361 DOCTEST_CHECK(ctx.getVoxelSize(vox1) == size);
1363 uint vox2 = ctx.addVoxel(center, size, rotation);
1364 DOCTEST_CHECK(ctx.getVoxelCenter(vox2) == center);
1365 DOCTEST_CHECK(ctx.getVoxelSize(vox2) == size);
1367 uint vox3 = ctx.addVoxel(center, size, rotation, RGB::red);
1368 DOCTEST_CHECK(ctx.getPrimitiveColor(vox3) == RGB::red);
1370 uint vox4 = ctx.addVoxel(center, size, rotation, RGBA::red);
1371 RGBAcolor color_rgba = ctx.getPrimitiveColorRGBA(vox4);
1372 DOCTEST_CHECK(color_rgba.r == RGBA::red.r);
1373 DOCTEST_CHECK(color_rgba.a == RGBA::red.a);
1375 DOCTEST_CHECK(ctx.getPrimitiveCount() >= 4);
1377 float area = ctx.getPrimitiveArea(vox1);
1378 DOCTEST_CHECK(area == doctest::Approx(2.f * (size.x * size.y + size.y * size.z + size.x * size.z)));
1382TEST_CASE(
"Texture Management") {
1383 SUBCASE(
"texture validation and properties") {
1384 capture_cerr cerr_buffer;
1388 ctx.setPrimitiveTextureFile(patch,
"lib/images/solid.jpg");
1389 DOCTEST_CHECK(ctx.getPrimitiveTextureFile(patch) ==
"lib/images/solid.jpg");
1390 DOCTEST_CHECK(!ctx.primitiveTextureHasTransparencyChannel(patch));
1392 Texture tex(
"lib/images/solid.jpg");
1393 std::vector<vec2> uv = {{0, 0}, {1, 0}, {1, 1}};
1394 float solid_frac = tex.getSolidFraction(uv);
1395 DOCTEST_CHECK(solid_frac == doctest::Approx(1.f));
1399TEST_CASE(
"Triangle Management") {
1400 SUBCASE(
"setTriangleVertices") {
1405 uint tri = ctx.addTriangle(v0, v1, v2);
1410 ctx.setTriangleVertices(tri, new_v0, new_v1, new_v2);
1412 std::vector<vec3> vertices = ctx.getPrimitiveVertices(tri);
1413 DOCTEST_CHECK(vertices[0] == new_v0);
1414 DOCTEST_CHECK(vertices[1] == new_v1);
1415 DOCTEST_CHECK(vertices[2] == new_v2);
1419TEST_CASE(
"UUID and Object Management") {
1420 SUBCASE(
"getAllUUIDs and cleanDeletedUUIDs") {
1422 uint p1 = ctx.addPatch();
1423 uint p2 = ctx.addPatch();
1424 uint p3 = ctx.addPatch();
1426 std::vector<uint> all_uuids = ctx.getAllUUIDs();
1427 DOCTEST_CHECK(all_uuids.size() == 3);
1428 DOCTEST_CHECK(std::find(all_uuids.begin(), all_uuids.end(), p1) != all_uuids.end());
1430 ctx.deletePrimitive(p2);
1431 std::vector<uint> uuids_with_deleted = {p1, p2, p3};
1432 ctx.cleanDeletedUUIDs(uuids_with_deleted);
1433 DOCTEST_CHECK(uuids_with_deleted.size() == 2);
1434 DOCTEST_CHECK(std::find(uuids_with_deleted.begin(), uuids_with_deleted.end(), p2) == uuids_with_deleted.end());
1436 std::vector<std::vector<uint>> nested_uuids = {{p1, p2}, {p3, p2}};
1437 ctx.cleanDeletedUUIDs(nested_uuids);
1438 DOCTEST_CHECK(nested_uuids[0].size() == 1);
1439 DOCTEST_CHECK(nested_uuids[1].size() == 1);
1441 std::vector<std::vector<std::vector<uint>>> triple_nested = {{{p1, p2, p3}}};
1442 ctx.cleanDeletedUUIDs(triple_nested);
1443 DOCTEST_CHECK(triple_nested[0][0].size() == 2);
1446 SUBCASE(
"object management utilities") {
1450 DOCTEST_CHECK(ctx.areObjectPrimitivesComplete(
obj));
1452 std::vector<uint> obj_ids = {
obj, 999};
1453 ctx.cleanDeletedObjectIDs(obj_ids);
1454 DOCTEST_CHECK(obj_ids.size() == 1);
1455 DOCTEST_CHECK(obj_ids[0] ==
obj);
1457 std::vector<std::vector<uint>> nested_obj_ids = {{
obj, 999}, {
obj}};
1458 ctx.cleanDeletedObjectIDs(nested_obj_ids);
1459 DOCTEST_CHECK(nested_obj_ids[0].size() == 1);
1460 DOCTEST_CHECK(nested_obj_ids[1].size() == 1);
1462 std::vector<std::vector<std::vector<uint>>> triple_nested_obj = {{{
obj, 999}}};
1463 ctx.cleanDeletedObjectIDs(triple_nested_obj);
1464 DOCTEST_CHECK(triple_nested_obj[0][0].size() == 1);
1466 DOCTEST_CHECK(ctx.doesObjectExist(
obj));
1469 ctx.setObjectOrigin(
obj, new_origin);
1472 ctx.setObjectAverageNormal(
obj,
make_vec3(0, 0, 0), new_normal);
1476TEST_CASE(
"Tile Object Advanced Features") {
1477 SUBCASE(
"tile object subdivision management") {
1481 float area_ratio = ctx.getTileObjectAreaRatio(tile);
1482 DOCTEST_CHECK(area_ratio > 0.f);
1484 ctx.setTileObjectSubdivisionCount({tile},
make_int2(4, 4));
1486 ctx.setTileObjectSubdivisionCount({tile}, 0.5f);
1490TEST_CASE(
"Pseudocolor Visualization") {
1491 SUBCASE(
"colorPrimitiveByDataPseudocolor") {
1493 std::vector<uint> patches;
1494 for (
int i = 0; i < 5; i++) {
1495 uint p = ctx.addPatch();
1496 ctx.setPrimitiveData(p,
"value",
float(i));
1497 patches.push_back(p);
1500 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches,
"value",
"hot", 10));
1501 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches,
"value",
"rainbow", 5, 0.f, 4.f));
1505TEST_CASE(
"Date and Time Extensions") {
1506 SUBCASE(
"getMonthString") {
1508 ctx.setDate(15, 1, 2025);
1509 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"JAN") == 0);
1510 ctx.setDate(15, 2, 2025);
1511 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"FEB") == 0);
1512 ctx.setDate(15, 12, 2025);
1513 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"DEC") == 0);
1517TEST_CASE(
"Tube Object Management") {
1518 SUBCASE(
"appendTubeSegment with texture") {
1521 std::vector<float> radii = {0.2f, 0.1f};
1522 uint tube = ctx.addTubeObject(10, nodes, radii);
1524 ctx.appendTubeSegment(tube,
make_vec3(0, 0, 2), 0.05f,
"lib/images/solid.jpg",
make_vec2(0.5f, 1.0f));
1525 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1528 SUBCASE(
"setTubeNodes preserves color-to-segment mapping") {
1532 std::vector<vec3> nodes = {
make_vec3(0, 0, 0),
make_vec3(0, 0, 1),
make_vec3(0, 0, 2),
make_vec3(0, 0, 3)};
1533 std::vector<float> radii = {0.5f, 0.5f, 0.5f, 0.5f};
1534 std::vector<RGBcolor> colors = {RGB::red, RGB::yellow, RGB::green, RGB::blue};
1537 uint tube = ctx.addTubeObject(subdiv, nodes, radii, colors);
1540 std::vector<vec3> new_nodes = nodes;
1541 for (
auto &n : new_nodes) n.x += 0.1f;
1542 ctx.setTubeNodes(tube, new_nodes);
1547 std::vector<uint> uuids = ctx.getObjectPrimitiveUUIDs(tube);
1549 for (
uint uuid : uuids) {
1550 RGBcolor c = ctx.getPrimitiveColorRGB(uuid);
1553 int expected_segment = -1;
1554 if (c.r == RGB::red.r && c.g == RGB::red.g && c.b == RGB::red.b) expected_segment = 0;
1555 else if (c.r == RGB::yellow.r && c.g == RGB::yellow.g && c.b == RGB::yellow.b) expected_segment = 1;
1556 else if (c.r == RGB::green.r && c.g == RGB::green.g && c.b == RGB::green.b) expected_segment = 2;
1558 DOCTEST_REQUIRE(expected_segment >= 0);
1560 float z_min = new_nodes[expected_segment].z;
1561 float z_max = new_nodes[expected_segment + 1].z;
1564 for (
uint v = 0; v < 3; v++) {
1565 vec3 vert = ctx.getTriangleVertex(uuid, v);
1566 if (vert.z < z_min - 0.01f || vert.z > z_max + 0.01f) {
1572 DOCTEST_CHECK_MESSAGE(mismatches == 0,
"Found " << mismatches <<
" triangles with vertices outside their color's segment range after setTubeNodes()");
1575 SUBCASE(
"pruneTubeNodes deletes correct primitives") {
1578 std::vector<vec3> nodes = {
make_vec3(0, 0, 0),
make_vec3(0, 0, 1),
make_vec3(0, 0, 2),
make_vec3(0, 0, 3)};
1579 std::vector<float> radii = {0.5f, 0.5f, 0.5f, 0.5f};
1580 std::vector<RGBcolor> colors = {RGB::red, RGB::yellow, RGB::green, RGB::blue};
1583 uint tube = ctx.addTubeObject(subdiv, nodes, radii, colors);
1585 uint uuids_before = ctx.getObjectPrimitiveUUIDs(tube).size();
1587 DOCTEST_CHECK(uuids_before == 48);
1589 ctx.pruneTubeNodes(tube, 3);
1592 DOCTEST_CHECK(ctx.doesObjectExist(tube));
1593 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1596 std::vector<uint> remaining = ctx.getObjectPrimitiveUUIDs(tube);
1597 DOCTEST_CHECK(remaining.size() == 32);
1601 for (
uint uuid : remaining) {
1602 RGBcolor c = ctx.getPrimitiveColorRGB(uuid);
1603 bool is_red = (c.r == RGB::red.r && c.g == RGB::red.g && c.b == RGB::red.b);
1604 bool is_yellow = (c.r == RGB::yellow.r && c.g == RGB::yellow.g && c.b == RGB::yellow.b);
1605 if (!is_red && !is_yellow) bad_colors++;
1607 DOCTEST_CHECK(bad_colors == 0);
1610 ctx.pruneTubeNodes(tube, 1);
1611 DOCTEST_CHECK_FALSE(ctx.doesObjectExist(tube));
1614 SUBCASE(
"setTubeNodes maintains circular cross-sections after bending") {
1618 std::vector<float> radii = {0.5f, 0.5f, 0.5f};
1620 uint tube = ctx.addTubeObject(subdiv, nodes, radii);
1624 ctx.setTubeNodes(tube, bent_nodes);
1629 std::vector<uint> uuids = ctx.getObjectPrimitiveUUIDs(tube);
1632 std::vector<vec3> axial(3);
1633 axial[0] = (bent_nodes[1] - bent_nodes[0]);
1634 axial[0].normalize();
1635 axial[1] = 0.5f * ((bent_nodes[1] - bent_nodes[0]) + (bent_nodes[2] - bent_nodes[1]));
1636 axial[1].normalize();
1637 axial[2] = (bent_nodes[2] - bent_nodes[1]);
1638 axial[2].normalize();
1640 int perp_failures = 0;
1641 int radius_failures = 0;
1643 for (
uint uuid : uuids) {
1644 for (
uint v = 0; v < 3; v++) {
1645 vec3 vert = ctx.getTriangleVertex(uuid, v);
1649 float min_dist = (vert - bent_nodes[0]).magnitude();
1650 for (
int n = 1; n < 3; n++) {
1651 float d = (vert - bent_nodes[n]).magnitude();
1658 vec3 radial = vert - bent_nodes[nearest];
1659 float dot = fabs(radial * axial[nearest]);
1663 if (fabs(radial.magnitude() - 0.5f) > 0.02f) {
1669 DOCTEST_CHECK_MESSAGE(perp_failures == 0,
"Found " << perp_failures <<
" vertices not perpendicular to local tube axis after bending");
1670 DOCTEST_CHECK_MESSAGE(radius_failures == 0,
"Found " << radius_failures <<
" vertices with incorrect radius after bending");
1674TEST_CASE(
"Edge Cases and Additional Coverage") {
1675 SUBCASE(
"Julian date edge cases") {
1677 ctx.setDate(1, 1, 2025);
1678 DOCTEST_CHECK(ctx.getJulianDate() == 1);
1680 ctx.setDate(31, 12, 2025);
1681 DOCTEST_CHECK(ctx.getJulianDate() == 365);
1683 ctx.setDate(100, 2025);
1684 Date d = ctx.getDate();
1685 DOCTEST_CHECK(d.day == 10);
1686 DOCTEST_CHECK(d.month == 4);
1689 SUBCASE(
"time edge cases") {
1691 ctx.setTime(0, 0, 0);
1692 Time t = ctx.getTime();
1693 DOCTEST_CHECK(t.hour == 0);
1694 DOCTEST_CHECK(t.minute == 0);
1695 DOCTEST_CHECK(t.second == 0);
1697 ctx.setTime(59, 59, 23);
1699 DOCTEST_CHECK(t.hour == 23);
1700 DOCTEST_CHECK(t.minute == 59);
1701 DOCTEST_CHECK(t.second == 59);
1704 SUBCASE(
"random number edge cases") {
1706 ctx.seedRandomGenerator(0);
1708 float r1 = ctx.randu(5.f, 5.f);
1709 DOCTEST_CHECK(r1 == doctest::Approx(5.f));
1711 int ri = ctx.randu(10, 10);
1712 DOCTEST_CHECK(ri == 10);
1714 float rn = ctx.randn(0.f, 0.f);
1715 DOCTEST_CHECK(rn == doctest::Approx(0.f));
1718 SUBCASE(
"texture edge cases") {
1719 capture_cerr cerr_buffer;
1721 uint patch = ctx.addPatch();
1723 ctx.overridePrimitiveTextureColor(patch);
1724 ctx.usePrimitiveTextureColor(patch);
1726 std::vector<uint> patches = {patch};
1727 ctx.overridePrimitiveTextureColor(patches);
1728 ctx.usePrimitiveTextureColor(patches);
1730 DOCTEST_CHECK(!ctx.isPrimitiveTextureColorOverridden(patch));
1733 SUBCASE(
"primitive existence checks") {
1735 uint p1 = ctx.addPatch();
1736 uint p2 = ctx.addPatch();
1738 DOCTEST_CHECK(ctx.doesPrimitiveExist(p1));
1739 DOCTEST_CHECK(ctx.doesPrimitiveExist({p1, p2}));
1741 ctx.deletePrimitive(p1);
1742 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p1));
1743 DOCTEST_CHECK(!ctx.doesPrimitiveExist({p1, p2}));
1744 DOCTEST_CHECK(ctx.doesPrimitiveExist(std::vector<uint>{p2}));
1747 SUBCASE(
"object containment checks") {
1750 std::vector<uint> prims = ctx.getObjectPrimitiveUUIDs(
obj);
1752 DOCTEST_CHECK(ctx.doesObjectContainPrimitive(
obj, prims[0]));
1754 uint independent_patch = ctx.addPatch();
1755 DOCTEST_CHECK(!ctx.doesObjectContainPrimitive(
obj, independent_patch));
1758 SUBCASE(
"transformation matrix operations") {
1760 uint p = ctx.addPatch();
1762 float identity[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
1763 ctx.setPrimitiveTransformationMatrix(p, identity);
1765 std::vector<uint> patches = {p};
1766 ctx.setPrimitiveTransformationMatrix(patches, identity);
1769 ctx.setObjectTransformationMatrix(
obj, identity);
1771 std::vector<uint> objs = {
obj};
1772 ctx.setObjectTransformationMatrix(objs, identity);
1774 float retrieved[16];
1775 ctx.getObjectTransformationMatrix(
obj, retrieved);
1776 for (
int i = 0; i < 16; i++) {
1777 DOCTEST_CHECK(retrieved[i] == doctest::Approx(identity[i]));
1781 SUBCASE(
"object type and texture checks") {
1785 DOCTEST_CHECK(!ctx.objectHasTexture(
obj));
1788 DOCTEST_CHECK(ctx.objectHasTexture(textured_obj));
1791 SUBCASE(
"tube object segment operations") {
1794 std::vector<float> radii = {0.2f, 0.15f, 0.1f};
1795 uint tube = ctx.addTubeObject(10, nodes, radii);
1797 float seg_volume = ctx.getTubeObjectSegmentVolume(tube, 0);
1798 DOCTEST_CHECK(seg_volume > 0.f);
1800 seg_volume = ctx.getTubeObjectSegmentVolume(tube, 1);
1801 DOCTEST_CHECK(seg_volume > 0.f);
1804 SUBCASE(
"cone object advanced properties") {
1808 float radius0 = ctx.getConeObjectNodeRadius(cone, 0);
1809 DOCTEST_CHECK(radius0 == doctest::Approx(1.f));
1811 float radius1 = ctx.getConeObjectNodeRadius(cone, 1);
1812 DOCTEST_CHECK(radius1 == doctest::Approx(0.5f));
1814 float length = ctx.getConeObjectLength(cone);
1815 DOCTEST_CHECK(length == doctest::Approx(2.f));
1817 DOCTEST_CHECK(ctx.getConeObjectSubdivisionCount(cone) == 10);
1820 SUBCASE(
"primitive color operations") {
1821 capture_cerr cerr_buffer;
1823 uint p = ctx.addPatch();
1825 ctx.setPrimitiveColor(p, RGB::blue);
1826 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::blue);
1828 ctx.setPrimitiveColor(p, RGBA::green);
1829 RGBAcolor rgba = ctx.getPrimitiveColorRGBA(p);
1830 DOCTEST_CHECK(rgba.r == RGBA::green.r);
1831 DOCTEST_CHECK(rgba.a == RGBA::green.a);
1833 std::vector<uint> patches = {p};
1834 ctx.setPrimitiveColor(patches, RGB::red);
1835 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::red);
1837 ctx.setPrimitiveColor(patches, RGBA::yellow);
1838 rgba = ctx.getPrimitiveColorRGBA(p);
1839 DOCTEST_CHECK(rgba.r == RGBA::yellow.r);
1842 SUBCASE(
"object color operations") {
1843 capture_cerr cerr_buffer;
1847 ctx.setObjectColor(
obj, RGB::cyan);
1848 ctx.setObjectColor(
obj, RGBA::magenta);
1850 std::vector<uint> objs = {
obj};
1851 ctx.setObjectColor(objs, RGB::white);
1852 ctx.setObjectColor(objs, RGBA::black);
1856TEST_CASE(
"Print and Information Functions") {
1857 SUBCASE(
"printPrimitiveInfo and printObjectInfo") {
1866 capture_cout cout_buffer;
1867 DOCTEST_CHECK_NOTHROW(ctx.printPrimitiveInfo(patch));
1868 DOCTEST_CHECK_NOTHROW(ctx.printObjectInfo(
obj));
1869 has_output = cout_buffer.has_output();
1870 output = cout_buffer.get_captured_output();
1874 DOCTEST_CHECK(has_output);
1875 DOCTEST_CHECK(output.find(
"Info for UUID") != std::string::npos);
1876 DOCTEST_CHECK(output.find(
"Info for ObjID") != std::string::npos);
1880TEST_CASE(
"Object Pointer Access") {
1881 SUBCASE(
"getObjectPointer functions") {
1885 DOCTEST_CHECK(ctx.doesObjectExist(box));
1888 DOCTEST_CHECK(ctx.doesObjectExist(disk));
1890 uint sphere = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
1891 DOCTEST_CHECK(ctx.doesObjectExist(sphere));
1894 std::vector<float> radii = {0.2f, 0.1f};
1895 uint tube = ctx.addTubeObject(10, nodes, radii);
1896 DOCTEST_CHECK(ctx.doesObjectExist(tube));
1899 DOCTEST_CHECK(ctx.doesObjectExist(cone));
1902 uint polymesh = ctx.addPolymeshObject(prim_uuids);
1903 DOCTEST_CHECK(ctx.doesObjectExist(polymesh));
1907TEST_CASE(
"Advanced Primitive Operations") {
1908 SUBCASE(
"primitive visibility and print operations") {
1910 uint p1 = ctx.addPatch();
1911 uint p2 = ctx.addPatch();
1914 ctx.hidePrimitive(p1);
1915 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
1916 ctx.showPrimitive(p1);
1917 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
1919 std::vector<uint> patches = {p1, p2};
1920 ctx.hidePrimitive(patches);
1921 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
1922 DOCTEST_CHECK(ctx.isPrimitiveHidden(p2));
1923 ctx.showPrimitive(patches);
1924 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
1925 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p2));
1928 SUBCASE(
"primitive counts by type") {
1930 uint initial_patch_count = ctx.getPatchCount();
1931 uint initial_triangle_count = ctx.getTriangleCount();
1933 uint p1 = ctx.addPatch();
1934 uint p2 = ctx.addPatch();
1937 DOCTEST_CHECK(ctx.getPatchCount() == initial_patch_count + 2);
1938 DOCTEST_CHECK(ctx.getTriangleCount() == initial_triangle_count + 1);
1941 ctx.hidePrimitive(p1);
1942 DOCTEST_CHECK(ctx.getPatchCount(
false) == initial_patch_count + 1);
1943 DOCTEST_CHECK(ctx.getPatchCount(
true) == initial_patch_count + 2);
1947TEST_CASE(
"Data Type and Size Functions") {
1948 SUBCASE(
"primitive data type operations") {
1950 uint p = ctx.addPatch();
1952 ctx.setPrimitiveData(p,
"test_int", 42);
1953 ctx.setPrimitiveData(p,
"test_float", 3.14f);
1954 ctx.setPrimitiveData(p,
"test_vec3",
make_vec3(1, 2, 3));
1956 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_int") == HELIOS_TYPE_INT);
1957 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_float") == HELIOS_TYPE_FLOAT);
1958 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_vec3") == HELIOS_TYPE_VEC3);
1960 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_int") == 1);
1961 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_vec3") == 1);
1963 std::vector<float> vec_data = {1.0f, 2.0f, 3.0f};
1964 ctx.setPrimitiveData(p,
"test_vector", vec_data);
1965 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_vector") == 3);
1969TEST_CASE(
"Additional Missing Coverage") {
1970 SUBCASE(
"getDirtyUUIDs function") {
1972 uint p1 = ctx.addPatch();
1973 uint p2 = ctx.addPatch();
1975 ctx.markGeometryClean();
1976 std::vector<uint> dirty_uuids = ctx.getDirtyUUIDs();
1977 DOCTEST_CHECK(dirty_uuids.empty());
1979 ctx.markPrimitiveDirty(p1);
1980 dirty_uuids = ctx.getDirtyUUIDs();
1981 DOCTEST_CHECK(dirty_uuids.size() == 1);
1982 DOCTEST_CHECK(std::find(dirty_uuids.begin(), dirty_uuids.end(), p1) != dirty_uuids.end());
1986TEST_CASE(
"Advanced Object Operations") {
1987 SUBCASE(
"object primitive count and area calculations") {
1991 DOCTEST_CHECK(ctx.getObjectPrimitiveCount(
obj) == 6);
1993 float area = ctx.getObjectArea(
obj);
1994 float expected_area = 2 * (2 * 3 + 3 * 4 + 2 * 4);
1995 DOCTEST_CHECK(area == doctest::Approx(expected_area).epsilon(0.01));
1998 SUBCASE(
"object bounding box operations") {
2002 vec3 min_corner, max_corner;
2003 ctx.getObjectBoundingBox(
obj, min_corner, max_corner);
2005 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
2006 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
2007 DOCTEST_CHECK(min_corner.y == doctest::Approx(0.f).epsilon(0.01));
2008 DOCTEST_CHECK(max_corner.y == doctest::Approx(4.f).epsilon(0.01));
2010 std::vector<uint> objs = {
obj};
2011 ctx.getObjectBoundingBox(objs, min_corner, max_corner);
2012 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
2013 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
2017TEST_CASE(
"Additional Object Features") {
2018 SUBCASE(
"getAllObjectIDs") {
2021 uint obj2 = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2023 std::vector<uint> all_ids = ctx.getAllObjectIDs();
2024 DOCTEST_CHECK(all_ids.size() >= 2);
2025 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj1) != all_ids.end());
2026 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj2) != all_ids.end());
2029 SUBCASE(
"object type checks") {
2032 uint sphere = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2035 DOCTEST_CHECK(ctx.getObjectType(box) == OBJECT_TYPE_BOX);
2036 DOCTEST_CHECK(ctx.getObjectType(sphere) == OBJECT_TYPE_SPHERE);
2037 DOCTEST_CHECK(ctx.getObjectType(disk) == OBJECT_TYPE_DISK);
2041TEST_CASE(
"Comprehensive Object Property Tests") {
2042 SUBCASE(
"rotation operations on objects") {
2044 std::vector<uint> objs;
2048 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f *
PI_F,
"z"));
2049 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f *
PI_F,
make_vec3(0, 0, 1)));
2051 DOCTEST_CHECK_NOTHROW(ctx.rotateObjectAboutOrigin(objs, 0.5f *
PI_F,
make_vec3(0, 0, 1)));
2054 SUBCASE(
"scaling operations on objects") {
2056 std::vector<uint> objs;
2059 DOCTEST_CHECK_NOTHROW(ctx.scaleObject(objs,
make_vec3(2, 2, 2)));
2060 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutCenter(objs,
make_vec3(0.5f, 0.5f, 0.5f)));
2061 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutPoint(objs,
make_vec3(2, 2, 2),
make_vec3(0, 0, 0)));
2062 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutOrigin(objs,
make_vec3(0.5f, 0.5f, 0.5f)));
2065 SUBCASE(
"translation operations on objects") {
2067 std::vector<uint> objs;
2070 DOCTEST_CHECK_NOTHROW(ctx.translateObject(objs,
make_vec3(1, 2, 3)));
2074TEST_CASE(
"Domain and Bounding Operations") {
2075 SUBCASE(
"domain bounding sphere") {
2082 ctx.getDomainBoundingSphere(center, radius);
2083 DOCTEST_CHECK(center.x == doctest::Approx(0.f).epsilon(0.1));
2084 DOCTEST_CHECK(radius > 2.f);
2088TEST_CASE(
"Missing Data and State Functions") {
2089 SUBCASE(
"listTimeseriesVariables") {
2094 ctx.addTimeseriesData(
"temp", 25.5f, date, time);
2095 ctx.addTimeseriesData(
"humidity", 60.0f, date, time);
2097 std::vector<std::string> vars = ctx.listTimeseriesVariables();
2098 DOCTEST_CHECK(vars.size() >= 2);
2099 DOCTEST_CHECK(std::find(vars.begin(), vars.end(),
"temp") != vars.end());
2100 DOCTEST_CHECK(std::find(vars.begin(), vars.end(),
"humidity") != vars.end());
2103 SUBCASE(
"clearTimeseriesData") {
2109 ctx.addTimeseriesData(
"temp", 25.5f, date, time);
2110 ctx.addTimeseriesData(
"humidity", 60.0f, date, time);
2111 DOCTEST_CHECK(ctx.listTimeseriesVariables().size() == 2);
2112 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"temp"));
2115 ctx.clearTimeseriesData();
2116 DOCTEST_CHECK(ctx.listTimeseriesVariables().empty());
2117 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"temp"));
2118 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"humidity"));
2121 ctx.clearTimeseriesData();
2122 DOCTEST_CHECK(ctx.listTimeseriesVariables().empty());
2125 SUBCASE(
"deleteTimeseriesVariable") {
2131 ctx.addTimeseriesData(
"temp", 25.5f, date, time0);
2132 ctx.addTimeseriesData(
"temp", 26.5f, date, time1);
2133 ctx.addTimeseriesData(
"humidity", 60.0f, date, time0);
2134 DOCTEST_CHECK(ctx.listTimeseriesVariables().size() == 2);
2137 ctx.deleteTimeseriesVariable(
"temp");
2138 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"temp"));
2139 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"humidity"));
2140 DOCTEST_CHECK(ctx.getTimeseriesLength(
"humidity") == 1);
2141 DOCTEST_CHECK(ctx.queryTimeseriesData(
"humidity", 0) == doctest::Approx(60.0f));
2144 ctx.addTimeseriesData(
"temp", 99.9f, date, time0);
2145 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"temp"));
2146 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 1);
2147 DOCTEST_CHECK(ctx.queryTimeseriesData(
"temp", 0) == doctest::Approx(99.9f));
2150 std::string captured;
2152 capture_cerr cerr_buffer;
2153 DOCTEST_CHECK_NOTHROW(ctx.deleteTimeseriesVariable(
"nonexistent"));
2154 captured = cerr_buffer.get_captured_output();
2156 DOCTEST_CHECK(captured.find(
"WARNING") != std::string::npos);
2157 DOCTEST_CHECK(captured.find(
"nonexistent") != std::string::npos);
2160 SUBCASE(
"getUniquePrimitiveParentObjectIDs") {
2163 uint obj2 = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2165 std::vector<uint> all_prims = ctx.getAllUUIDs();
2166 std::vector<uint> obj_ids = ctx.getUniquePrimitiveParentObjectIDs(all_prims);
2167 DOCTEST_CHECK(obj_ids.size() >= 2);
2168 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj1) != obj_ids.end());
2169 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj2) != obj_ids.end());
2173TEST_CASE(
"Comprehensive Coverage Tests") {
2174 SUBCASE(
"additional object operations with vectors") {
2176 std::vector<uint> obj_ids;
2180 std::vector<uint> all_uuids = ctx.getObjectPrimitiveUUIDs(obj_ids);
2181 DOCTEST_CHECK(all_uuids.size() == 12);
2183 std::vector<std::vector<uint>> nested_obj_ids = {{obj_ids[0]}, {obj_ids[1]}};
2184 std::vector<uint> nested_uuids = ctx.getObjectPrimitiveUUIDs(nested_obj_ids);
2185 DOCTEST_CHECK(nested_uuids.size() == 12);
2187 ctx.hideObject(obj_ids);
2188 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[0]));
2189 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[1]));
2191 ctx.showObject(obj_ids);
2192 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[0]));
2193 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[1]));
2196 SUBCASE(
"object texture color overrides") {
2198 std::vector<uint> obj_ids;
2201 ctx.overrideObjectTextureColor(obj_ids);
2202 ctx.useObjectTextureColor(obj_ids);
2206TEST_CASE(
"getAllUUIDs Cache Performance") {
2207 SUBCASE(
"Cache invalidation on primitive add/delete") {
2211 std::vector<uint> empty_uuids = ctx.getAllUUIDs();
2212 DOCTEST_CHECK(empty_uuids.empty());
2215 uint p1 = ctx.addPatch();
2216 std::vector<uint> one_uuid = ctx.getAllUUIDs();
2217 DOCTEST_CHECK(one_uuid.size() == 1);
2218 DOCTEST_CHECK(one_uuid[0] == p1);
2221 std::vector<uint> same_uuid = ctx.getAllUUIDs();
2222 DOCTEST_CHECK(same_uuid.size() == 1);
2223 DOCTEST_CHECK(same_uuid[0] == p1);
2229 std::vector<uint> three_uuids = ctx.getAllUUIDs();
2230 DOCTEST_CHECK(three_uuids.size() == 3);
2231 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), p1) != three_uuids.end());
2232 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), t1) != three_uuids.end());
2233 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), v1) != three_uuids.end());
2236 ctx.deletePrimitive(t1);
2237 std::vector<uint> two_uuids = ctx.getAllUUIDs();
2238 DOCTEST_CHECK(two_uuids.size() == 2);
2239 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), t1) == two_uuids.end());
2240 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), p1) != two_uuids.end());
2241 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), v1) != two_uuids.end());
2244 SUBCASE(
"Cache invalidation on hide/show primitives") {
2246 uint p1 = ctx.addPatch();
2247 uint p2 = ctx.addPatch();
2248 uint p3 = ctx.addPatch();
2251 std::vector<uint> all_visible = ctx.getAllUUIDs();
2252 DOCTEST_CHECK(all_visible.size() == 3);
2255 ctx.hidePrimitive(p2);
2256 std::vector<uint> two_visible = ctx.getAllUUIDs();
2257 DOCTEST_CHECK(two_visible.size() == 2);
2258 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p2) == two_visible.end());
2259 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p1) != two_visible.end());
2260 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p3) != two_visible.end());
2263 std::vector<uint> to_hide = {p1, p3};
2264 ctx.hidePrimitive(to_hide);
2265 std::vector<uint> none_visible = ctx.getAllUUIDs();
2266 DOCTEST_CHECK(none_visible.empty());
2269 ctx.showPrimitive(p1);
2270 std::vector<uint> one_visible = ctx.getAllUUIDs();
2271 DOCTEST_CHECK(one_visible.size() == 1);
2272 DOCTEST_CHECK(one_visible[0] == p1);
2275 std::vector<uint> to_show = {p2, p3};
2276 ctx.showPrimitive(to_show);
2277 std::vector<uint> all_back = ctx.getAllUUIDs();
2278 DOCTEST_CHECK(all_back.size() == 3);
2281 SUBCASE(
"Cache invalidation on copy primitives") {
2283 uint original = ctx.addPatch();
2285 std::vector<uint> before_copy = ctx.getAllUUIDs();
2286 DOCTEST_CHECK(before_copy.size() == 1);
2288 uint copied = ctx.copyPrimitive(original);
2289 std::vector<uint> after_copy = ctx.getAllUUIDs();
2290 DOCTEST_CHECK(after_copy.size() == 2);
2291 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), original) != after_copy.end());
2292 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), copied) != after_copy.end());
2295 std::vector<uint> originals = {original, copied};
2296 std::vector<uint> copies = ctx.copyPrimitive(originals);
2297 std::vector<uint> after_multi_copy = ctx.getAllUUIDs();
2298 DOCTEST_CHECK(after_multi_copy.size() == 4);
2299 for (
uint copy_id: copies) {
2300 DOCTEST_CHECK(std::find(after_multi_copy.begin(), after_multi_copy.end(), copy_id) != after_multi_copy.end());
2304 SUBCASE(
"Cache consistency across mixed operations") {
2308 uint p1 = ctx.addPatch();
2311 std::vector<uint> step1 = ctx.getAllUUIDs();
2312 DOCTEST_CHECK(step1.size() == 2);
2314 ctx.hidePrimitive(p1);
2315 std::vector<uint> step2 = ctx.getAllUUIDs();
2316 DOCTEST_CHECK(step2.size() == 1);
2317 DOCTEST_CHECK(step2[0] == p2);
2320 std::vector<uint> step3 = ctx.getAllUUIDs();
2321 DOCTEST_CHECK(step3.size() == 2);
2323 ctx.showPrimitive(p1);
2324 std::vector<uint> step4 = ctx.getAllUUIDs();
2325 DOCTEST_CHECK(step4.size() == 3);
2327 ctx.deletePrimitive(p2);
2328 std::vector<uint> step5 = ctx.getAllUUIDs();
2329 DOCTEST_CHECK(step5.size() == 2);
2330 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p2) == step5.end());
2331 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p1) != step5.end());
2332 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p3) != step5.end());
2336TEST_CASE(
"Error Handling") {
2337 SUBCASE(
"Context error handling") {
2338 Context context_test;
2340 capture_cerr cerr_buffer;
2343 DOCTEST_CHECK_THROWS_AS(center = context_test.getPatchCenter(tri), std::runtime_error);
2347 std::vector<uint> vlist{vox};
2348 DOCTEST_CHECK_THROWS_AS(context_test.rotatePrimitive(vlist,
PI_F / 4.f,
"a"), std::runtime_error);
2352TEST_CASE(
"Zero Area Triangle Detection") {
2353 SUBCASE(
"addTubeObject with nearly identical vertices should not create zero-area triangles") {
2357 std::vector<vec3> nodes = {
make_vec3(0.300000012f, -0.112000048f, 0.00999999978f),
make_vec3(0.29995966f, -0.111979447f, 0.0109989736f),
make_vec3(0.299919307f, -0.111958846f, 0.0119979475f)};
2359 std::vector<float> radii = {0.000500000024f, 0.000500000024f, 0.000500000024f};
2360 std::vector<RGBcolor> colors = {RGB::green, RGB::green, RGB::green};
2363 uint tube_obj = ctx.addTubeObject(7, nodes, radii, colors);
2366 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
2369 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
2370 DOCTEST_CHECK(tube_primitives.size() > 0);
2372 for (
uint uuid: tube_primitives) {
2373 float area = ctx.getPrimitiveArea(uuid);
2374 DOCTEST_CHECK(area > 0.0f);
2375 DOCTEST_CHECK(area > 1e-12f);
2379 SUBCASE(
"addTubeObject with extremely small displacements") {
2385 std::vector<float> radii = {1e-4f, 1e-4f, 1e-4f};
2387 uint tube_obj = ctx.addTubeObject(6, nodes, radii);
2388 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
2390 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
2391 for (
uint uuid: tube_primitives) {
2392 float area = ctx.getPrimitiveArea(uuid);
2393 DOCTEST_CHECK(area > 0.0f);
2398TEST_CASE(
"Transparent Texture Zero Area Validation") {
2399 SUBCASE(
"addSphere with transparent texture should filter zero-area triangles") {
2403 std::vector<uint> sphere_uuids = ctx.addSphere(20,
make_vec3(0, 0, 0), 1.0f,
"lib/images/diamond_texture.png");
2406 DOCTEST_CHECK(sphere_uuids.size() > 0);
2407 for (
uint uuid: sphere_uuids) {
2408 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2409 float area = ctx.getPrimitiveArea(uuid);
2410 DOCTEST_CHECK(area > 0.0f);
2414 std::vector<uint> sphere_disk_uuids = ctx.addSphere(30,
make_vec3(2, 0, 0), 1.0f,
"lib/images/disk_texture.png");
2416 DOCTEST_CHECK(sphere_disk_uuids.size() > 0);
2417 for (
uint uuid: sphere_disk_uuids) {
2418 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2419 float area = ctx.getPrimitiveArea(uuid);
2420 DOCTEST_CHECK(area > 0.0f);
2424 int zero_area_count_diamond = 0;
2425 for (
uint uuid: sphere_uuids) {
2426 float area = ctx.getPrimitiveArea(uuid);
2428 zero_area_count_diamond++;
2431 DOCTEST_CHECK(zero_area_count_diamond == 0);
2433 int zero_area_count_disk = 0;
2434 for (
uint uuid: sphere_disk_uuids) {
2435 float area = ctx.getPrimitiveArea(uuid);
2437 zero_area_count_disk++;
2440 DOCTEST_CHECK(zero_area_count_disk == 0);
2443 std::vector<uint> solid_sphere_uuids = ctx.addSphere(20,
make_vec3(4, 0, 0), 1.0f, RGB::green);
2445 int zero_area_count_solid = 0;
2446 for (
uint uuid: solid_sphere_uuids) {
2447 float area = ctx.getPrimitiveArea(uuid);
2449 zero_area_count_solid++;
2452 DOCTEST_CHECK(zero_area_count_solid == 0);
2455 SUBCASE(
"texture transparency validation preserves object integrity") {
2459 std::vector<uint> sphere_uuids = ctx.addSphere(15,
make_vec3(0, 0, 0), 1.0f,
"lib/images/diamond_texture.png");
2462 for (
uint uuid: sphere_uuids) {
2463 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2464 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
2466 float area = ctx.getPrimitiveArea(uuid);
2467 DOCTEST_CHECK(area > 0.0f);
2468 DOCTEST_CHECK(area > 1e-10f);
2471 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2472 DOCTEST_CHECK(solid_fraction > 0.0f);
2473 DOCTEST_CHECK(solid_fraction <= 1.0f);
2477 std::vector<uint> all_uuids = ctx.getAllUUIDs();
2478 int total_zero_area = 0;
2479 int total_negative_area = 0;
2481 for (
uint uuid: all_uuids) {
2482 float area = ctx.getPrimitiveArea(uuid);
2487 total_negative_area++;
2492 DOCTEST_CHECK(total_zero_area == 0);
2493 DOCTEST_CHECK(total_negative_area == 0);
2496 for (
uint uuid: sphere_uuids) {
2497 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2498 DOCTEST_CHECK(solid_fraction >= 0.0f);
2499 DOCTEST_CHECK(solid_fraction <= 1.0f);
2502 if (ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE) {
2503 vec3 v0 = ctx.getTriangleVertex(uuid, 0);
2504 vec3 v1 = ctx.getTriangleVertex(uuid, 1);
2505 vec3 v2 = ctx.getTriangleVertex(uuid, 2);
2507 float effective_area = ctx.getPrimitiveArea(uuid);
2510 DOCTEST_CHECK(effective_area <= geometric_area + 1e-6f);
2511 DOCTEST_CHECK(effective_area > 0.0f);
2516 DOCTEST_SUBCASE(
"Test Other Primitive Methods Zero Area Validation") {
2521 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2522 std::vector<uint> tube_uuids = ctx_other.addTube(8, tube_nodes, tube_radii,
"lib/images/diamond_texture.png");
2525 int tube_positive_area = 0, tube_zero_area = 0;
2526 for (
uint uuid: tube_uuids) {
2527 float area = ctx_other.getPrimitiveArea(uuid);
2528 DOCTEST_CHECK(area >= 0.0f);
2530 tube_positive_area++;
2536 DOCTEST_CHECK(tube_positive_area > 0);
2537 DOCTEST_CHECK(tube_zero_area == 0);
2543 int disk_positive_area = 0, disk_zero_area = 0;
2544 for (
uint uuid: disk_uuids) {
2545 float area = ctx_other.getPrimitiveArea(uuid);
2546 DOCTEST_CHECK(area >= 0.0f);
2548 disk_positive_area++;
2554 DOCTEST_CHECK(disk_positive_area > 0);
2555 DOCTEST_CHECK(disk_zero_area == 0);
2558 std::vector<uint> cone_uuids = ctx_other.addCone(8,
make_vec3(0, 0, 0),
make_vec3(0, 0, 1), 0.1f, 0.2f,
"lib/images/diamond_texture.png");
2561 int cone_positive_area = 0, cone_zero_area = 0;
2562 for (
uint uuid: cone_uuids) {
2563 float area = ctx_other.getPrimitiveArea(uuid);
2564 DOCTEST_CHECK(area >= 0.0f);
2566 cone_positive_area++;
2572 DOCTEST_CHECK(cone_positive_area > 0);
2573 DOCTEST_CHECK(cone_zero_area == 0);
2579 int tile_positive_area = 0, tile_zero_area = 0;
2580 for (
uint uuid: tile_uuids) {
2581 float area = ctx_other.getPrimitiveArea(uuid);
2582 DOCTEST_CHECK(area >= 0.0f);
2584 tile_positive_area++;
2590 DOCTEST_CHECK(tile_positive_area > 0);
2591 DOCTEST_CHECK(tile_zero_area == 0);
2595 DOCTEST_SUBCASE(
"Test Compound Object Methods Zero Area Validation") {
2596 Context ctx_compound;
2599 uint sphere_obj = ctx_compound.addSphereObject(8,
make_vec3(0, 0, 0), 0.5f,
"lib/images/diamond_texture.png");
2600 std::vector<uint> sphere_primitives = ctx_compound.getObjectPrimitiveUUIDs(sphere_obj);
2603 int sphere_positive_area = 0, sphere_zero_area = 0;
2604 for (
uint uuid: sphere_primitives) {
2605 float area = ctx_compound.getPrimitiveArea(uuid);
2606 DOCTEST_CHECK(area >= 0.0f);
2608 sphere_positive_area++;
2614 DOCTEST_CHECK(sphere_positive_area > 0);
2615 DOCTEST_CHECK(sphere_zero_area == 0);
2619 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2620 uint tube_obj = ctx_compound.addTubeObject(8, tube_nodes, tube_radii,
"lib/images/diamond_texture.png");
2621 std::vector<uint> tube_primitives = ctx_compound.getObjectPrimitiveUUIDs(tube_obj);
2624 int tube_positive_area = 0, tube_zero_area = 0;
2625 for (
uint uuid: tube_primitives) {
2626 float area = ctx_compound.getPrimitiveArea(uuid);
2627 DOCTEST_CHECK(area >= 0.0f);
2629 tube_positive_area++;
2635 DOCTEST_CHECK(tube_positive_area > 0);
2636 DOCTEST_CHECK(tube_zero_area == 0);
2640 std::vector<uint> disk_primitives = ctx_compound.getObjectPrimitiveUUIDs(disk_obj);
2643 int disk_positive_area = 0, disk_zero_area = 0;
2644 for (
uint uuid: disk_primitives) {
2645 float area = ctx_compound.getPrimitiveArea(uuid);
2646 DOCTEST_CHECK(area >= 0.0f);
2648 disk_positive_area++;
2654 DOCTEST_CHECK(disk_positive_area > 0);
2655 DOCTEST_CHECK(disk_zero_area == 0);
2658 uint cone_obj = ctx_compound.addConeObject(8,
make_vec3(0, 0, 0),
make_vec3(0, 0, 1), 0.1f, 0.2f,
"lib/images/diamond_texture.png");
2659 std::vector<uint> cone_primitives = ctx_compound.getObjectPrimitiveUUIDs(cone_obj);
2662 int cone_positive_area = 0, cone_zero_area = 0;
2663 for (
uint uuid: cone_primitives) {
2664 float area = ctx_compound.getPrimitiveArea(uuid);
2665 DOCTEST_CHECK(area >= 0.0f);
2667 cone_positive_area++;
2673 DOCTEST_CHECK(cone_positive_area > 0);
2674 DOCTEST_CHECK(cone_zero_area == 0);
2679TEST_CASE(
"File path resolution priority") {
2680 SUBCASE(
"resolveFilePath current directory priority") {
2685 std::string testFileName =
"test_file_resolution.jpg";
2686 std::filesystem::path currentDirFile = std::filesystem::current_path() / testFileName;
2689 std::filesystem::path sourceTexture =
"core/lib/models/texture.jpg";
2691 if (std::filesystem::exists(sourceTexture)) {
2693 std::filesystem::copy_file(sourceTexture, currentDirFile, std::filesystem::copy_options::overwrite_existing);
2694 DOCTEST_CHECK(std::filesystem::exists(currentDirFile));
2698 DOCTEST_CHECK(resolved == std::filesystem::canonical(currentDirFile));
2701 std::filesystem::remove(currentDirFile);
2705 SUBCASE(
"addPatch with texture from current directory") {
2709 std::filesystem::create_directories(
"test_models");
2710 std::string testTexture =
"test_models/test_texture.jpg";
2711 std::filesystem::path testTexturePath = std::filesystem::current_path() / testTexture;
2714 std::filesystem::path sourceTexture =
"core/lib/models/texture.jpg";
2716 if (std::filesystem::exists(sourceTexture)) {
2717 std::filesystem::copy_file(sourceTexture, testTexturePath, std::filesystem::copy_options::overwrite_existing);
2723 DOCTEST_CHECK_NOTHROW({ patch_id = ctx.addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1), rotation, testTexture.c_str()); });
2724 DOCTEST_CHECK(patch_id > 0);
2727 bool has_transparency = ctx.primitiveTextureHasTransparencyChannel(patch_id);
2728 DOCTEST_CHECK((has_transparency || !has_transparency));
2731 std::filesystem::remove(testTexturePath);
2732 std::filesystem::remove(
"test_models");
2736 SUBCASE(
"Material System - Label-Based Creation") {
2740 DOCTEST_CHECK(ctx.doesMaterialExist(
"__default__"));
2741 DOCTEST_CHECK(ctx.getMaterialCount() == 0);
2744 ctx.addMaterial(
"leaf_material");
2745 DOCTEST_CHECK(ctx.doesMaterialExist(
"leaf_material"));
2746 DOCTEST_CHECK(ctx.getMaterialCount() == 1);
2748 ctx.addMaterial(
"bark_material");
2749 DOCTEST_CHECK(ctx.doesMaterialExist(
"bark_material"));
2750 DOCTEST_CHECK(ctx.getMaterialCount() == 2);
2753 std::vector<std::string> labels = ctx.listMaterials();
2754 DOCTEST_CHECK(labels.size() == 2);
2757 DOCTEST_CHECK_THROWS(ctx.addMaterial(
"__reserved"));
2760 SUBCASE(
"Material System - Rename") {
2764 uint UUID = ctx.addTriangle(
make_vec3(0, 0, 0),
make_vec3(1, 0, 0),
make_vec3(0, 1, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
make_vec2(0, 1));
2765 std::string auto_label = ctx.getPrimitiveMaterialLabel(UUID);
2766 DOCTEST_CHECK(auto_label.substr(0, 7) ==
"__auto_");
2767 DOCTEST_CHECK(ctx.doesMaterialExist(auto_label));
2770 ctx.renameMaterial(auto_label,
"bean_trifoliate_leaf");
2771 DOCTEST_CHECK(ctx.doesMaterialExist(
"bean_trifoliate_leaf"));
2773 DOCTEST_CHECK(ctx.doesMaterialExist(auto_label));
2776 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(UUID) ==
"bean_trifoliate_leaf");
2779 std::string tex = ctx.getMaterialTexture(
"bean_trifoliate_leaf");
2780 DOCTEST_CHECK(tex ==
"lib/images/disk_texture.png");
2783 uint UUID2 = ctx.addTriangle(
make_vec3(2, 0, 0),
make_vec3(3, 0, 0),
make_vec3(2, 1, 0),
"lib/images/disk_texture.png",
make_vec2(0, 0),
make_vec2(1, 0),
make_vec2(0, 1));
2784 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(UUID2) ==
"bean_trifoliate_leaf");
2787 ctx.addMaterial(
"old_name");
2788 ctx.renameMaterial(
"old_name",
"new_name");
2789 DOCTEST_CHECK(ctx.doesMaterialExist(
"new_name"));
2790 DOCTEST_CHECK(!ctx.doesMaterialExist(
"old_name"));
2793 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"nonexistent",
"new_name2"));
2794 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
"__reserved"));
2795 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
"new_name"));
2796 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
""));
2799 SUBCASE(
"Material System - Properties") {
2803 ctx.addMaterial(
"test_mat");
2806 ctx.setMaterialColor(
"test_mat", purple);
2808 RGBAcolor color = ctx.getMaterialColor(
"test_mat");
2809 DOCTEST_CHECK(color.r == doctest::Approx(0.5f).epsilon(0.001));
2810 DOCTEST_CHECK(color.g == doctest::Approx(0.0f).epsilon(0.001));
2811 DOCTEST_CHECK(color.b == doctest::Approx(0.5f).epsilon(0.001));
2814 ctx.setMaterialTexture(
"test_mat",
"lib/images/disk_texture.png");
2815 std::string tex = ctx.getMaterialTexture(
"test_mat");
2816 DOCTEST_CHECK(tex ==
"lib/images/disk_texture.png");
2819 ctx.setMaterialTextureColorOverride(
"test_mat",
true);
2820 DOCTEST_CHECK(ctx.isMaterialTextureColorOverridden(
"test_mat"));
2822 ctx.setMaterialTextureColorOverride(
"test_mat",
false);
2823 DOCTEST_CHECK(!ctx.isMaterialTextureColorOverridden(
"test_mat"));
2826 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 1);
2829 ctx.setMaterialTwosidedFlag(
"test_mat", 0);
2830 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 0);
2833 ctx.setMaterialTwosidedFlag(
"test_mat", 1);
2834 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 1);
2837 SUBCASE(
"Material System - Assignment to Primitives") {
2841 ctx.addMaterial(
"red_mat");
2849 ctx.assignMaterialToPrimitive(p1,
"red_mat");
2850 ctx.assignMaterialToPrimitive(p2,
"red_mat");
2853 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p1) ==
"red_mat");
2854 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p2) ==
"red_mat");
2857 RGBcolor c1 = ctx.getPrimitiveColor(p1);
2858 DOCTEST_CHECK(c1.r == doctest::Approx(1.0f).epsilon(0.001));
2859 DOCTEST_CHECK(c1.g == doctest::Approx(0.0f).epsilon(0.001));
2864 c1 = ctx.getPrimitiveColor(p1);
2865 RGBcolor c2 = ctx.getPrimitiveColor(p2);
2866 DOCTEST_CHECK(c1.g == doctest::Approx(1.0f).epsilon(0.001));
2867 DOCTEST_CHECK(c2.g == doctest::Approx(1.0f).epsilon(0.001));
2870 std::vector<uint> users = ctx.getPrimitivesUsingMaterial(
"red_mat");
2871 DOCTEST_CHECK(users.size() == 2);
2874 SUBCASE(
"Material System - Batch Assignment") {
2877 ctx.addMaterial(
"batch_mat");
2878 ctx.setMaterialColor(
"batch_mat",
make_RGBAcolor(0.5f, 0.5f, 0.5f, 1));
2880 std::vector<uint> UUIDs;
2881 for (
int i = 0; i < 10; i++) {
2886 ctx.assignMaterialToPrimitive(UUIDs,
"batch_mat");
2889 for (
uint uuid: UUIDs) {
2890 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(uuid) ==
"batch_mat");
2894 SUBCASE(
"Material System - Deletion") {
2897 ctx.addMaterial(
"temp_mat");
2901 ctx.assignMaterialToPrimitive(p1,
"temp_mat");
2905 ctx.deleteMaterial(
"temp_mat");
2907 DOCTEST_CHECK(!ctx.doesMaterialExist(
"temp_mat"));
2908 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p1) ==
"__default__");
2911 SUBCASE(
"Material System - XML Round-Trip") {
2915 ctx.addMaterial(
"red_mat");
2918 ctx.addMaterial(
"textured_mat");
2919 ctx.setMaterialColor(
"textured_mat",
make_RGBAcolor(0, 1, 0, 1));
2920 ctx.setMaterialTexture(
"textured_mat",
"lib/images/disk_texture.png");
2923 ctx.addMaterial(
"onesided_mat");
2924 ctx.setMaterialColor(
"onesided_mat",
make_RGBAcolor(0, 0, 1, 1));
2925 ctx.setMaterialTwosidedFlag(
"onesided_mat", 0);
2933 ctx.assignMaterialToPrimitive(p1,
"red_mat");
2934 ctx.assignMaterialToPrimitive(p2,
"textured_mat");
2935 ctx.assignMaterialToPrimitive(p3,
"red_mat");
2936 ctx.assignMaterialToPrimitive(p4,
"onesided_mat");
2939 ctx.writeXML(
"test_materials.xml", {p1, p2, p3, p4},
true);
2943 std::vector<uint> loaded_UUIDs = ctx2.loadXML(
"test_materials.xml",
true);
2945 DOCTEST_CHECK(loaded_UUIDs.size() == 4);
2948 DOCTEST_CHECK(ctx2.doesMaterialExist(
"red_mat"));
2949 DOCTEST_CHECK(ctx2.doesMaterialExist(
"textured_mat"));
2950 DOCTEST_CHECK(ctx2.doesMaterialExist(
"onesided_mat"));
2952 RGBcolor loaded_color1 = ctx2.getPrimitiveColor(loaded_UUIDs[0]);
2953 DOCTEST_CHECK(loaded_color1.r == doctest::Approx(1.0f).epsilon(0.001));
2955 DOCTEST_CHECK(ctx2.getPrimitiveTextureFile(loaded_UUIDs[1]) ==
"lib/images/disk_texture.png");
2958 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"red_mat") == 1);
2959 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"textured_mat") == 1);
2960 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"onesided_mat") == 0);
2963 std::filesystem::remove(
"test_materials.xml");
2966 SUBCASE(
"getPrimitiveTwosidedFlag helper function") {
2970 ctx.addMaterial(
"onesided_mat");
2971 ctx.setMaterialTwosidedFlag(
"onesided_mat", 0);
2973 ctx.addMaterial(
"twosided_mat");
2974 ctx.setMaterialTwosidedFlag(
"twosided_mat", 1);
2976 ctx.addMaterial(
"transparent_mat");
2977 ctx.setMaterialTwosidedFlag(
"transparent_mat", 2);
2987 ctx.assignMaterialToPrimitive(UUID_mat_onesided,
"onesided_mat");
2988 ctx.assignMaterialToPrimitive(UUID_mat_twosided,
"twosided_mat");
2989 ctx.assignMaterialToPrimitive(UUID_mat_transparent,
"transparent_mat");
2992 ctx.setPrimitiveData(UUID_prim_data,
"twosided_flag",
uint(0));
2995 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_onesided) == 0);
2998 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_twosided) == 1);
3001 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_transparent) == 2);
3004 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_prim_data) == 0);
3007 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_default) == 1);
3010 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_default, 2) == 2);
3014 ctx.setPrimitiveData(UUID_mat_onesided,
"twosided_flag",
uint(1));
3015 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_onesided) == 0);
3018 SUBCASE(
"Material Data - Setting and Getting with Labels") {
3022 ctx.addMaterial(
"data_mat");
3025 ctx.setMaterialData(
"data_mat",
"twosided_flag", 1u);
3026 DOCTEST_CHECK(ctx.doesMaterialDataExist(
"data_mat",
"twosided_flag"));
3027 DOCTEST_CHECK(ctx.getMaterialDataType(
"data_mat",
"twosided_flag") == HELIOS_TYPE_UINT);
3029 ctx.getMaterialData(
"data_mat",
"twosided_flag", flag_val);
3030 DOCTEST_CHECK(flag_val == 1u);
3033 ctx.setMaterialData(
"data_mat",
"test_int", -42);
3035 ctx.getMaterialData(
"data_mat",
"test_int", int_val);
3036 DOCTEST_CHECK(int_val == -42);
3039 ctx.setMaterialData(
"data_mat",
"test_float", 3.14f);
3041 ctx.getMaterialData(
"data_mat",
"test_float", float_val);
3042 DOCTEST_CHECK(float_val == doctest::Approx(3.14f).epsilon(0.001));
3046 ctx.setMaterialData(
"data_mat",
"test_vec3", test_vec);
3048 ctx.getMaterialData(
"data_mat",
"test_vec3", vec_val);
3049 DOCTEST_CHECK(vec_val.x == doctest::Approx(1.0f).epsilon(0.001));
3050 DOCTEST_CHECK(vec_val.y == doctest::Approx(2.0f).epsilon(0.001));
3051 DOCTEST_CHECK(vec_val.z == doctest::Approx(3.0f).epsilon(0.001));
3054 ctx.setMaterialData(
"data_mat",
"test_string", std::string(
"hello"));
3055 std::string str_val;
3056 ctx.getMaterialData(
"data_mat",
"test_string", str_val);
3057 DOCTEST_CHECK(str_val ==
"hello");
3060 ctx.clearMaterialData(
"data_mat",
"test_int");
3061 DOCTEST_CHECK(!ctx.doesMaterialDataExist(
"data_mat",
"test_int"));
3064 SUBCASE(
"Material Data - Fallback Helper Method") {
3068 ctx.addMaterial(
"fallback_mat");
3069 ctx.setMaterialData(
"fallback_mat",
"twosided_flag", 0u);
3073 ctx.assignMaterialToPrimitive(p1,
"fallback_mat");
3077 ctx.getDataWithMaterialFallback(p1,
"twosided_flag", flag_val);
3078 DOCTEST_CHECK(flag_val == 0u);
3082 ctx.assignMaterialToPrimitive(p2,
"fallback_mat");
3083 ctx.setPrimitiveData(p2,
"custom_data", 42);
3087 ctx.getDataWithMaterialFallback(p2,
"custom_data", custom_val);
3088 DOCTEST_CHECK(custom_val == 42);
3092 ctx.assignMaterialToPrimitive(p3,
"fallback_mat");
3095 int nonexistent_val;
3096 DOCTEST_CHECK_THROWS(ctx.getDataWithMaterialFallback(p3,
"nonexistent", nonexistent_val));
3099 SUBCASE(
"Material Data - XML Round-Trip with Labels") {
3103 ctx.addMaterial(
"data_round_trip_mat");
3104 ctx.setMaterialColor(
"data_round_trip_mat",
make_RGBAcolor(0.5f, 0.25f, 0.75f, 1));
3105 ctx.setMaterialData(
"data_round_trip_mat",
"twosided_flag", 1u);
3106 ctx.setMaterialData(
"data_round_trip_mat",
"reflectance", 0.8f);
3107 ctx.setMaterialData(
"data_round_trip_mat",
"normal",
make_vec3(0, 0, 1));
3112 ctx.assignMaterialToPrimitive(p1,
"data_round_trip_mat");
3113 ctx.assignMaterialToPrimitive(p2,
"data_round_trip_mat");
3116 ctx.writeXML(
"test_material_data.xml",
true);
3120 ctx2.loadXML(
"test_material_data.xml",
true);
3123 DOCTEST_CHECK(ctx2.doesMaterialExist(
"data_round_trip_mat"));
3125 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"twosided_flag"));
3127 ctx2.getMaterialData(
"data_round_trip_mat",
"twosided_flag", flag_val);
3128 DOCTEST_CHECK(flag_val == 1u);
3130 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"reflectance"));
3132 ctx2.getMaterialData(
"data_round_trip_mat",
"reflectance", refl_val);
3133 DOCTEST_CHECK(refl_val == doctest::Approx(0.8f).epsilon(0.001));
3135 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"normal"));
3137 ctx2.getMaterialData(
"data_round_trip_mat",
"normal", norm_val);
3138 DOCTEST_CHECK(norm_val.x == doctest::Approx(0.0f).epsilon(0.001));
3139 DOCTEST_CHECK(norm_val.y == doctest::Approx(0.0f).epsilon(0.001));
3140 DOCTEST_CHECK(norm_val.z == doctest::Approx(1.0f).epsilon(0.001));
3143 std::filesystem::remove(
"test_material_data.xml");
3146 SUBCASE(
"Material Methods - getPrimitiveMaterialID and getMaterial") {
3150 ctx.addMaterial(
"test_mat_1");
3152 uint mat1_id = ctx.getMaterialIDFromLabel(
"test_mat_1");
3154 ctx.addMaterial(
"test_mat_2");
3156 uint mat2_id = ctx.getMaterialIDFromLabel(
"test_mat_2");
3163 ctx.assignMaterialToPrimitive(p1,
"test_mat_1");
3164 ctx.assignMaterialToPrimitive(p2,
"test_mat_2");
3165 ctx.assignMaterialToPrimitive(p3,
"test_mat_1");
3168 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p1) == mat1_id);
3169 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p2) == mat2_id);
3170 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p3) == mat1_id);
3173 const Material &mat1 = ctx.getMaterial(mat1_id);
3174 DOCTEST_CHECK(mat1.label ==
"test_mat_1");
3175 DOCTEST_CHECK(mat1.color.r == doctest::Approx(1.0f));
3176 DOCTEST_CHECK(mat1.color.g == doctest::Approx(0.0f));
3177 DOCTEST_CHECK(mat1.color.b == doctest::Approx(0.0f));
3179 const Material &mat2 = ctx.getMaterial(mat2_id);
3180 DOCTEST_CHECK(mat2.label ==
"test_mat_2");
3181 DOCTEST_CHECK(mat2.color.r == doctest::Approx(0.0f));
3182 DOCTEST_CHECK(mat2.color.g == doctest::Approx(1.0f));
3183 DOCTEST_CHECK(mat2.color.b == doctest::Approx(0.0f));
3186 DOCTEST_CHECK_THROWS((
void) ctx.getMaterial(99999));
3189 SUBCASE(
"Material Methods - getMaterialIDFromLabel") {
3193 ctx.addMaterial(
"material_a");
3194 ctx.addMaterial(
"material_b");
3195 ctx.addMaterial(
"material_c");
3198 uint id_a = ctx.getMaterialIDFromLabel(
"material_a");
3199 uint id_b = ctx.getMaterialIDFromLabel(
"material_b");
3200 uint id_c = ctx.getMaterialIDFromLabel(
"material_c");
3203 DOCTEST_CHECK(id_a != id_b);
3204 DOCTEST_CHECK(id_b != id_c);
3205 DOCTEST_CHECK(id_a != id_c);
3208 DOCTEST_CHECK(ctx.getMaterialIDFromLabel(
"material_a") == id_a);
3209 DOCTEST_CHECK(ctx.getMaterialIDFromLabel(
"material_b") == id_b);
3212 DOCTEST_CHECK_THROWS((
void) ctx.getMaterialIDFromLabel(
"nonexistent_material"));
3215 SUBCASE(
"Material copy-on-write - basic color modification") {
3223 std::string mat1_before = context.getPrimitiveMaterialLabel(uuid1);
3224 std::string mat2_before = context.getPrimitiveMaterialLabel(uuid2);
3225 DOCTEST_CHECK(mat1_before == mat2_before);
3228 context.setPrimitiveColor(uuid1, RGB::blue);
3231 std::string mat1_after = context.getPrimitiveMaterialLabel(uuid1);
3232 std::string mat2_after = context.getPrimitiveMaterialLabel(uuid2);
3233 DOCTEST_CHECK(mat1_after != mat2_after);
3236 RGBcolor color1 = context.getPrimitiveColor(uuid1);
3237 RGBcolor color2 = context.getPrimitiveColor(uuid2);
3238 DOCTEST_CHECK(color1 == RGB::blue);
3239 DOCTEST_CHECK(color2 == RGB::red);
3242 SUBCASE(
"Material copy-on-write - object-level modification") {
3246 uint obj1 = context.addSphereObject(10,
make_vec3(0, 0, 0), 1.f, RGB::green);
3247 uint obj2 = context.addSphereObject(10,
make_vec3(3, 0, 0), 1.f, RGB::green);
3250 context.setObjectColor(obj1, RGB::yellow);
3253 auto prims1 = context.getObjectPrimitiveUUIDs(obj1);
3254 auto prims2 = context.getObjectPrimitiveUUIDs(obj2);
3256 RGBcolor color1 = context.getPrimitiveColor(prims1[0]);
3257 RGBcolor color2 = context.getPrimitiveColor(prims2[0]);
3259 DOCTEST_CHECK(color1 == RGB::yellow);
3260 DOCTEST_CHECK(color2 == RGB::green);
3263 SUBCASE(
"Material copy-on-write - non-shared optimization") {
3269 std::string mat1 = context.getPrimitiveMaterialLabel(uuid);
3272 context.setPrimitiveColor(uuid, RGB::magenta);
3274 std::string mat2 = context.getPrimitiveMaterialLabel(uuid);
3277 DOCTEST_CHECK(mat1 == mat2);
3281TEST_CASE(
"Context Timeseries File Loading") {
3283 SUBCASE(
"ISO-8601 UTC datetime") {
3285 std::string warning_msg;
3287 capture_cerr capture;
3288 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_utc.csv", {},
",",
"ISO8601", 0);
3289 warning_msg = capture.get_captured_output();
3291 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3294 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3295 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3297 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3298 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3300 float humid = ctx.queryTimeseriesData(
"humidity", date,
make_Time(10, 0, 0));
3301 DOCTEST_CHECK(humid == doctest::Approx(0.65f));
3304 Location loc = ctx.getLocation();
3305 DOCTEST_CHECK(loc.UTC_offset == doctest::Approx(0.0f));
3308 SUBCASE(
"ISO-8601 with timezone offset") {
3310 std::string warning_msg;
3312 capture_cerr capture;
3313 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_offset.csv", {},
",",
"ISO8601", 0);
3314 warning_msg = capture.get_captured_output();
3316 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3320 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(2, 0, 0));
3321 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3323 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(4, 0, 0));
3324 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3327 Location loc = ctx.getLocation();
3328 DOCTEST_CHECK(loc.UTC_offset == doctest::Approx(8.0f));
3331 SUBCASE(
"Compact date (YYYYMMDD no delimiters)") {
3333 std::string warning_msg;
3335 capture_cerr capture;
3336 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_compact_date.csv", {},
",",
"YYYYMMDD", 0);
3337 warning_msg = capture.get_captured_output();
3339 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3342 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3343 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3345 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3346 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3349 SUBCASE(
"Compact datetime (YYYYMMDDHH)") {
3351 std::string warning_msg;
3353 capture_cerr capture;
3354 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_compact_datetime.csv", {},
",",
"YYYYMMDDHH", 0);
3355 warning_msg = capture.get_captured_output();
3357 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3360 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3361 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3363 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3364 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3367 SUBCASE(
"Time column (HH:MM and HH:MM:SS)") {
3369 std::string warning_msg;
3371 capture_cerr capture;
3372 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_time_column.csv", {},
",",
"YYYYMMDD", 0);
3373 warning_msg = capture.get_captured_output();
3375 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3378 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 30, 0));
3379 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3382 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(11, 15, 30));
3383 DOCTEST_CHECK(temp2 == doctest::Approx(16.2f));
3385 float temp3 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3386 DOCTEST_CHECK(temp3 == doctest::Approx(17.8f));
3389 SUBCASE(
"Datetime with space separator") {
3391 std::string warning_msg;
3393 capture_cerr capture;
3394 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_datetime_space.csv", {},
" ",
"YYYY-MM-DD HH:MM", 0);
3395 warning_msg = capture.get_captured_output();
3397 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3400 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3401 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3403 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3404 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3407 SUBCASE(
"European DD/MM/YYYY HH:MM datetime") {
3409 std::string warning_msg;
3411 capture_cerr capture;
3412 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_ddmmyyyy_hhmm.csv", {},
",",
"DD/MM/YYYY HH:MM", 0);
3413 warning_msg = capture.get_captured_output();
3415 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3418 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3419 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3421 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3422 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3425 SUBCASE(
"US MM/DD/YYYY HH:MM datetime") {
3427 std::string warning_msg;
3429 capture_cerr capture;
3430 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_mmddyyyy_hhmm.csv", {},
",",
"MM/DD/YYYY HH:MM", 0);
3431 warning_msg = capture.get_captured_output();
3433 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3436 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3437 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3439 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3440 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3443 SUBCASE(
"Backward compatibility - existing weather_data.csv") {
3446 std::string warning_msg;
3448 capture_cerr capture;
3449 ctx.loadTabularTimeseriesData(
"lib/testdata/weather_data.csv", {},
",",
"DDMMYYYY", 0);
3450 warning_msg = capture.get_captured_output();
3452 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3455 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(13, 0, 0));
3456 DOCTEST_CHECK(temp == doctest::Approx(35.32343f));
3458 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(14, 0, 0));
3459 DOCTEST_CHECK(temp2 == doctest::Approx(36.23432f));
3462 SUBCASE(
"User-specified column labels") {
3464 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_utc.csv",
3465 {
"datetime",
"temp",
"rh"},
",",
"ISO8601", 1);
3468 float temp = ctx.queryTimeseriesData(
"temp", date,
make_Time(10, 0, 0));
3469 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3471 float rh = ctx.queryTimeseriesData(
"rh", date,
make_Time(10, 0, 0));
3472 DOCTEST_CHECK(rh == doctest::Approx(0.65f));
3475 SUBCASE(
"Real data: Open-Meteo Davis CA (ISO-8601 no seconds)") {
3480 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_openmeteo_davis.csv",
3481 {
"datetime",
"temperature",
"humidity",
"precipitation"},
3488 float temp_midnight = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(0, 0, 0));
3489 DOCTEST_CHECK(temp_midnight == doctest::Approx(9.1f));
3492 float temp_noon = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(12, 0, 0));
3493 DOCTEST_CHECK(temp_noon == doctest::Approx(14.1f));
3495 float humid_noon = ctx.queryTimeseriesData(
"humidity", jan1,
make_Time(12, 0, 0));
3496 DOCTEST_CHECK(humid_noon == doctest::Approx(66.0f));
3499 float precip = ctx.queryTimeseriesData(
"precipitation", jan2,
make_Time(18, 0, 0));
3500 DOCTEST_CHECK(precip == doctest::Approx(1.20f));
3503 SUBCASE(
"Real data: Open-Meteo NYC (ISO-8601 negative temperatures)") {
3506 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_openmeteo_nyc.csv",
3507 {
"datetime",
"temperature",
"precipitation"},
3515 float temp = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(0, 0, 0));
3516 DOCTEST_CHECK(temp == doctest::Approx(1.8f));
3519 float temp_cold = ctx.queryTimeseriesData(
"temperature", jan2,
make_Time(7, 0, 0));
3520 DOCTEST_CHECK(temp_cold == doctest::Approx(-4.6f));
3523 float temp_neg = ctx.queryTimeseriesData(
"temperature", jan2,
make_Time(0, 0, 0));
3524 DOCTEST_CHECK(temp_neg == doctest::Approx(-1.1f));
3527 float temp_warm = ctx.queryTimeseriesData(
"temperature", jan3,
make_Time(13, 0, 0));
3528 DOCTEST_CHECK(temp_warm == doctest::Approx(7.7f));