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));
1485 DOCTEST_CHECK(ctx.getTileObjectSubdivisionCount(tile) ==
make_int2(4, 4));
1488 ctx.setTileObjectSubdivisionCount({tile}, 16.f);
1489 DOCTEST_CHECK(ctx.getTileObjectAreaRatio(tile) == doctest::Approx(16.f).epsilon(0.25f));
1493 capture_cerr capture;
1494 DOCTEST_CHECK_THROWS(ctx.setTileObjectSubdivisionCount({tile}, 0.5f));
1498 SUBCASE(
"subdivision update preserves flat tile orientation") {
1505 uint tile = ctx.addTileObject(center, size, nullrotation,
make_int2(2, 2));
1507 vec3 normal_before = ctx.getPrimitiveNormal(ctx.getObjectPrimitiveUUIDs(tile).front());
1508 DOCTEST_CHECK(normal_before.z == doctest::Approx(1.f).epsilon(errtol));
1510 ctx.setTileObjectSubdivisionCount({tile},
make_int2(5, 3));
1512 DOCTEST_CHECK(ctx.getTileObjectSubdivisionCount(tile) ==
make_int2(5, 3));
1514 vec3 normal_after = ctx.getPrimitiveNormal(ctx.getObjectPrimitiveUUIDs(tile).front());
1515 DOCTEST_CHECK(normal_after.x == doctest::Approx(0.f).epsilon(errtol));
1516 DOCTEST_CHECK(normal_after.y == doctest::Approx(0.f).epsilon(errtol));
1517 DOCTEST_CHECK(normal_after.z == doctest::Approx(1.f).epsilon(errtol));
1520 vec3 center_after = ctx.getTileObjectCenter(tile);
1521 DOCTEST_CHECK(center_after.x == doctest::Approx(center.x).epsilon(errtol));
1522 DOCTEST_CHECK(center_after.y == doctest::Approx(center.y).epsilon(errtol));
1523 DOCTEST_CHECK(center_after.z == doctest::Approx(center.z).epsilon(errtol));
1524 vec2 size_after = ctx.getTileObjectSize(tile);
1525 DOCTEST_CHECK(size_after.x == doctest::Approx(size.x).epsilon(errtol));
1526 DOCTEST_CHECK(size_after.y == doctest::Approx(size.y).epsilon(errtol));
1529 SUBCASE(
"subdivision update preserves arbitrary tile orientation") {
1535 vec3 normal_before = ctx.getTileObjectNormal(tile);
1537 ctx.setTileObjectSubdivisionCount({tile},
make_int2(4, 4));
1539 vec3 normal_after = ctx.getTileObjectNormal(tile);
1540 DOCTEST_CHECK(normal_after.x == doctest::Approx(normal_before.x).epsilon(errtol));
1541 DOCTEST_CHECK(normal_after.y == doctest::Approx(normal_before.y).epsilon(errtol));
1542 DOCTEST_CHECK(normal_after.z == doctest::Approx(normal_before.z).epsilon(errtol));
1545 SUBCASE(
"subdivision update preserves textured tile orientation") {
1551 uint tile = ctx.addTileObject(center, size, rotation,
make_int2(2, 2),
"lib/images/disk_texture.png");
1553 vec3 normal_before = ctx.getTileObjectNormal(tile);
1554 vec3 center_before = ctx.getTileObjectCenter(tile);
1556 ctx.setTileObjectSubdivisionCount({tile},
make_int2(4, 4));
1558 DOCTEST_CHECK(ctx.getTileObjectSubdivisionCount(tile) ==
make_int2(4, 4));
1560 vec3 normal_after = ctx.getTileObjectNormal(tile);
1561 DOCTEST_CHECK(normal_after.x == doctest::Approx(normal_before.x).epsilon(errtol));
1562 DOCTEST_CHECK(normal_after.y == doctest::Approx(normal_before.y).epsilon(errtol));
1563 DOCTEST_CHECK(normal_after.z == doctest::Approx(normal_before.z).epsilon(errtol));
1565 vec3 center_after = ctx.getTileObjectCenter(tile);
1566 DOCTEST_CHECK(center_after.x == doctest::Approx(center_before.x).epsilon(errtol));
1567 DOCTEST_CHECK(center_after.y == doctest::Approx(center_before.y).epsilon(errtol));
1568 DOCTEST_CHECK(center_after.z == doctest::Approx(center_before.z).epsilon(errtol));
1571 SUBCASE(
"subdivision update of multiple tiles and object data survival") {
1578 ctx.setObjectData(tile_a,
"plot_id", 7);
1579 ctx.setObjectData(tile_b,
"plot_id", 9);
1581 ctx.setTileObjectSubdivisionCount({tile_a, tile_b},
make_int2(3, 3));
1584 DOCTEST_CHECK(ctx.doesObjectExist(tile_a));
1585 DOCTEST_CHECK(ctx.doesObjectExist(tile_b));
1586 DOCTEST_CHECK(ctx.getTileObjectSubdivisionCount(tile_a) ==
make_int2(3, 3));
1587 DOCTEST_CHECK(ctx.getTileObjectSubdivisionCount(tile_b) ==
make_int2(3, 3));
1590 std::vector<uint> uuids_a = ctx.getObjectPrimitiveUUIDs(tile_a);
1591 std::vector<uint> uuids_b = ctx.getObjectPrimitiveUUIDs(tile_b);
1592 DOCTEST_CHECK(uuids_a.size() == 9);
1593 DOCTEST_CHECK(uuids_b.size() == 9);
1594 for (
uint UUID: uuids_a) {
1595 DOCTEST_CHECK(ctx.getPrimitiveParentObjectID(UUID) == tile_a);
1597 for (
uint UUID: uuids_b) {
1598 DOCTEST_CHECK(ctx.getPrimitiveParentObjectID(UUID) == tile_b);
1603 ctx.getObjectData(tile_a,
"plot_id", plot_a);
1604 ctx.getObjectData(tile_b,
"plot_id", plot_b);
1605 DOCTEST_CHECK(plot_a == 7);
1606 DOCTEST_CHECK(plot_b == 9);
1609 DOCTEST_CHECK(ctx.getTileObjectSize(tile_a).x == doctest::Approx(2.f).epsilon(errtol));
1610 DOCTEST_CHECK(ctx.getTileObjectSize(tile_b).x == doctest::Approx(4.f).epsilon(errtol));
1613 SUBCASE(
"per-subpatch data preserved on 1:1 resolution call") {
1618 std::vector<uint> uuids_before = ctx.getObjectPrimitiveUUIDs(tile);
1619 DOCTEST_CHECK(uuids_before.size() == 4);
1621 for (
uint UUID: uuids_before) {
1622 vec3 c = ctx.getPatchCenter(UUID);
1623 ctx.setPrimitiveData(UUID,
"tag_x", c.x);
1624 ctx.setPrimitiveData(UUID,
"tag_y", c.y);
1627 ctx.setTileObjectSubdivisionCount({tile},
make_int2(2, 2));
1629 std::vector<uint> uuids_after = ctx.getObjectPrimitiveUUIDs(tile);
1630 DOCTEST_CHECK(uuids_after.size() == 4);
1631 for (
uint UUID: uuids_after) {
1632 vec3 c = ctx.getPatchCenter(UUID);
1633 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(UUID,
"tag_x"));
1635 ctx.getPrimitiveData(UUID,
"tag_x", tag_x);
1636 ctx.getPrimitiveData(UUID,
"tag_y", tag_y);
1638 DOCTEST_CHECK(tag_x == doctest::Approx(c.x).epsilon(errtol));
1639 DOCTEST_CHECK(tag_y == doctest::Approx(c.y).epsilon(errtol));
1643 SUBCASE(
"per-subpatch data spatially remapped on refine") {
1649 for (
uint UUID: ctx.getObjectPrimitiveUUIDs(tile)) {
1650 vec3 c = ctx.getPatchCenter(UUID);
1651 int quadrant = (c.x < 0 ? 0 : 1) + (c.y < 0 ? 0 : 2);
1652 ctx.setPrimitiveData(UUID,
"quadrant", quadrant);
1655 ctx.setTileObjectSubdivisionCount({tile},
make_int2(4, 4));
1657 std::vector<uint> uuids_after = ctx.getObjectPrimitiveUUIDs(tile);
1658 DOCTEST_CHECK(uuids_after.size() == 16);
1659 for (
uint UUID: uuids_after) {
1660 vec3 c = ctx.getPatchCenter(UUID);
1661 int expected_quadrant = (c.x < 0 ? 0 : 1) + (c.y < 0 ? 0 : 2);
1662 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(UUID,
"quadrant"));
1664 ctx.getPrimitiveData(UUID,
"quadrant", q);
1665 DOCTEST_CHECK(q == expected_quadrant);
1669 SUBCASE(
"per-subpatch data spatially remapped on coarsen") {
1674 for (
uint UUID: ctx.getObjectPrimitiveUUIDs(tile)) {
1675 vec3 c = ctx.getPatchCenter(UUID);
1676 int quadrant = (c.x < 0 ? 0 : 1) + (c.y < 0 ? 0 : 2);
1677 ctx.setPrimitiveData(UUID,
"quadrant", quadrant);
1680 ctx.setTileObjectSubdivisionCount({tile},
make_int2(2, 2));
1682 std::vector<uint> uuids_after = ctx.getObjectPrimitiveUUIDs(tile);
1683 DOCTEST_CHECK(uuids_after.size() == 4);
1684 for (
uint UUID: uuids_after) {
1685 vec3 c = ctx.getPatchCenter(UUID);
1686 int expected_quadrant = (c.x < 0 ? 0 : 1) + (c.y < 0 ? 0 : 2);
1688 ctx.getPrimitiveData(UUID,
"quadrant", q);
1689 DOCTEST_CHECK(q == expected_quadrant);
1693 SUBCASE(
"multiple primitive data types preserved across resolution change") {
1697 uint patch = ctx.getObjectPrimitiveUUIDs(tile).front();
1699 ctx.setPrimitiveData(patch,
"f", 3.5f);
1700 ctx.setPrimitiveData(patch,
"s", std::string(
"hello"));
1701 ctx.setPrimitiveData(patch,
"v",
make_vec3(1, 2, 3));
1703 ctx.setTileObjectSubdivisionCount({tile},
make_int2(3, 3));
1705 for (
uint UUID: ctx.getObjectPrimitiveUUIDs(tile)) {
1709 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(UUID,
"f"));
1710 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(UUID,
"s"));
1711 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(UUID,
"v"));
1712 ctx.getPrimitiveData(UUID,
"f", f);
1713 ctx.getPrimitiveData(UUID,
"s", s);
1714 ctx.getPrimitiveData(UUID,
"v", v);
1715 DOCTEST_CHECK(f == doctest::Approx(3.5f).epsilon(errtol));
1716 DOCTEST_CHECK(s ==
"hello");
1717 DOCTEST_CHECK(v.x == doctest::Approx(1.f).epsilon(errtol));
1718 DOCTEST_CHECK(v.z == doctest::Approx(3.f).epsilon(errtol));
1723TEST_CASE(
"Pseudocolor Visualization") {
1724 SUBCASE(
"colorPrimitiveByDataPseudocolor") {
1726 std::vector<uint> patches;
1727 for (
int i = 0; i < 5; i++) {
1728 uint p = ctx.addPatch();
1729 ctx.setPrimitiveData(p,
"value",
float(i));
1730 patches.push_back(p);
1733 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches,
"value",
"hot", 10));
1734 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches,
"value",
"rainbow", 5, 0.f, 4.f));
1738TEST_CASE(
"Date and Time Extensions") {
1739 SUBCASE(
"getMonthString") {
1741 ctx.setDate(15, 1, 2025);
1742 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"JAN") == 0);
1743 ctx.setDate(15, 2, 2025);
1744 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"FEB") == 0);
1745 ctx.setDate(15, 12, 2025);
1746 DOCTEST_CHECK(strcmp(ctx.getMonthString(),
"DEC") == 0);
1750TEST_CASE(
"Tube Object Management") {
1751 SUBCASE(
"appendTubeSegment with texture") {
1754 std::vector<float> radii = {0.2f, 0.1f};
1755 uint tube = ctx.addTubeObject(10, nodes, radii);
1757 ctx.appendTubeSegment(tube,
make_vec3(0, 0, 2), 0.05f,
"lib/images/solid.jpg",
make_vec2(0.5f, 1.0f));
1758 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1761 SUBCASE(
"setTubeNodes preserves color-to-segment mapping") {
1765 std::vector<vec3> nodes = {
make_vec3(0, 0, 0),
make_vec3(0, 0, 1),
make_vec3(0, 0, 2),
make_vec3(0, 0, 3)};
1766 std::vector<float> radii = {0.5f, 0.5f, 0.5f, 0.5f};
1767 std::vector<RGBcolor> colors = {RGB::red, RGB::yellow, RGB::green, RGB::blue};
1770 uint tube = ctx.addTubeObject(subdiv, nodes, radii, colors);
1773 std::vector<vec3> new_nodes = nodes;
1774 for (
auto &n : new_nodes) n.x += 0.1f;
1775 ctx.setTubeNodes(tube, new_nodes);
1780 std::vector<uint> uuids = ctx.getObjectPrimitiveUUIDs(tube);
1782 for (
uint uuid : uuids) {
1783 RGBcolor c = ctx.getPrimitiveColorRGB(uuid);
1786 int expected_segment = -1;
1787 if (c.r == RGB::red.r && c.g == RGB::red.g && c.b == RGB::red.b) expected_segment = 0;
1788 else if (c.r == RGB::yellow.r && c.g == RGB::yellow.g && c.b == RGB::yellow.b) expected_segment = 1;
1789 else if (c.r == RGB::green.r && c.g == RGB::green.g && c.b == RGB::green.b) expected_segment = 2;
1791 DOCTEST_REQUIRE(expected_segment >= 0);
1793 float z_min = new_nodes[expected_segment].z;
1794 float z_max = new_nodes[expected_segment + 1].z;
1797 for (
uint v = 0; v < 3; v++) {
1798 vec3 vert = ctx.getTriangleVertex(uuid, v);
1799 if (vert.z < z_min - 0.01f || vert.z > z_max + 0.01f) {
1805 DOCTEST_CHECK_MESSAGE(mismatches == 0,
"Found " << mismatches <<
" triangles with vertices outside their color's segment range after setTubeNodes()");
1808 SUBCASE(
"pruneTubeNodes deletes correct primitives") {
1811 std::vector<vec3> nodes = {
make_vec3(0, 0, 0),
make_vec3(0, 0, 1),
make_vec3(0, 0, 2),
make_vec3(0, 0, 3)};
1812 std::vector<float> radii = {0.5f, 0.5f, 0.5f, 0.5f};
1813 std::vector<RGBcolor> colors = {RGB::red, RGB::yellow, RGB::green, RGB::blue};
1816 uint tube = ctx.addTubeObject(subdiv, nodes, radii, colors);
1818 uint uuids_before = ctx.getObjectPrimitiveUUIDs(tube).size();
1820 DOCTEST_CHECK(uuids_before == 48);
1822 ctx.pruneTubeNodes(tube, 3);
1825 DOCTEST_CHECK(ctx.doesObjectExist(tube));
1826 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1829 std::vector<uint> remaining = ctx.getObjectPrimitiveUUIDs(tube);
1830 DOCTEST_CHECK(remaining.size() == 32);
1834 for (
uint uuid : remaining) {
1835 RGBcolor c = ctx.getPrimitiveColorRGB(uuid);
1836 bool is_red = (c.r == RGB::red.r && c.g == RGB::red.g && c.b == RGB::red.b);
1837 bool is_yellow = (c.r == RGB::yellow.r && c.g == RGB::yellow.g && c.b == RGB::yellow.b);
1838 if (!is_red && !is_yellow) bad_colors++;
1840 DOCTEST_CHECK(bad_colors == 0);
1843 ctx.pruneTubeNodes(tube, 1);
1844 DOCTEST_CHECK_FALSE(ctx.doesObjectExist(tube));
1847 SUBCASE(
"setTubeNodes maintains circular cross-sections after bending") {
1851 std::vector<float> radii = {0.5f, 0.5f, 0.5f};
1853 uint tube = ctx.addTubeObject(subdiv, nodes, radii);
1857 ctx.setTubeNodes(tube, bent_nodes);
1862 std::vector<uint> uuids = ctx.getObjectPrimitiveUUIDs(tube);
1865 std::vector<vec3> axial(3);
1866 axial[0] = (bent_nodes[1] - bent_nodes[0]);
1867 axial[0].normalize();
1868 axial[1] = 0.5f * ((bent_nodes[1] - bent_nodes[0]) + (bent_nodes[2] - bent_nodes[1]));
1869 axial[1].normalize();
1870 axial[2] = (bent_nodes[2] - bent_nodes[1]);
1871 axial[2].normalize();
1873 int perp_failures = 0;
1874 int radius_failures = 0;
1876 for (
uint uuid : uuids) {
1877 for (
uint v = 0; v < 3; v++) {
1878 vec3 vert = ctx.getTriangleVertex(uuid, v);
1882 float min_dist = (vert - bent_nodes[0]).magnitude();
1883 for (
int n = 1; n < 3; n++) {
1884 float d = (vert - bent_nodes[n]).magnitude();
1891 vec3 radial = vert - bent_nodes[nearest];
1892 float dot = fabs(radial * axial[nearest]);
1896 if (fabs(radial.magnitude() - 0.5f) > 0.02f) {
1902 DOCTEST_CHECK_MESSAGE(perp_failures == 0,
"Found " << perp_failures <<
" vertices not perpendicular to local tube axis after bending");
1903 DOCTEST_CHECK_MESSAGE(radius_failures == 0,
"Found " << radius_failures <<
" vertices with incorrect radius after bending");
1907TEST_CASE(
"Edge Cases and Additional Coverage") {
1908 SUBCASE(
"Julian date edge cases") {
1910 ctx.setDate(1, 1, 2025);
1911 DOCTEST_CHECK(ctx.getJulianDate() == 1);
1913 ctx.setDate(31, 12, 2025);
1914 DOCTEST_CHECK(ctx.getJulianDate() == 365);
1916 ctx.setDate(100, 2025);
1917 Date d = ctx.getDate();
1918 DOCTEST_CHECK(d.day == 10);
1919 DOCTEST_CHECK(d.month == 4);
1922 SUBCASE(
"time edge cases") {
1924 ctx.setTime(0, 0, 0);
1925 Time t = ctx.getTime();
1926 DOCTEST_CHECK(t.hour == 0);
1927 DOCTEST_CHECK(t.minute == 0);
1928 DOCTEST_CHECK(t.second == 0);
1930 ctx.setTime(59, 59, 23);
1932 DOCTEST_CHECK(t.hour == 23);
1933 DOCTEST_CHECK(t.minute == 59);
1934 DOCTEST_CHECK(t.second == 59);
1937 SUBCASE(
"random number edge cases") {
1939 ctx.seedRandomGenerator(0);
1941 float r1 = ctx.randu(5.f, 5.f);
1942 DOCTEST_CHECK(r1 == doctest::Approx(5.f));
1944 int ri = ctx.randu(10, 10);
1945 DOCTEST_CHECK(ri == 10);
1947 float rn = ctx.randn(0.f, 0.f);
1948 DOCTEST_CHECK(rn == doctest::Approx(0.f));
1951 SUBCASE(
"texture edge cases") {
1952 capture_cerr cerr_buffer;
1954 uint patch = ctx.addPatch();
1956 ctx.overridePrimitiveTextureColor(patch);
1957 ctx.usePrimitiveTextureColor(patch);
1959 std::vector<uint> patches = {patch};
1960 ctx.overridePrimitiveTextureColor(patches);
1961 ctx.usePrimitiveTextureColor(patches);
1963 DOCTEST_CHECK(!ctx.isPrimitiveTextureColorOverridden(patch));
1966 SUBCASE(
"primitive existence checks") {
1968 uint p1 = ctx.addPatch();
1969 uint p2 = ctx.addPatch();
1971 DOCTEST_CHECK(ctx.doesPrimitiveExist(p1));
1972 DOCTEST_CHECK(ctx.doesPrimitiveExist({p1, p2}));
1974 ctx.deletePrimitive(p1);
1975 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p1));
1976 DOCTEST_CHECK(!ctx.doesPrimitiveExist({p1, p2}));
1977 DOCTEST_CHECK(ctx.doesPrimitiveExist(std::vector<uint>{p2}));
1980 SUBCASE(
"object containment checks") {
1983 std::vector<uint> prims = ctx.getObjectPrimitiveUUIDs(
obj);
1985 DOCTEST_CHECK(ctx.doesObjectContainPrimitive(
obj, prims[0]));
1987 uint independent_patch = ctx.addPatch();
1988 DOCTEST_CHECK(!ctx.doesObjectContainPrimitive(
obj, independent_patch));
1991 SUBCASE(
"transformation matrix operations") {
1993 uint p = ctx.addPatch();
1995 float identity[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
1996 ctx.setPrimitiveTransformationMatrix(p, identity);
1998 std::vector<uint> patches = {p};
1999 ctx.setPrimitiveTransformationMatrix(patches, identity);
2002 ctx.setObjectTransformationMatrix(
obj, identity);
2004 std::vector<uint> objs = {
obj};
2005 ctx.setObjectTransformationMatrix(objs, identity);
2007 float retrieved[16];
2008 ctx.getObjectTransformationMatrix(
obj, retrieved);
2009 for (
int i = 0; i < 16; i++) {
2010 DOCTEST_CHECK(retrieved[i] == doctest::Approx(identity[i]));
2014 SUBCASE(
"object type and texture checks") {
2018 DOCTEST_CHECK(!ctx.objectHasTexture(
obj));
2021 DOCTEST_CHECK(ctx.objectHasTexture(textured_obj));
2024 SUBCASE(
"tube object segment operations") {
2027 std::vector<float> radii = {0.2f, 0.15f, 0.1f};
2028 uint tube = ctx.addTubeObject(10, nodes, radii);
2030 float seg_volume = ctx.getTubeObjectSegmentVolume(tube, 0);
2031 DOCTEST_CHECK(seg_volume > 0.f);
2033 seg_volume = ctx.getTubeObjectSegmentVolume(tube, 1);
2034 DOCTEST_CHECK(seg_volume > 0.f);
2037 SUBCASE(
"cone object advanced properties") {
2041 float radius0 = ctx.getConeObjectNodeRadius(cone, 0);
2042 DOCTEST_CHECK(radius0 == doctest::Approx(1.f));
2044 float radius1 = ctx.getConeObjectNodeRadius(cone, 1);
2045 DOCTEST_CHECK(radius1 == doctest::Approx(0.5f));
2047 float length = ctx.getConeObjectLength(cone);
2048 DOCTEST_CHECK(length == doctest::Approx(2.f));
2050 DOCTEST_CHECK(ctx.getConeObjectSubdivisionCount(cone) == 10);
2053 SUBCASE(
"primitive color operations") {
2054 capture_cerr cerr_buffer;
2056 uint p = ctx.addPatch();
2058 ctx.setPrimitiveColor(p, RGB::blue);
2059 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::blue);
2061 ctx.setPrimitiveColor(p, RGBA::green);
2062 RGBAcolor rgba = ctx.getPrimitiveColorRGBA(p);
2063 DOCTEST_CHECK(rgba.r == RGBA::green.r);
2064 DOCTEST_CHECK(rgba.a == RGBA::green.a);
2066 std::vector<uint> patches = {p};
2067 ctx.setPrimitiveColor(patches, RGB::red);
2068 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::red);
2070 ctx.setPrimitiveColor(patches, RGBA::yellow);
2071 rgba = ctx.getPrimitiveColorRGBA(p);
2072 DOCTEST_CHECK(rgba.r == RGBA::yellow.r);
2075 SUBCASE(
"object color operations") {
2076 capture_cerr cerr_buffer;
2080 ctx.setObjectColor(
obj, RGB::cyan);
2081 ctx.setObjectColor(
obj, RGBA::magenta);
2083 std::vector<uint> objs = {
obj};
2084 ctx.setObjectColor(objs, RGB::white);
2085 ctx.setObjectColor(objs, RGBA::black);
2089TEST_CASE(
"Print and Information Functions") {
2090 SUBCASE(
"printPrimitiveInfo and printObjectInfo") {
2099 capture_cout cout_buffer;
2100 DOCTEST_CHECK_NOTHROW(ctx.printPrimitiveInfo(patch));
2101 DOCTEST_CHECK_NOTHROW(ctx.printObjectInfo(
obj));
2102 has_output = cout_buffer.has_output();
2103 output = cout_buffer.get_captured_output();
2107 DOCTEST_CHECK(has_output);
2108 DOCTEST_CHECK(output.find(
"Info for UUID") != std::string::npos);
2109 DOCTEST_CHECK(output.find(
"Info for ObjID") != std::string::npos);
2113TEST_CASE(
"Object Pointer Access") {
2114 SUBCASE(
"getObjectPointer functions") {
2118 DOCTEST_CHECK(ctx.doesObjectExist(box));
2121 DOCTEST_CHECK(ctx.doesObjectExist(disk));
2123 uint sphere = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2124 DOCTEST_CHECK(ctx.doesObjectExist(sphere));
2127 std::vector<float> radii = {0.2f, 0.1f};
2128 uint tube = ctx.addTubeObject(10, nodes, radii);
2129 DOCTEST_CHECK(ctx.doesObjectExist(tube));
2132 DOCTEST_CHECK(ctx.doesObjectExist(cone));
2135 uint polymesh = ctx.addPolymeshObject(prim_uuids);
2136 DOCTEST_CHECK(ctx.doesObjectExist(polymesh));
2140TEST_CASE(
"Advanced Primitive Operations") {
2141 SUBCASE(
"primitive visibility and print operations") {
2143 uint p1 = ctx.addPatch();
2144 uint p2 = ctx.addPatch();
2147 ctx.hidePrimitive(p1);
2148 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
2149 ctx.showPrimitive(p1);
2150 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
2152 std::vector<uint> patches = {p1, p2};
2153 ctx.hidePrimitive(patches);
2154 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
2155 DOCTEST_CHECK(ctx.isPrimitiveHidden(p2));
2156 ctx.showPrimitive(patches);
2157 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
2158 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p2));
2161 SUBCASE(
"primitive counts by type") {
2163 uint initial_patch_count = ctx.getPatchCount();
2164 uint initial_triangle_count = ctx.getTriangleCount();
2166 uint p1 = ctx.addPatch();
2167 uint p2 = ctx.addPatch();
2170 DOCTEST_CHECK(ctx.getPatchCount() == initial_patch_count + 2);
2171 DOCTEST_CHECK(ctx.getTriangleCount() == initial_triangle_count + 1);
2174 ctx.hidePrimitive(p1);
2175 DOCTEST_CHECK(ctx.getPatchCount(
false) == initial_patch_count + 1);
2176 DOCTEST_CHECK(ctx.getPatchCount(
true) == initial_patch_count + 2);
2180TEST_CASE(
"Data Type and Size Functions") {
2181 SUBCASE(
"primitive data type operations") {
2183 uint p = ctx.addPatch();
2185 ctx.setPrimitiveData(p,
"test_int", 42);
2186 ctx.setPrimitiveData(p,
"test_float", 3.14f);
2187 ctx.setPrimitiveData(p,
"test_vec3",
make_vec3(1, 2, 3));
2189 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_int") == HELIOS_TYPE_INT);
2190 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_float") == HELIOS_TYPE_FLOAT);
2191 DOCTEST_CHECK(ctx.getPrimitiveDataType(
"test_vec3") == HELIOS_TYPE_VEC3);
2193 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_int") == 1);
2194 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_vec3") == 1);
2196 std::vector<float> vec_data = {1.0f, 2.0f, 3.0f};
2197 ctx.setPrimitiveData(p,
"test_vector", vec_data);
2198 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p,
"test_vector") == 3);
2202TEST_CASE(
"Additional Missing Coverage") {
2203 SUBCASE(
"getDirtyUUIDs function") {
2205 uint p1 = ctx.addPatch();
2206 uint p2 = ctx.addPatch();
2208 ctx.markGeometryClean();
2209 std::vector<uint> dirty_uuids = ctx.getDirtyUUIDs();
2210 DOCTEST_CHECK(dirty_uuids.empty());
2212 ctx.markPrimitiveDirty(p1);
2213 dirty_uuids = ctx.getDirtyUUIDs();
2214 DOCTEST_CHECK(dirty_uuids.size() == 1);
2215 DOCTEST_CHECK(std::find(dirty_uuids.begin(), dirty_uuids.end(), p1) != dirty_uuids.end());
2219TEST_CASE(
"Advanced Object Operations") {
2220 SUBCASE(
"object primitive count and area calculations") {
2224 DOCTEST_CHECK(ctx.getObjectPrimitiveCount(
obj) == 6);
2226 float area = ctx.getObjectArea(
obj);
2227 float expected_area = 2 * (2 * 3 + 3 * 4 + 2 * 4);
2228 DOCTEST_CHECK(area == doctest::Approx(expected_area).epsilon(0.01));
2231 SUBCASE(
"object bounding box operations") {
2235 vec3 min_corner, max_corner;
2236 ctx.getObjectBoundingBox(
obj, min_corner, max_corner);
2238 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
2239 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
2240 DOCTEST_CHECK(min_corner.y == doctest::Approx(0.f).epsilon(0.01));
2241 DOCTEST_CHECK(max_corner.y == doctest::Approx(4.f).epsilon(0.01));
2243 std::vector<uint> objs = {
obj};
2244 ctx.getObjectBoundingBox(objs, min_corner, max_corner);
2245 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
2246 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
2250TEST_CASE(
"Additional Object Features") {
2251 SUBCASE(
"getAllObjectIDs") {
2254 uint obj2 = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2256 std::vector<uint> all_ids = ctx.getAllObjectIDs();
2257 DOCTEST_CHECK(all_ids.size() >= 2);
2258 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj1) != all_ids.end());
2259 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj2) != all_ids.end());
2262 SUBCASE(
"object type checks") {
2265 uint sphere = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2268 DOCTEST_CHECK(ctx.getObjectType(box) == OBJECT_TYPE_BOX);
2269 DOCTEST_CHECK(ctx.getObjectType(sphere) == OBJECT_TYPE_SPHERE);
2270 DOCTEST_CHECK(ctx.getObjectType(disk) == OBJECT_TYPE_DISK);
2274TEST_CASE(
"Comprehensive Object Property Tests") {
2275 SUBCASE(
"rotation operations on objects") {
2277 std::vector<uint> objs;
2281 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f *
PI_F,
"z"));
2282 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f *
PI_F,
make_vec3(0, 0, 1)));
2284 DOCTEST_CHECK_NOTHROW(ctx.rotateObjectAboutOrigin(objs, 0.5f *
PI_F,
make_vec3(0, 0, 1)));
2287 SUBCASE(
"scaling operations on objects") {
2289 std::vector<uint> objs;
2292 DOCTEST_CHECK_NOTHROW(ctx.scaleObject(objs,
make_vec3(2, 2, 2)));
2293 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutCenter(objs,
make_vec3(0.5f, 0.5f, 0.5f)));
2294 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutPoint(objs,
make_vec3(2, 2, 2),
make_vec3(0, 0, 0)));
2295 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutOrigin(objs,
make_vec3(0.5f, 0.5f, 0.5f)));
2298 SUBCASE(
"translation operations on objects") {
2300 std::vector<uint> objs;
2303 DOCTEST_CHECK_NOTHROW(ctx.translateObject(objs,
make_vec3(1, 2, 3)));
2307TEST_CASE(
"Domain and Bounding Operations") {
2308 SUBCASE(
"domain bounding sphere") {
2315 ctx.getDomainBoundingSphere(center, radius);
2316 DOCTEST_CHECK(center.x == doctest::Approx(0.f).epsilon(0.1));
2317 DOCTEST_CHECK(radius > 2.f);
2321TEST_CASE(
"Missing Data and State Functions") {
2322 SUBCASE(
"listTimeseriesVariables") {
2327 ctx.addTimeseriesData(
"temp", 25.5f, date, time);
2328 ctx.addTimeseriesData(
"humidity", 60.0f, date, time);
2330 std::vector<std::string> vars = ctx.listTimeseriesVariables();
2331 DOCTEST_CHECK(vars.size() >= 2);
2332 DOCTEST_CHECK(std::find(vars.begin(), vars.end(),
"temp") != vars.end());
2333 DOCTEST_CHECK(std::find(vars.begin(), vars.end(),
"humidity") != vars.end());
2336 SUBCASE(
"clearTimeseriesData") {
2342 ctx.addTimeseriesData(
"temp", 25.5f, date, time);
2343 ctx.addTimeseriesData(
"humidity", 60.0f, date, time);
2344 DOCTEST_CHECK(ctx.listTimeseriesVariables().size() == 2);
2345 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"temp"));
2348 ctx.clearTimeseriesData();
2349 DOCTEST_CHECK(ctx.listTimeseriesVariables().empty());
2350 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"temp"));
2351 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"humidity"));
2354 ctx.clearTimeseriesData();
2355 DOCTEST_CHECK(ctx.listTimeseriesVariables().empty());
2358 SUBCASE(
"deleteTimeseriesVariable") {
2364 ctx.addTimeseriesData(
"temp", 25.5f, date, time0);
2365 ctx.addTimeseriesData(
"temp", 26.5f, date, time1);
2366 ctx.addTimeseriesData(
"humidity", 60.0f, date, time0);
2367 DOCTEST_CHECK(ctx.listTimeseriesVariables().size() == 2);
2370 ctx.deleteTimeseriesVariable(
"temp");
2371 DOCTEST_CHECK_FALSE(ctx.doesTimeseriesVariableExist(
"temp"));
2372 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"humidity"));
2373 DOCTEST_CHECK(ctx.getTimeseriesLength(
"humidity") == 1);
2374 DOCTEST_CHECK(ctx.queryTimeseriesData(
"humidity", 0) == doctest::Approx(60.0f));
2377 ctx.addTimeseriesData(
"temp", 99.9f, date, time0);
2378 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist(
"temp"));
2379 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 1);
2380 DOCTEST_CHECK(ctx.queryTimeseriesData(
"temp", 0) == doctest::Approx(99.9f));
2383 std::string captured;
2385 capture_cerr cerr_buffer;
2386 DOCTEST_CHECK_NOTHROW(ctx.deleteTimeseriesVariable(
"nonexistent"));
2387 captured = cerr_buffer.get_captured_output();
2389 DOCTEST_CHECK(captured.find(
"WARNING") != std::string::npos);
2390 DOCTEST_CHECK(captured.find(
"nonexistent") != std::string::npos);
2393 SUBCASE(
"deleteTimeseriesDataPoint by label") {
2400 ctx.addTimeseriesData(
"temp", 25.5f, date, time0);
2401 ctx.addTimeseriesData(
"temp", 26.5f, date, time1);
2402 ctx.addTimeseriesData(
"temp", 27.5f, date, time2);
2403 ctx.addTimeseriesData(
"humidity", 60.0f, date, time1);
2404 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 3);
2407 ctx.deleteTimeseriesDataPoint(
"temp", date, time1);
2408 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 2);
2409 DOCTEST_CHECK(ctx.queryTimeseriesData(
"temp", 0) == doctest::Approx(25.5f));
2410 DOCTEST_CHECK(ctx.queryTimeseriesData(
"temp", 1) == doctest::Approx(27.5f));
2412 DOCTEST_CHECK(ctx.getTimeseriesLength(
"humidity") == 1);
2413 DOCTEST_CHECK(ctx.queryTimeseriesData(
"humidity", 0) == doctest::Approx(60.0f));
2416 std::string captured;
2418 capture_cerr cerr_buffer;
2419 DOCTEST_CHECK_NOTHROW(ctx.deleteTimeseriesDataPoint(
"temp", date, time1));
2420 captured = cerr_buffer.get_captured_output();
2422 DOCTEST_CHECK(captured.find(
"WARNING") != std::string::npos);
2423 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 2);
2427 capture_cerr cerr_buffer;
2428 DOCTEST_CHECK_NOTHROW(ctx.deleteTimeseriesDataPoint(
"nonexistent", date, time0));
2429 captured = cerr_buffer.get_captured_output();
2431 DOCTEST_CHECK(captured.find(
"WARNING") != std::string::npos);
2432 DOCTEST_CHECK(captured.find(
"nonexistent") != std::string::npos);
2435 SUBCASE(
"deleteTimeseriesDataPoint across all variables") {
2441 ctx.addTimeseriesData(
"temp", 25.5f, date, time0);
2442 ctx.addTimeseriesData(
"temp", 26.5f, date, time1);
2443 ctx.addTimeseriesData(
"humidity", 60.0f, date, time0);
2444 ctx.addTimeseriesData(
"humidity", 65.0f, date, time1);
2445 ctx.addTimeseriesData(
"pressure", 1013.0f, date, time1);
2449 ctx.deleteTimeseriesDataPoint(date, time0);
2450 DOCTEST_CHECK(ctx.getTimeseriesLength(
"temp") == 1);
2451 DOCTEST_CHECK(ctx.queryTimeseriesData(
"temp", 0) == doctest::Approx(26.5f));
2452 DOCTEST_CHECK(ctx.getTimeseriesLength(
"humidity") == 1);
2453 DOCTEST_CHECK(ctx.queryTimeseriesData(
"humidity", 0) == doctest::Approx(65.0f));
2454 DOCTEST_CHECK(ctx.getTimeseriesLength(
"pressure") == 1);
2455 DOCTEST_CHECK(ctx.queryTimeseriesData(
"pressure", 0) == doctest::Approx(1013.0f));
2458 std::string captured;
2459 Time time_no_match =
make_Time(23, 59, 59);
2461 capture_cerr cerr_buffer;
2462 DOCTEST_CHECK_NOTHROW(ctx.deleteTimeseriesDataPoint(date, time_no_match));
2463 captured = cerr_buffer.get_captured_output();
2465 DOCTEST_CHECK(captured.find(
"WARNING") != std::string::npos);
2468 SUBCASE(
"getUniquePrimitiveParentObjectIDs") {
2471 uint obj2 = ctx.addSphereObject(10,
make_vec3(0, 0, 0), 1.f);
2473 std::vector<uint> all_prims = ctx.getAllUUIDs();
2474 std::vector<uint> obj_ids = ctx.getUniquePrimitiveParentObjectIDs(all_prims);
2475 DOCTEST_CHECK(obj_ids.size() >= 2);
2476 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj1) != obj_ids.end());
2477 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj2) != obj_ids.end());
2481TEST_CASE(
"Comprehensive Coverage Tests") {
2482 SUBCASE(
"additional object operations with vectors") {
2484 std::vector<uint> obj_ids;
2488 std::vector<uint> all_uuids = ctx.getObjectPrimitiveUUIDs(obj_ids);
2489 DOCTEST_CHECK(all_uuids.size() == 12);
2491 std::vector<std::vector<uint>> nested_obj_ids = {{obj_ids[0]}, {obj_ids[1]}};
2492 std::vector<uint> nested_uuids = ctx.getObjectPrimitiveUUIDs(nested_obj_ids);
2493 DOCTEST_CHECK(nested_uuids.size() == 12);
2495 ctx.hideObject(obj_ids);
2496 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[0]));
2497 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[1]));
2499 ctx.showObject(obj_ids);
2500 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[0]));
2501 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[1]));
2504 SUBCASE(
"object texture color overrides") {
2506 std::vector<uint> obj_ids;
2509 ctx.overrideObjectTextureColor(obj_ids);
2510 ctx.useObjectTextureColor(obj_ids);
2514TEST_CASE(
"getAllUUIDs Cache Performance") {
2515 SUBCASE(
"Cache invalidation on primitive add/delete") {
2519 std::vector<uint> empty_uuids = ctx.getAllUUIDs();
2520 DOCTEST_CHECK(empty_uuids.empty());
2523 uint p1 = ctx.addPatch();
2524 std::vector<uint> one_uuid = ctx.getAllUUIDs();
2525 DOCTEST_CHECK(one_uuid.size() == 1);
2526 DOCTEST_CHECK(one_uuid[0] == p1);
2529 std::vector<uint> same_uuid = ctx.getAllUUIDs();
2530 DOCTEST_CHECK(same_uuid.size() == 1);
2531 DOCTEST_CHECK(same_uuid[0] == p1);
2537 std::vector<uint> three_uuids = ctx.getAllUUIDs();
2538 DOCTEST_CHECK(three_uuids.size() == 3);
2539 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), p1) != three_uuids.end());
2540 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), t1) != three_uuids.end());
2541 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), v1) != three_uuids.end());
2544 ctx.deletePrimitive(t1);
2545 std::vector<uint> two_uuids = ctx.getAllUUIDs();
2546 DOCTEST_CHECK(two_uuids.size() == 2);
2547 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), t1) == two_uuids.end());
2548 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), p1) != two_uuids.end());
2549 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), v1) != two_uuids.end());
2552 SUBCASE(
"Cache invalidation on hide/show primitives") {
2554 uint p1 = ctx.addPatch();
2555 uint p2 = ctx.addPatch();
2556 uint p3 = ctx.addPatch();
2559 std::vector<uint> all_visible = ctx.getAllUUIDs();
2560 DOCTEST_CHECK(all_visible.size() == 3);
2563 ctx.hidePrimitive(p2);
2564 std::vector<uint> two_visible = ctx.getAllUUIDs();
2565 DOCTEST_CHECK(two_visible.size() == 2);
2566 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p2) == two_visible.end());
2567 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p1) != two_visible.end());
2568 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p3) != two_visible.end());
2571 std::vector<uint> to_hide = {p1, p3};
2572 ctx.hidePrimitive(to_hide);
2573 std::vector<uint> none_visible = ctx.getAllUUIDs();
2574 DOCTEST_CHECK(none_visible.empty());
2577 ctx.showPrimitive(p1);
2578 std::vector<uint> one_visible = ctx.getAllUUIDs();
2579 DOCTEST_CHECK(one_visible.size() == 1);
2580 DOCTEST_CHECK(one_visible[0] == p1);
2583 std::vector<uint> to_show = {p2, p3};
2584 ctx.showPrimitive(to_show);
2585 std::vector<uint> all_back = ctx.getAllUUIDs();
2586 DOCTEST_CHECK(all_back.size() == 3);
2589 SUBCASE(
"Cache invalidation on copy primitives") {
2591 uint original = ctx.addPatch();
2593 std::vector<uint> before_copy = ctx.getAllUUIDs();
2594 DOCTEST_CHECK(before_copy.size() == 1);
2596 uint copied = ctx.copyPrimitive(original);
2597 std::vector<uint> after_copy = ctx.getAllUUIDs();
2598 DOCTEST_CHECK(after_copy.size() == 2);
2599 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), original) != after_copy.end());
2600 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), copied) != after_copy.end());
2603 std::vector<uint> originals = {original, copied};
2604 std::vector<uint> copies = ctx.copyPrimitive(originals);
2605 std::vector<uint> after_multi_copy = ctx.getAllUUIDs();
2606 DOCTEST_CHECK(after_multi_copy.size() == 4);
2607 for (
uint copy_id: copies) {
2608 DOCTEST_CHECK(std::find(after_multi_copy.begin(), after_multi_copy.end(), copy_id) != after_multi_copy.end());
2612 SUBCASE(
"Cache consistency across mixed operations") {
2616 uint p1 = ctx.addPatch();
2619 std::vector<uint> step1 = ctx.getAllUUIDs();
2620 DOCTEST_CHECK(step1.size() == 2);
2622 ctx.hidePrimitive(p1);
2623 std::vector<uint> step2 = ctx.getAllUUIDs();
2624 DOCTEST_CHECK(step2.size() == 1);
2625 DOCTEST_CHECK(step2[0] == p2);
2628 std::vector<uint> step3 = ctx.getAllUUIDs();
2629 DOCTEST_CHECK(step3.size() == 2);
2631 ctx.showPrimitive(p1);
2632 std::vector<uint> step4 = ctx.getAllUUIDs();
2633 DOCTEST_CHECK(step4.size() == 3);
2635 ctx.deletePrimitive(p2);
2636 std::vector<uint> step5 = ctx.getAllUUIDs();
2637 DOCTEST_CHECK(step5.size() == 2);
2638 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p2) == step5.end());
2639 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p1) != step5.end());
2640 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p3) != step5.end());
2644TEST_CASE(
"Error Handling") {
2645 SUBCASE(
"Context error handling") {
2646 Context context_test;
2648 capture_cerr cerr_buffer;
2651 DOCTEST_CHECK_THROWS_AS(center = context_test.getPatchCenter(tri), std::runtime_error);
2655 std::vector<uint> vlist{vox};
2656 DOCTEST_CHECK_THROWS_AS(context_test.rotatePrimitive(vlist,
PI_F / 4.f,
"a"), std::runtime_error);
2660TEST_CASE(
"Zero Area Triangle Detection") {
2661 SUBCASE(
"addTubeObject with nearly identical vertices should not create zero-area triangles") {
2665 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)};
2667 std::vector<float> radii = {0.000500000024f, 0.000500000024f, 0.000500000024f};
2668 std::vector<RGBcolor> colors = {RGB::green, RGB::green, RGB::green};
2671 uint tube_obj = ctx.addTubeObject(7, nodes, radii, colors);
2674 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
2677 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
2678 DOCTEST_CHECK(tube_primitives.size() > 0);
2680 for (
uint uuid: tube_primitives) {
2681 float area = ctx.getPrimitiveArea(uuid);
2682 DOCTEST_CHECK(area > 0.0f);
2683 DOCTEST_CHECK(area > 1e-12f);
2687 SUBCASE(
"addTubeObject with extremely small displacements") {
2693 std::vector<float> radii = {1e-4f, 1e-4f, 1e-4f};
2695 uint tube_obj = ctx.addTubeObject(6, nodes, radii);
2696 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
2698 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
2699 for (
uint uuid: tube_primitives) {
2700 float area = ctx.getPrimitiveArea(uuid);
2701 DOCTEST_CHECK(area > 0.0f);
2706TEST_CASE(
"Transparent Texture Zero Area Validation") {
2707 SUBCASE(
"addSphere with transparent texture should filter zero-area triangles") {
2711 std::vector<uint> sphere_uuids = ctx.addSphere(20,
make_vec3(0, 0, 0), 1.0f,
"lib/images/diamond_texture.png");
2714 DOCTEST_CHECK(sphere_uuids.size() > 0);
2715 for (
uint uuid: sphere_uuids) {
2716 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2717 float area = ctx.getPrimitiveArea(uuid);
2718 DOCTEST_CHECK(area > 0.0f);
2722 std::vector<uint> sphere_disk_uuids = ctx.addSphere(30,
make_vec3(2, 0, 0), 1.0f,
"lib/images/disk_texture.png");
2724 DOCTEST_CHECK(sphere_disk_uuids.size() > 0);
2725 for (
uint uuid: sphere_disk_uuids) {
2726 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2727 float area = ctx.getPrimitiveArea(uuid);
2728 DOCTEST_CHECK(area > 0.0f);
2732 int zero_area_count_diamond = 0;
2733 for (
uint uuid: sphere_uuids) {
2734 float area = ctx.getPrimitiveArea(uuid);
2736 zero_area_count_diamond++;
2739 DOCTEST_CHECK(zero_area_count_diamond == 0);
2741 int zero_area_count_disk = 0;
2742 for (
uint uuid: sphere_disk_uuids) {
2743 float area = ctx.getPrimitiveArea(uuid);
2745 zero_area_count_disk++;
2748 DOCTEST_CHECK(zero_area_count_disk == 0);
2751 std::vector<uint> solid_sphere_uuids = ctx.addSphere(20,
make_vec3(4, 0, 0), 1.0f, RGB::green);
2753 int zero_area_count_solid = 0;
2754 for (
uint uuid: solid_sphere_uuids) {
2755 float area = ctx.getPrimitiveArea(uuid);
2757 zero_area_count_solid++;
2760 DOCTEST_CHECK(zero_area_count_solid == 0);
2763 SUBCASE(
"texture transparency validation preserves object integrity") {
2767 std::vector<uint> sphere_uuids = ctx.addSphere(15,
make_vec3(0, 0, 0), 1.0f,
"lib/images/diamond_texture.png");
2770 for (
uint uuid: sphere_uuids) {
2771 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2772 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
2774 float area = ctx.getPrimitiveArea(uuid);
2775 DOCTEST_CHECK(area > 0.0f);
2776 DOCTEST_CHECK(area > 1e-10f);
2779 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2780 DOCTEST_CHECK(solid_fraction > 0.0f);
2781 DOCTEST_CHECK(solid_fraction <= 1.0f);
2785 std::vector<uint> all_uuids = ctx.getAllUUIDs();
2786 int total_zero_area = 0;
2787 int total_negative_area = 0;
2789 for (
uint uuid: all_uuids) {
2790 float area = ctx.getPrimitiveArea(uuid);
2795 total_negative_area++;
2800 DOCTEST_CHECK(total_zero_area == 0);
2801 DOCTEST_CHECK(total_negative_area == 0);
2804 for (
uint uuid: sphere_uuids) {
2805 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2806 DOCTEST_CHECK(solid_fraction >= 0.0f);
2807 DOCTEST_CHECK(solid_fraction <= 1.0f);
2810 if (ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE) {
2811 vec3 v0 = ctx.getTriangleVertex(uuid, 0);
2812 vec3 v1 = ctx.getTriangleVertex(uuid, 1);
2813 vec3 v2 = ctx.getTriangleVertex(uuid, 2);
2815 float effective_area = ctx.getPrimitiveArea(uuid);
2818 DOCTEST_CHECK(effective_area <= geometric_area + 1e-6f);
2819 DOCTEST_CHECK(effective_area > 0.0f);
2824 DOCTEST_SUBCASE(
"Test Other Primitive Methods Zero Area Validation") {
2829 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2830 std::vector<uint> tube_uuids = ctx_other.addTube(8, tube_nodes, tube_radii,
"lib/images/diamond_texture.png");
2833 int tube_positive_area = 0, tube_zero_area = 0;
2834 for (
uint uuid: tube_uuids) {
2835 float area = ctx_other.getPrimitiveArea(uuid);
2836 DOCTEST_CHECK(area >= 0.0f);
2838 tube_positive_area++;
2844 DOCTEST_CHECK(tube_positive_area > 0);
2845 DOCTEST_CHECK(tube_zero_area == 0);
2851 int disk_positive_area = 0, disk_zero_area = 0;
2852 for (
uint uuid: disk_uuids) {
2853 float area = ctx_other.getPrimitiveArea(uuid);
2854 DOCTEST_CHECK(area >= 0.0f);
2856 disk_positive_area++;
2862 DOCTEST_CHECK(disk_positive_area > 0);
2863 DOCTEST_CHECK(disk_zero_area == 0);
2866 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");
2869 int cone_positive_area = 0, cone_zero_area = 0;
2870 for (
uint uuid: cone_uuids) {
2871 float area = ctx_other.getPrimitiveArea(uuid);
2872 DOCTEST_CHECK(area >= 0.0f);
2874 cone_positive_area++;
2880 DOCTEST_CHECK(cone_positive_area > 0);
2881 DOCTEST_CHECK(cone_zero_area == 0);
2887 int tile_positive_area = 0, tile_zero_area = 0;
2888 for (
uint uuid: tile_uuids) {
2889 float area = ctx_other.getPrimitiveArea(uuid);
2890 DOCTEST_CHECK(area >= 0.0f);
2892 tile_positive_area++;
2898 DOCTEST_CHECK(tile_positive_area > 0);
2899 DOCTEST_CHECK(tile_zero_area == 0);
2903 DOCTEST_SUBCASE(
"Test Compound Object Methods Zero Area Validation") {
2904 Context ctx_compound;
2907 uint sphere_obj = ctx_compound.addSphereObject(8,
make_vec3(0, 0, 0), 0.5f,
"lib/images/diamond_texture.png");
2908 std::vector<uint> sphere_primitives = ctx_compound.getObjectPrimitiveUUIDs(sphere_obj);
2911 int sphere_positive_area = 0, sphere_zero_area = 0;
2912 for (
uint uuid: sphere_primitives) {
2913 float area = ctx_compound.getPrimitiveArea(uuid);
2914 DOCTEST_CHECK(area >= 0.0f);
2916 sphere_positive_area++;
2922 DOCTEST_CHECK(sphere_positive_area > 0);
2923 DOCTEST_CHECK(sphere_zero_area == 0);
2927 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2928 uint tube_obj = ctx_compound.addTubeObject(8, tube_nodes, tube_radii,
"lib/images/diamond_texture.png");
2929 std::vector<uint> tube_primitives = ctx_compound.getObjectPrimitiveUUIDs(tube_obj);
2932 int tube_positive_area = 0, tube_zero_area = 0;
2933 for (
uint uuid: tube_primitives) {
2934 float area = ctx_compound.getPrimitiveArea(uuid);
2935 DOCTEST_CHECK(area >= 0.0f);
2937 tube_positive_area++;
2943 DOCTEST_CHECK(tube_positive_area > 0);
2944 DOCTEST_CHECK(tube_zero_area == 0);
2948 std::vector<uint> disk_primitives = ctx_compound.getObjectPrimitiveUUIDs(disk_obj);
2951 int disk_positive_area = 0, disk_zero_area = 0;
2952 for (
uint uuid: disk_primitives) {
2953 float area = ctx_compound.getPrimitiveArea(uuid);
2954 DOCTEST_CHECK(area >= 0.0f);
2956 disk_positive_area++;
2962 DOCTEST_CHECK(disk_positive_area > 0);
2963 DOCTEST_CHECK(disk_zero_area == 0);
2966 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");
2967 std::vector<uint> cone_primitives = ctx_compound.getObjectPrimitiveUUIDs(cone_obj);
2970 int cone_positive_area = 0, cone_zero_area = 0;
2971 for (
uint uuid: cone_primitives) {
2972 float area = ctx_compound.getPrimitiveArea(uuid);
2973 DOCTEST_CHECK(area >= 0.0f);
2975 cone_positive_area++;
2981 DOCTEST_CHECK(cone_positive_area > 0);
2982 DOCTEST_CHECK(cone_zero_area == 0);
2987TEST_CASE(
"File path resolution priority") {
2988 SUBCASE(
"resolveFilePath current directory priority") {
2993 std::string testFileName =
"test_file_resolution.jpg";
2994 std::filesystem::path currentDirFile = std::filesystem::current_path() / testFileName;
2997 std::filesystem::path sourceTexture =
"core/lib/models/texture.jpg";
2999 if (std::filesystem::exists(sourceTexture)) {
3001 std::filesystem::copy_file(sourceTexture, currentDirFile, std::filesystem::copy_options::overwrite_existing);
3002 DOCTEST_CHECK(std::filesystem::exists(currentDirFile));
3006 DOCTEST_CHECK(resolved == std::filesystem::canonical(currentDirFile));
3009 std::filesystem::remove(currentDirFile);
3013 SUBCASE(
"addPatch with texture from current directory") {
3017 std::filesystem::create_directories(
"test_models");
3018 std::string testTexture =
"test_models/test_texture.jpg";
3019 std::filesystem::path testTexturePath = std::filesystem::current_path() / testTexture;
3022 std::filesystem::path sourceTexture =
"core/lib/models/texture.jpg";
3024 if (std::filesystem::exists(sourceTexture)) {
3025 std::filesystem::copy_file(sourceTexture, testTexturePath, std::filesystem::copy_options::overwrite_existing);
3031 DOCTEST_CHECK_NOTHROW({ patch_id = ctx.addPatch(
make_vec3(0, 0, 0),
make_vec2(1, 1), rotation, testTexture.c_str()); });
3032 DOCTEST_CHECK(patch_id > 0);
3035 bool has_transparency = ctx.primitiveTextureHasTransparencyChannel(patch_id);
3036 DOCTEST_CHECK((has_transparency || !has_transparency));
3039 std::filesystem::remove(testTexturePath);
3040 std::filesystem::remove(
"test_models");
3044 SUBCASE(
"Material System - Label-Based Creation") {
3048 DOCTEST_CHECK(ctx.doesMaterialExist(
"__default__"));
3049 DOCTEST_CHECK(ctx.getMaterialCount() == 0);
3052 ctx.addMaterial(
"leaf_material");
3053 DOCTEST_CHECK(ctx.doesMaterialExist(
"leaf_material"));
3054 DOCTEST_CHECK(ctx.getMaterialCount() == 1);
3056 ctx.addMaterial(
"bark_material");
3057 DOCTEST_CHECK(ctx.doesMaterialExist(
"bark_material"));
3058 DOCTEST_CHECK(ctx.getMaterialCount() == 2);
3061 std::vector<std::string> labels = ctx.listMaterials();
3062 DOCTEST_CHECK(labels.size() == 2);
3065 DOCTEST_CHECK_THROWS(ctx.addMaterial(
"__reserved"));
3068 SUBCASE(
"Material System - Rename") {
3072 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));
3073 std::string auto_label = ctx.getPrimitiveMaterialLabel(UUID);
3074 DOCTEST_CHECK(auto_label.substr(0, 7) ==
"__auto_");
3075 DOCTEST_CHECK(ctx.doesMaterialExist(auto_label));
3078 ctx.renameMaterial(auto_label,
"bean_trifoliate_leaf");
3079 DOCTEST_CHECK(ctx.doesMaterialExist(
"bean_trifoliate_leaf"));
3081 DOCTEST_CHECK(ctx.doesMaterialExist(auto_label));
3084 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(UUID) ==
"bean_trifoliate_leaf");
3087 std::string tex = ctx.getMaterialTexture(
"bean_trifoliate_leaf");
3088 DOCTEST_CHECK(tex ==
"lib/images/disk_texture.png");
3091 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));
3092 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(UUID2) ==
"bean_trifoliate_leaf");
3095 ctx.addMaterial(
"old_name");
3096 ctx.renameMaterial(
"old_name",
"new_name");
3097 DOCTEST_CHECK(ctx.doesMaterialExist(
"new_name"));
3098 DOCTEST_CHECK(!ctx.doesMaterialExist(
"old_name"));
3101 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"nonexistent",
"new_name2"));
3102 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
"__reserved"));
3103 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
"new_name"));
3104 DOCTEST_CHECK_THROWS(ctx.renameMaterial(
"bean_trifoliate_leaf",
""));
3107 SUBCASE(
"Material System - Properties") {
3111 ctx.addMaterial(
"test_mat");
3114 ctx.setMaterialColor(
"test_mat", purple);
3116 RGBAcolor color = ctx.getMaterialColor(
"test_mat");
3117 DOCTEST_CHECK(color.r == doctest::Approx(0.5f).epsilon(0.001));
3118 DOCTEST_CHECK(color.g == doctest::Approx(0.0f).epsilon(0.001));
3119 DOCTEST_CHECK(color.b == doctest::Approx(0.5f).epsilon(0.001));
3122 ctx.setMaterialTexture(
"test_mat",
"lib/images/disk_texture.png");
3123 std::string tex = ctx.getMaterialTexture(
"test_mat");
3124 DOCTEST_CHECK(tex ==
"lib/images/disk_texture.png");
3127 ctx.setMaterialTextureColorOverride(
"test_mat",
true);
3128 DOCTEST_CHECK(ctx.isMaterialTextureColorOverridden(
"test_mat"));
3130 ctx.setMaterialTextureColorOverride(
"test_mat",
false);
3131 DOCTEST_CHECK(!ctx.isMaterialTextureColorOverridden(
"test_mat"));
3134 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 1);
3137 ctx.setMaterialTwosidedFlag(
"test_mat", 0);
3138 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 0);
3141 ctx.setMaterialTwosidedFlag(
"test_mat", 1);
3142 DOCTEST_CHECK(ctx.getMaterialTwosidedFlag(
"test_mat") == 1);
3145 SUBCASE(
"Material System - Assignment to Primitives") {
3149 ctx.addMaterial(
"red_mat");
3157 ctx.assignMaterialToPrimitive(p1,
"red_mat");
3158 ctx.assignMaterialToPrimitive(p2,
"red_mat");
3161 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p1) ==
"red_mat");
3162 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p2) ==
"red_mat");
3165 RGBcolor c1 = ctx.getPrimitiveColor(p1);
3166 DOCTEST_CHECK(c1.r == doctest::Approx(1.0f).epsilon(0.001));
3167 DOCTEST_CHECK(c1.g == doctest::Approx(0.0f).epsilon(0.001));
3172 c1 = ctx.getPrimitiveColor(p1);
3173 RGBcolor c2 = ctx.getPrimitiveColor(p2);
3174 DOCTEST_CHECK(c1.g == doctest::Approx(1.0f).epsilon(0.001));
3175 DOCTEST_CHECK(c2.g == doctest::Approx(1.0f).epsilon(0.001));
3178 std::vector<uint> users = ctx.getPrimitivesUsingMaterial(
"red_mat");
3179 DOCTEST_CHECK(users.size() == 2);
3182 SUBCASE(
"Material System - Batch Assignment") {
3185 ctx.addMaterial(
"batch_mat");
3186 ctx.setMaterialColor(
"batch_mat",
make_RGBAcolor(0.5f, 0.5f, 0.5f, 1));
3188 std::vector<uint> UUIDs;
3189 for (
int i = 0; i < 10; i++) {
3194 ctx.assignMaterialToPrimitive(UUIDs,
"batch_mat");
3197 for (
uint uuid: UUIDs) {
3198 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(uuid) ==
"batch_mat");
3202 SUBCASE(
"Material System - Deletion") {
3205 ctx.addMaterial(
"temp_mat");
3209 ctx.assignMaterialToPrimitive(p1,
"temp_mat");
3213 ctx.deleteMaterial(
"temp_mat");
3215 DOCTEST_CHECK(!ctx.doesMaterialExist(
"temp_mat"));
3216 DOCTEST_CHECK(ctx.getPrimitiveMaterialLabel(p1) ==
"__default__");
3219 SUBCASE(
"Material System - XML Round-Trip") {
3223 ctx.addMaterial(
"red_mat");
3226 ctx.addMaterial(
"textured_mat");
3227 ctx.setMaterialColor(
"textured_mat",
make_RGBAcolor(0, 1, 0, 1));
3228 ctx.setMaterialTexture(
"textured_mat",
"lib/images/disk_texture.png");
3231 ctx.addMaterial(
"onesided_mat");
3232 ctx.setMaterialColor(
"onesided_mat",
make_RGBAcolor(0, 0, 1, 1));
3233 ctx.setMaterialTwosidedFlag(
"onesided_mat", 0);
3241 ctx.assignMaterialToPrimitive(p1,
"red_mat");
3242 ctx.assignMaterialToPrimitive(p2,
"textured_mat");
3243 ctx.assignMaterialToPrimitive(p3,
"red_mat");
3244 ctx.assignMaterialToPrimitive(p4,
"onesided_mat");
3247 ctx.writeXML(
"test_materials.xml", {p1, p2, p3, p4},
true);
3251 std::vector<uint> loaded_UUIDs = ctx2.loadXML(
"test_materials.xml",
true);
3253 DOCTEST_CHECK(loaded_UUIDs.size() == 4);
3256 DOCTEST_CHECK(ctx2.doesMaterialExist(
"red_mat"));
3257 DOCTEST_CHECK(ctx2.doesMaterialExist(
"textured_mat"));
3258 DOCTEST_CHECK(ctx2.doesMaterialExist(
"onesided_mat"));
3260 RGBcolor loaded_color1 = ctx2.getPrimitiveColor(loaded_UUIDs[0]);
3261 DOCTEST_CHECK(loaded_color1.r == doctest::Approx(1.0f).epsilon(0.001));
3263 DOCTEST_CHECK(ctx2.getPrimitiveTextureFile(loaded_UUIDs[1]) ==
"lib/images/disk_texture.png");
3266 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"red_mat") == 1);
3267 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"textured_mat") == 1);
3268 DOCTEST_CHECK(ctx2.getMaterialTwosidedFlag(
"onesided_mat") == 0);
3271 std::filesystem::remove(
"test_materials.xml");
3274 SUBCASE(
"getPrimitiveTwosidedFlag helper function") {
3278 ctx.addMaterial(
"onesided_mat");
3279 ctx.setMaterialTwosidedFlag(
"onesided_mat", 0);
3281 ctx.addMaterial(
"twosided_mat");
3282 ctx.setMaterialTwosidedFlag(
"twosided_mat", 1);
3284 ctx.addMaterial(
"transparent_mat");
3285 ctx.setMaterialTwosidedFlag(
"transparent_mat", 2);
3295 ctx.assignMaterialToPrimitive(UUID_mat_onesided,
"onesided_mat");
3296 ctx.assignMaterialToPrimitive(UUID_mat_twosided,
"twosided_mat");
3297 ctx.assignMaterialToPrimitive(UUID_mat_transparent,
"transparent_mat");
3300 ctx.setPrimitiveData(UUID_prim_data,
"twosided_flag",
uint(0));
3303 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_onesided) == 0);
3306 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_twosided) == 1);
3309 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_transparent) == 2);
3312 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_prim_data) == 0);
3315 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_default) == 1);
3318 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_default, 2) == 2);
3322 ctx.setPrimitiveData(UUID_mat_onesided,
"twosided_flag",
uint(1));
3323 DOCTEST_CHECK(ctx.getPrimitiveTwosidedFlag(UUID_mat_onesided) == 0);
3326 SUBCASE(
"Material Data - Setting and Getting with Labels") {
3330 ctx.addMaterial(
"data_mat");
3333 ctx.setMaterialData(
"data_mat",
"twosided_flag", 1u);
3334 DOCTEST_CHECK(ctx.doesMaterialDataExist(
"data_mat",
"twosided_flag"));
3335 DOCTEST_CHECK(ctx.getMaterialDataType(
"data_mat",
"twosided_flag") == HELIOS_TYPE_UINT);
3337 ctx.getMaterialData(
"data_mat",
"twosided_flag", flag_val);
3338 DOCTEST_CHECK(flag_val == 1u);
3341 ctx.setMaterialData(
"data_mat",
"test_int", -42);
3343 ctx.getMaterialData(
"data_mat",
"test_int", int_val);
3344 DOCTEST_CHECK(int_val == -42);
3347 ctx.setMaterialData(
"data_mat",
"test_float", 3.14f);
3349 ctx.getMaterialData(
"data_mat",
"test_float", float_val);
3350 DOCTEST_CHECK(float_val == doctest::Approx(3.14f).epsilon(0.001));
3354 ctx.setMaterialData(
"data_mat",
"test_vec3", test_vec);
3356 ctx.getMaterialData(
"data_mat",
"test_vec3", vec_val);
3357 DOCTEST_CHECK(vec_val.x == doctest::Approx(1.0f).epsilon(0.001));
3358 DOCTEST_CHECK(vec_val.y == doctest::Approx(2.0f).epsilon(0.001));
3359 DOCTEST_CHECK(vec_val.z == doctest::Approx(3.0f).epsilon(0.001));
3362 ctx.setMaterialData(
"data_mat",
"test_string", std::string(
"hello"));
3363 std::string str_val;
3364 ctx.getMaterialData(
"data_mat",
"test_string", str_val);
3365 DOCTEST_CHECK(str_val ==
"hello");
3368 ctx.clearMaterialData(
"data_mat",
"test_int");
3369 DOCTEST_CHECK(!ctx.doesMaterialDataExist(
"data_mat",
"test_int"));
3372 SUBCASE(
"Material Data - Fallback Helper Method") {
3376 ctx.addMaterial(
"fallback_mat");
3377 ctx.setMaterialData(
"fallback_mat",
"twosided_flag", 0u);
3381 ctx.assignMaterialToPrimitive(p1,
"fallback_mat");
3385 ctx.getDataWithMaterialFallback(p1,
"twosided_flag", flag_val);
3386 DOCTEST_CHECK(flag_val == 0u);
3390 ctx.assignMaterialToPrimitive(p2,
"fallback_mat");
3391 ctx.setPrimitiveData(p2,
"custom_data", 42);
3395 ctx.getDataWithMaterialFallback(p2,
"custom_data", custom_val);
3396 DOCTEST_CHECK(custom_val == 42);
3400 ctx.assignMaterialToPrimitive(p3,
"fallback_mat");
3403 int nonexistent_val;
3404 DOCTEST_CHECK_THROWS(ctx.getDataWithMaterialFallback(p3,
"nonexistent", nonexistent_val));
3407 SUBCASE(
"Material Data - XML Round-Trip with Labels") {
3411 ctx.addMaterial(
"data_round_trip_mat");
3412 ctx.setMaterialColor(
"data_round_trip_mat",
make_RGBAcolor(0.5f, 0.25f, 0.75f, 1));
3413 ctx.setMaterialData(
"data_round_trip_mat",
"twosided_flag", 1u);
3414 ctx.setMaterialData(
"data_round_trip_mat",
"reflectance", 0.8f);
3415 ctx.setMaterialData(
"data_round_trip_mat",
"normal",
make_vec3(0, 0, 1));
3420 ctx.assignMaterialToPrimitive(p1,
"data_round_trip_mat");
3421 ctx.assignMaterialToPrimitive(p2,
"data_round_trip_mat");
3424 ctx.writeXML(
"test_material_data.xml",
true);
3428 ctx2.loadXML(
"test_material_data.xml",
true);
3431 DOCTEST_CHECK(ctx2.doesMaterialExist(
"data_round_trip_mat"));
3433 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"twosided_flag"));
3435 ctx2.getMaterialData(
"data_round_trip_mat",
"twosided_flag", flag_val);
3436 DOCTEST_CHECK(flag_val == 1u);
3438 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"reflectance"));
3440 ctx2.getMaterialData(
"data_round_trip_mat",
"reflectance", refl_val);
3441 DOCTEST_CHECK(refl_val == doctest::Approx(0.8f).epsilon(0.001));
3443 DOCTEST_CHECK(ctx2.doesMaterialDataExist(
"data_round_trip_mat",
"normal"));
3445 ctx2.getMaterialData(
"data_round_trip_mat",
"normal", norm_val);
3446 DOCTEST_CHECK(norm_val.x == doctest::Approx(0.0f).epsilon(0.001));
3447 DOCTEST_CHECK(norm_val.y == doctest::Approx(0.0f).epsilon(0.001));
3448 DOCTEST_CHECK(norm_val.z == doctest::Approx(1.0f).epsilon(0.001));
3451 std::filesystem::remove(
"test_material_data.xml");
3454 SUBCASE(
"Material Methods - getPrimitiveMaterialID and getMaterial") {
3458 ctx.addMaterial(
"test_mat_1");
3460 uint mat1_id = ctx.getMaterialIDFromLabel(
"test_mat_1");
3462 ctx.addMaterial(
"test_mat_2");
3464 uint mat2_id = ctx.getMaterialIDFromLabel(
"test_mat_2");
3471 ctx.assignMaterialToPrimitive(p1,
"test_mat_1");
3472 ctx.assignMaterialToPrimitive(p2,
"test_mat_2");
3473 ctx.assignMaterialToPrimitive(p3,
"test_mat_1");
3476 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p1) == mat1_id);
3477 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p2) == mat2_id);
3478 DOCTEST_CHECK(ctx.getPrimitiveMaterialID(p3) == mat1_id);
3481 const Material &mat1 = ctx.getMaterial(mat1_id);
3482 DOCTEST_CHECK(mat1.label ==
"test_mat_1");
3483 DOCTEST_CHECK(mat1.color.r == doctest::Approx(1.0f));
3484 DOCTEST_CHECK(mat1.color.g == doctest::Approx(0.0f));
3485 DOCTEST_CHECK(mat1.color.b == doctest::Approx(0.0f));
3487 const Material &mat2 = ctx.getMaterial(mat2_id);
3488 DOCTEST_CHECK(mat2.label ==
"test_mat_2");
3489 DOCTEST_CHECK(mat2.color.r == doctest::Approx(0.0f));
3490 DOCTEST_CHECK(mat2.color.g == doctest::Approx(1.0f));
3491 DOCTEST_CHECK(mat2.color.b == doctest::Approx(0.0f));
3494 DOCTEST_CHECK_THROWS((
void) ctx.getMaterial(99999));
3497 SUBCASE(
"Material Methods - getMaterialIDFromLabel") {
3501 ctx.addMaterial(
"material_a");
3502 ctx.addMaterial(
"material_b");
3503 ctx.addMaterial(
"material_c");
3506 uint id_a = ctx.getMaterialIDFromLabel(
"material_a");
3507 uint id_b = ctx.getMaterialIDFromLabel(
"material_b");
3508 uint id_c = ctx.getMaterialIDFromLabel(
"material_c");
3511 DOCTEST_CHECK(id_a != id_b);
3512 DOCTEST_CHECK(id_b != id_c);
3513 DOCTEST_CHECK(id_a != id_c);
3516 DOCTEST_CHECK(ctx.getMaterialIDFromLabel(
"material_a") == id_a);
3517 DOCTEST_CHECK(ctx.getMaterialIDFromLabel(
"material_b") == id_b);
3520 DOCTEST_CHECK_THROWS((
void) ctx.getMaterialIDFromLabel(
"nonexistent_material"));
3523 SUBCASE(
"Material copy-on-write - basic color modification") {
3531 std::string mat1_before =
context.getPrimitiveMaterialLabel(uuid1);
3532 std::string mat2_before =
context.getPrimitiveMaterialLabel(uuid2);
3533 DOCTEST_CHECK(mat1_before == mat2_before);
3536 context.setPrimitiveColor(uuid1, RGB::blue);
3539 std::string mat1_after =
context.getPrimitiveMaterialLabel(uuid1);
3540 std::string mat2_after =
context.getPrimitiveMaterialLabel(uuid2);
3541 DOCTEST_CHECK(mat1_after != mat2_after);
3544 RGBcolor color1 =
context.getPrimitiveColor(uuid1);
3545 RGBcolor color2 =
context.getPrimitiveColor(uuid2);
3546 DOCTEST_CHECK(color1 == RGB::blue);
3547 DOCTEST_CHECK(color2 == RGB::red);
3550 SUBCASE(
"Material copy-on-write - object-level modification") {
3558 context.setObjectColor(obj1, RGB::yellow);
3561 auto prims1 =
context.getObjectPrimitiveUUIDs(obj1);
3562 auto prims2 =
context.getObjectPrimitiveUUIDs(obj2);
3564 RGBcolor color1 =
context.getPrimitiveColor(prims1[0]);
3565 RGBcolor color2 =
context.getPrimitiveColor(prims2[0]);
3567 DOCTEST_CHECK(color1 == RGB::yellow);
3568 DOCTEST_CHECK(color2 == RGB::green);
3571 SUBCASE(
"Material copy-on-write - non-shared optimization") {
3577 std::string mat1 =
context.getPrimitiveMaterialLabel(uuid);
3580 context.setPrimitiveColor(uuid, RGB::magenta);
3582 std::string mat2 =
context.getPrimitiveMaterialLabel(uuid);
3585 DOCTEST_CHECK(mat1 == mat2);
3589TEST_CASE(
"Context Timeseries File Loading") {
3591 SUBCASE(
"ISO-8601 UTC datetime") {
3593 std::string warning_msg;
3595 capture_cerr capture;
3596 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_utc.csv", {},
",",
"ISO8601", 0);
3597 warning_msg = capture.get_captured_output();
3599 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3602 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3603 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3605 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3606 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3608 float humid = ctx.queryTimeseriesData(
"humidity", date,
make_Time(10, 0, 0));
3609 DOCTEST_CHECK(humid == doctest::Approx(0.65f));
3612 Location loc = ctx.getLocation();
3613 DOCTEST_CHECK(loc.UTC_offset == doctest::Approx(0.0f));
3616 SUBCASE(
"ISO-8601 with timezone offset") {
3618 std::string warning_msg;
3620 capture_cerr capture;
3621 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_offset.csv", {},
",",
"ISO8601", 0);
3622 warning_msg = capture.get_captured_output();
3624 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3628 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(2, 0, 0));
3629 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3631 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(4, 0, 0));
3632 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3635 Location loc = ctx.getLocation();
3636 DOCTEST_CHECK(loc.UTC_offset == doctest::Approx(8.0f));
3639 SUBCASE(
"Compact date (YYYYMMDD no delimiters)") {
3641 std::string warning_msg;
3643 capture_cerr capture;
3644 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_compact_date.csv", {},
",",
"YYYYMMDD", 0);
3645 warning_msg = capture.get_captured_output();
3647 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3650 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3651 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3653 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3654 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3657 SUBCASE(
"Compact datetime (YYYYMMDDHH)") {
3659 std::string warning_msg;
3661 capture_cerr capture;
3662 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_compact_datetime.csv", {},
",",
"YYYYMMDDHH", 0);
3663 warning_msg = capture.get_captured_output();
3665 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3668 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3669 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3671 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3672 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3675 SUBCASE(
"Time column (HH:MM and HH:MM:SS)") {
3677 std::string warning_msg;
3679 capture_cerr capture;
3680 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_time_column.csv", {},
",",
"YYYYMMDD", 0);
3681 warning_msg = capture.get_captured_output();
3683 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3686 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 30, 0));
3687 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3690 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(11, 15, 30));
3691 DOCTEST_CHECK(temp2 == doctest::Approx(16.2f));
3693 float temp3 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3694 DOCTEST_CHECK(temp3 == doctest::Approx(17.8f));
3697 SUBCASE(
"Datetime with space separator") {
3699 std::string warning_msg;
3701 capture_cerr capture;
3702 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_datetime_space.csv", {},
" ",
"YYYY-MM-DD HH:MM", 0);
3703 warning_msg = capture.get_captured_output();
3705 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3708 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3709 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3711 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3712 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3715 SUBCASE(
"European DD/MM/YYYY HH:MM datetime") {
3717 std::string warning_msg;
3719 capture_cerr capture;
3720 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_ddmmyyyy_hhmm.csv", {},
",",
"DD/MM/YYYY HH:MM", 0);
3721 warning_msg = capture.get_captured_output();
3723 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3726 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3727 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3729 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3730 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3733 SUBCASE(
"US MM/DD/YYYY HH:MM datetime") {
3735 std::string warning_msg;
3737 capture_cerr capture;
3738 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_mmddyyyy_hhmm.csv", {},
",",
"MM/DD/YYYY HH:MM", 0);
3739 warning_msg = capture.get_captured_output();
3741 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3744 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(10, 0, 0));
3745 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3747 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(12, 0, 0));
3748 DOCTEST_CHECK(temp2 == doctest::Approx(17.8f));
3751 SUBCASE(
"Backward compatibility - existing weather_data.csv") {
3754 std::string warning_msg;
3756 capture_cerr capture;
3757 ctx.loadTabularTimeseriesData(
"lib/testdata/weather_data.csv", {},
",",
"DDMMYYYY", 0);
3758 warning_msg = capture.get_captured_output();
3760 DOCTEST_CHECK(warning_msg.find(
"headerlines argument was specified as zero") != std::string::npos);
3763 float temp = ctx.queryTimeseriesData(
"temperature", date,
make_Time(13, 0, 0));
3764 DOCTEST_CHECK(temp == doctest::Approx(35.32343f));
3766 float temp2 = ctx.queryTimeseriesData(
"temperature", date,
make_Time(14, 0, 0));
3767 DOCTEST_CHECK(temp2 == doctest::Approx(36.23432f));
3770 SUBCASE(
"User-specified column labels") {
3772 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_iso8601_utc.csv",
3773 {
"datetime",
"temp",
"rh"},
",",
"ISO8601", 1);
3776 float temp = ctx.queryTimeseriesData(
"temp", date,
make_Time(10, 0, 0));
3777 DOCTEST_CHECK(temp == doctest::Approx(15.5f));
3779 float rh = ctx.queryTimeseriesData(
"rh", date,
make_Time(10, 0, 0));
3780 DOCTEST_CHECK(rh == doctest::Approx(0.65f));
3783 SUBCASE(
"Real data: Open-Meteo Davis CA (ISO-8601 no seconds)") {
3788 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_openmeteo_davis.csv",
3789 {
"datetime",
"temperature",
"humidity",
"precipitation"},
3796 float temp_midnight = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(0, 0, 0));
3797 DOCTEST_CHECK(temp_midnight == doctest::Approx(9.1f));
3800 float temp_noon = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(12, 0, 0));
3801 DOCTEST_CHECK(temp_noon == doctest::Approx(14.1f));
3803 float humid_noon = ctx.queryTimeseriesData(
"humidity", jan1,
make_Time(12, 0, 0));
3804 DOCTEST_CHECK(humid_noon == doctest::Approx(66.0f));
3807 float precip = ctx.queryTimeseriesData(
"precipitation", jan2,
make_Time(18, 0, 0));
3808 DOCTEST_CHECK(precip == doctest::Approx(1.20f));
3811 SUBCASE(
"Real data: Open-Meteo NYC (ISO-8601 negative temperatures)") {
3814 ctx.loadTabularTimeseriesData(
"lib/testdata/timeseries_openmeteo_nyc.csv",
3815 {
"datetime",
"temperature",
"precipitation"},
3823 float temp = ctx.queryTimeseriesData(
"temperature", jan1,
make_Time(0, 0, 0));
3824 DOCTEST_CHECK(temp == doctest::Approx(1.8f));
3827 float temp_cold = ctx.queryTimeseriesData(
"temperature", jan2,
make_Time(7, 0, 0));
3828 DOCTEST_CHECK(temp_cold == doctest::Approx(-4.6f));
3831 float temp_neg = ctx.queryTimeseriesData(
"temperature", jan2,
make_Time(0, 0, 0));
3832 DOCTEST_CHECK(temp_neg == doctest::Approx(-1.1f));
3835 float temp_warm = ctx.queryTimeseriesData(
"temperature", jan3,
make_Time(13, 0, 0));
3836 DOCTEST_CHECK(temp_warm == doctest::Approx(7.7f));