1.3.49
 
Loading...
Searching...
No Matches
Test_context.h
1#pragma once
2// =================================================================================
3// Suite 4: Context Class
4//
5// Tests for the main Context class, which manages the scene, primitives,
6// objects, data, and simulation state.
7// =================================================================================
8TEST_CASE("Core Context State and Configuration") {
9 SUBCASE("Constructor and basic setup") {
10 Context ctx;
11 DOCTEST_CHECK(ctx.getPrimitiveCount() == 0);
12 DOCTEST_CHECK(ctx.getObjectCount() == 0);
13 DOCTEST_CHECK(!ctx.isGeometryDirty());
14
15 Date d = ctx.getDate();
16 DOCTEST_CHECK(d.day == 1);
17 DOCTEST_CHECK(d.month == 6);
18 DOCTEST_CHECK(d.year == 2000);
19
20 Time t = ctx.getTime();
21 DOCTEST_CHECK(t.hour == 12);
22 DOCTEST_CHECK(t.minute == 0);
23 DOCTEST_CHECK(t.second == 0);
24
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));
29 }
30
31 SUBCASE("Random number generator") {
32 Context ctx;
33 ctx.seedRandomGenerator(12345);
34 std::minstd_rand0 *gen1 = ctx.getRandomGenerator();
35 float rand1 = (*gen1)();
36
37 ctx.seedRandomGenerator(12345);
38 std::minstd_rand0 *gen2 = ctx.getRandomGenerator();
39 float rand2 = (*gen2)();
40
41 DOCTEST_CHECK(rand1 == rand2);
42
43 float r_uniform = ctx.randu();
44 DOCTEST_CHECK(r_uniform >= 0.f);
45 DOCTEST_CHECK(r_uniform <= 1.f);
46
47 float r_norm = ctx.randn();
48 // Hard to test for normality, but let's check it's a number
49 DOCTEST_CHECK(!std::isnan(r_norm));
50 }
51
52 SUBCASE("Random number ranges") {
53 Context ctx;
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));
63 }
64
65 SUBCASE("Texture utility methods") {
66 Context ctx;
67 capture_cerr cerr_buffer;
68 DOCTEST_CHECK_NOTHROW(ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, "lib/images/solid.jpg"));
69 DOCTEST_CHECK_THROWS(ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, "lib/images/missing.png"));
70 DOCTEST_CHECK_THROWS(ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, "lib/images/invalid.txt"));
71
72 Texture tex("lib/images/solid.jpg");
73 DOCTEST_CHECK(tex.getTextureFile() == "lib/images/solid.jpg");
74 int2 res = tex.getImageResolution();
75 DOCTEST_CHECK(res.x == 5);
76 DOCTEST_CHECK(res.y == 5);
77 DOCTEST_CHECK(!tex.hasTransparencyChannel());
78 const auto *alpha = tex.getTransparencyData();
79 DOCTEST_CHECK(alpha->empty());
80 std::vector<vec2> uv{{0.f, 0.f}, {1.f, 0.f}, {1.f, 1.f}};
81 float sf = tex.getSolidFraction(uv);
82 DOCTEST_CHECK(sf == doctest::Approx(1.f));
83 }
84
85 SUBCASE("Geometry dirty flags") {
86 Context ctx;
87 uint p = ctx.addPatch();
88 DOCTEST_CHECK(ctx.isGeometryDirty());
89 DOCTEST_CHECK(ctx.isPrimitiveDirty(p));
90
91 ctx.markGeometryClean();
92 DOCTEST_CHECK(!ctx.isGeometryDirty());
93 DOCTEST_CHECK(!ctx.isPrimitiveDirty(p));
94
95 ctx.markPrimitiveDirty(p);
96 DOCTEST_CHECK(ctx.isGeometryDirty());
97 DOCTEST_CHECK(ctx.isPrimitiveDirty(p));
98
99 ctx.markPrimitiveClean(p);
100 DOCTEST_CHECK(!ctx.isGeometryDirty());
101 DOCTEST_CHECK(!ctx.isPrimitiveDirty(p));
102
103 ctx.markGeometryDirty();
104 DOCTEST_CHECK(ctx.isGeometryDirty());
105 }
106
107 SUBCASE("Geometry dirty flags vector") {
108 Context ctx;
109 std::vector<uint> ids{ctx.addPatch(), ctx.addPatch()};
110 ctx.markGeometryClean();
111 ctx.markPrimitiveDirty(ids);
112 for (uint id: ids) {
113 DOCTEST_CHECK(ctx.isPrimitiveDirty(id));
114 }
115 ctx.markPrimitiveClean(ids);
116 for (uint id: ids) {
117 DOCTEST_CHECK(!ctx.isPrimitiveDirty(id));
118 }
119
120 vec3 shift = make_vec3(1.f, 0.f, 0.f);
121 ctx.translatePrimitive(ids, shift);
122 for (uint id: ids) {
123 vec3 c = ctx.getPatchCenter(id);
124 DOCTEST_CHECK(c.x == doctest::Approx(shift.x).epsilon(errtol));
125 }
126 }
127
128 SUBCASE("Date and Time Manipulation") {
129 Context ctx;
130 ctx.setDate(15, 7, 2025);
131 Date d = ctx.getDate();
132 DOCTEST_CHECK(d.day == 15);
133 DOCTEST_CHECK(d.month == 7);
134 DOCTEST_CHECK(d.year == 2025);
135 DOCTEST_CHECK(strcmp(ctx.getMonthString(), "JUL") == 0);
136 DOCTEST_CHECK(ctx.getJulianDate() == 196);
137
138 ctx.setTime(45, 30, 10);
139 Time t = ctx.getTime();
140 DOCTEST_CHECK(t.hour == 10);
141 DOCTEST_CHECK(t.minute == 30);
142 DOCTEST_CHECK(t.second == 45);
143
144 capture_cerr cerr_buffer;
145 DOCTEST_CHECK_THROWS(ctx.setDate(32, 1, 2025));
146 DOCTEST_CHECK_THROWS(ctx.setTime(60, 0, 0));
147 }
148
149 SUBCASE("Location Manipulation") {
150 Context ctx;
151 Location loc = {40.7128, -74.0060, 10.0};
152 ctx.setLocation(loc);
153 Location l = ctx.getLocation();
154 DOCTEST_CHECK(l.latitude_deg == doctest::Approx(40.7128));
155 DOCTEST_CHECK(l.longitude_deg == doctest::Approx(-74.0060));
156 DOCTEST_CHECK(l.UTC_offset == doctest::Approx(10.0));
157 }
158
159 SUBCASE("primitive orientation and transforms") {
160 Context ctx;
161 uint id = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
162 ctx.markGeometryClean();
163
164 vec3 n = ctx.getPrimitiveNormal(id);
165 DOCTEST_CHECK(n == vec3(0.f, 0.f, 1.f));
166
167 ctx.setPrimitiveElevation(id, make_vec3(0, 0, 0), 0.f);
168 n = ctx.getPrimitiveNormal(id);
169 DOCTEST_CHECK(n.x == doctest::Approx(0.f).epsilon(errtol));
170 DOCTEST_CHECK(n.y == doctest::Approx(1.f).epsilon(errtol));
171 DOCTEST_CHECK(n.z == doctest::Approx(0.f).epsilon(errtol));
172
173 ctx.setPrimitiveAzimuth(id, make_vec3(0, 0, 0), 0.5f * PI_F);
174 n = ctx.getPrimitiveNormal(id);
175 DOCTEST_CHECK(n.x == doctest::Approx(1.f).epsilon(errtol));
176 DOCTEST_CHECK(n.y == doctest::Approx(0.f).epsilon(errtol));
177
178 ctx.setPrimitiveNormal(id, make_vec3(0, 0, 0), make_vec3(0, 0, 1));
179 n = ctx.getPrimitiveNormal(id);
180 DOCTEST_CHECK(n.z == doctest::Approx(1.f).epsilon(errtol));
181
182 float M[16];
183 makeTranslationMatrix(make_vec3(1.f, 2.f, 3.f), M);
184 ctx.setPrimitiveTransformationMatrix(id, M);
185 float out[16];
186 ctx.getPrimitiveTransformationMatrix(id, out);
187 for (int i = 0; i < 16; ++i) {
188 DOCTEST_CHECK(out[i] == doctest::Approx(M[i]));
189 }
190 DOCTEST_CHECK(ctx.isPrimitiveDirty(id));
191 }
192}
193
194TEST_CASE("Primitive Management: Creation, Properties, and Operations") {
195 SUBCASE("addPatch") {
196 vec3 center, center_r;
197 vec2 size, size_r;
198 std::vector<vec3> vertices, vertices_r;
199 SphericalCoord rotation, rotation_r;
200 vec3 normal, normal_r;
201 RGBcolor color, color_r;
202 uint UUID;
203 std::vector<uint> UUIDs;
204 PrimitiveType type;
205 float area_r;
206 uint objID;
207
208 Context context_test;
209
210 // uint addPatch( const vec3& center, const vec2& size );
211 center = make_vec3(1, 2, 3);
212 size = make_vec2(1, 2);
213 vertices.resize(4);
214 vertices.at(0) = center + make_vec3(-0.5f * size.x, -0.5f * size.y, 0.f);
215 vertices.at(1) = center + make_vec3(0.5f * size.x, -0.5f * size.y, 0.f);
216 vertices.at(2) = center + make_vec3(0.5f * size.x, 0.5f * size.y, 0.f);
217 vertices.at(3) = center + make_vec3(-0.5f * size.x, 0.5f * size.y, 0.f);
218
219 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
220 DOCTEST_CHECK_NOTHROW(type = context_test.getPrimitiveType(UUID));
221 DOCTEST_CHECK_NOTHROW(center_r = context_test.getPatchCenter(UUID));
222 DOCTEST_CHECK_NOTHROW(size_r = context_test.getPatchSize(UUID));
223 DOCTEST_CHECK_NOTHROW(normal_r = context_test.getPrimitiveNormal(UUID));
224 DOCTEST_CHECK_NOTHROW(vertices_r = context_test.getPrimitiveVertices(UUID));
225 DOCTEST_CHECK_NOTHROW(area_r = context_test.getPrimitiveArea(UUID));
226 DOCTEST_CHECK_NOTHROW(color_r = context_test.getPrimitiveColor(UUID));
227
228 DOCTEST_CHECK(type == PRIMITIVE_TYPE_PATCH);
229 DOCTEST_CHECK(center_r.x == center.x);
230 DOCTEST_CHECK(center_r.y == center.y);
231 DOCTEST_CHECK(center_r.z == center.z);
232 DOCTEST_CHECK(size_r.x == size.x);
233 DOCTEST_CHECK(size_r.y == size.y);
234 DOCTEST_CHECK(normal_r.x == 0.f);
235 DOCTEST_CHECK(normal_r.y == 0.f);
236 DOCTEST_CHECK(normal_r.z == 1.f);
237 DOCTEST_CHECK(vertices_r.size() == 4);
238 DOCTEST_CHECK(vertices_r.at(0).x == vertices.at(0).x);
239 DOCTEST_CHECK(vertices_r.at(0).y == vertices.at(0).y);
240 DOCTEST_CHECK(vertices_r.at(0).z == vertices.at(0).z);
241 DOCTEST_CHECK(vertices_r.at(1).x == vertices.at(1).x);
242 DOCTEST_CHECK(vertices_r.at(1).y == vertices.at(1).y);
243 DOCTEST_CHECK(vertices_r.at(1).z == vertices.at(1).z);
244 DOCTEST_CHECK(vertices_r.at(2).x == vertices.at(2).x);
245 DOCTEST_CHECK(vertices_r.at(2).y == vertices.at(2).y);
246 DOCTEST_CHECK(vertices_r.at(2).z == vertices.at(2).z);
247 DOCTEST_CHECK(vertices_r.at(3).x == vertices.at(3).x);
248 DOCTEST_CHECK(vertices_r.at(3).y == vertices.at(3).y);
249 DOCTEST_CHECK(vertices_r.at(3).z == vertices.at(3).z);
250 CHECK(area_r == doctest::Approx(size.x * size.y).epsilon(errtol));
251 DOCTEST_CHECK(color_r.r == 0.f);
252 DOCTEST_CHECK(color_r.g == 0.f);
253 DOCTEST_CHECK(color_r.b == 0.f);
254 DOCTEST_CHECK(context_test.getPrimitiveTextureFile(UUID).empty());
255 }
256 SUBCASE("rotated patch") {
257 Context context_test;
258
259 vec3 center = make_vec3(1, 2, 3);
260 vec2 size = make_vec2(1, 2);
261 SphericalCoord rotation = make_SphericalCoord(1.f, 0.15f * PI_F, 0.5f * PI_F);
262 rotation.azimuth = 0.5f * PI_F;
263
264 uint UUID;
265 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size, rotation));
266
267 vec3 normal_r;
268 DOCTEST_CHECK_NOTHROW(normal_r = context_test.getPrimitiveNormal(UUID));
269
270 SphericalCoord rotation_r;
271 DOCTEST_CHECK_NOTHROW(rotation_r = make_SphericalCoord(0.5f * PI_F - asinf(normal_r.z), atan2f(normal_r.x, normal_r.y)));
272
273 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
274
275 DOCTEST_CHECK(rotation_r.elevation == doctest::Approx(rotation.elevation).epsilon(errtol));
276 DOCTEST_CHECK(rotation_r.azimuth == doctest::Approx(rotation.azimuth).epsilon(errtol));
277 }
278 SUBCASE("addTriangle") {
279 Context context_test;
280
281 vec3 v0, v0_r;
282 vec3 v1, v1_r;
283 vec3 v2, v2_r;
284 uint UUID;
285
286 // uint addTriangle( const vec3& v0, const vec3& v1, const vec3& v2, const RGBcolor &color );
287 v0 = make_vec3(1, 2, 3);
288 v1 = make_vec3(2, 4, 6);
289 v2 = make_vec3(3, 6, 5);
290 std::vector<vec3> vertices{v0, v1, v2};
291 RGBcolor color = RGB::red;
292
293 DOCTEST_CHECK_NOTHROW(UUID = context_test.addTriangle(v0, v1, v2, color));
294 DOCTEST_CHECK(context_test.getPrimitiveType(UUID) == PRIMITIVE_TYPE_TRIANGLE);
295
296 vec3 normal = normalize(cross(v1 - v0, v2 - v1));
297 vec3 normal_r = context_test.getPrimitiveNormal(UUID);
298 DOCTEST_CHECK(normal_r.x == doctest::Approx(normal.x).epsilon(errtol));
299 DOCTEST_CHECK(normal_r.y == doctest::Approx(normal.y).epsilon(errtol));
300 DOCTEST_CHECK(normal_r.z == doctest::Approx(normal.z).epsilon(errtol));
301
302 std::vector<vec3> vertices_r;
303 DOCTEST_CHECK_NOTHROW(vertices_r = context_test.getPrimitiveVertices(UUID));
304 DOCTEST_CHECK(vertices_r.size() == 3);
305 DOCTEST_CHECK(vertices_r.at(0).x == v0.x);
306 DOCTEST_CHECK(vertices_r.at(0).y == v0.y);
307 DOCTEST_CHECK(vertices_r.at(0).z == v0.z);
308
309 RGBcolor color_r;
310 DOCTEST_CHECK_NOTHROW(color_r = context_test.getPrimitiveColor(UUID));
311 DOCTEST_CHECK(color_r.r == color.r);
312 DOCTEST_CHECK(color_r.g == color.g);
313 DOCTEST_CHECK(color_r.b == color.b);
314 DOCTEST_CHECK(context_test.getPrimitiveTextureFile(UUID).empty());
315
316 float a = (v1 - v0).magnitude();
317 float b = (v2 - v0).magnitude();
318 float c = (v2 - v1).magnitude();
319 float s = 0.5f * (a + b + c);
320 float area = sqrtf(s * (s - a) * (s - b) * (s - c));
321 float area_r;
322 DOCTEST_CHECK_NOTHROW(area_r = context_test.getPrimitiveArea(UUID));
323 DOCTEST_CHECK(area_r == doctest::Approx(area).epsilon(errtol));
324 }
325 SUBCASE("copyPrimitive (patch)") {
326 Context context_test;
327 uint UUID, UUID_cpy;
328
329 std::vector<float> cpdata{5.2f, 2.5f, 3.1f};
330
331 vec3 center = make_vec3(1, 2, 3);
332 vec2 size = make_vec2(1, 2);
333
334 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
335
336 DOCTEST_CHECK_NOTHROW(context_test.setPrimitiveData(UUID, "somedata", cpdata));
337
338 DOCTEST_CHECK_NOTHROW(UUID_cpy = context_test.copyPrimitive(UUID));
339
340 vec3 center_cpy;
341 DOCTEST_CHECK_NOTHROW(center_cpy = context_test.getPatchCenter(UUID_cpy));
342 vec2 size_cpy;
343 DOCTEST_CHECK_NOTHROW(size_cpy = context_test.getPatchSize(UUID_cpy));
344
345 DOCTEST_CHECK(UUID_cpy == 1);
346 DOCTEST_CHECK(center_cpy.x == center.x);
347 DOCTEST_CHECK(center_cpy.y == center.y);
348 DOCTEST_CHECK(center_cpy.z == center.z);
349 DOCTEST_CHECK(size_cpy.x == size.x);
350 DOCTEST_CHECK(size_cpy.y == size.y);
351
352 std::vector<float> cpdata_copy;
353 context_test.getPrimitiveData(UUID_cpy, "somedata", cpdata_copy);
354
355 DOCTEST_CHECK(cpdata.size() == cpdata_copy.size());
356 for (uint i = 0; i < cpdata.size(); i++) {
357 DOCTEST_CHECK(cpdata.at(i) == cpdata_copy.at(i));
358 }
359
360 // translate the copied patch
361 vec3 shift = make_vec3(5.f, 4.f, 3.f);
362 DOCTEST_CHECK_NOTHROW(context_test.translatePrimitive(UUID_cpy, shift));
363 DOCTEST_CHECK_NOTHROW(center_cpy = context_test.getPatchCenter(UUID_cpy));
364 vec3 center_r;
365 DOCTEST_CHECK_NOTHROW(center_r = context_test.getPatchCenter(UUID));
366
367 DOCTEST_CHECK(center_cpy.x == doctest::Approx(center.x + shift.x).epsilon(errtol));
368 DOCTEST_CHECK(center_cpy.y == doctest::Approx(center.y + shift.y).epsilon(errtol));
369 DOCTEST_CHECK(center_cpy.z == doctest::Approx(center.z + shift.z).epsilon(errtol));
370 DOCTEST_CHECK(center_r.x == center.x);
371 DOCTEST_CHECK(center_r.y == center.y);
372 DOCTEST_CHECK(center_r.z == center.z);
373 }
374 SUBCASE("copyPrimitive (triangle)") {
375 Context context_test;
376
377 vec3 v0 = make_vec3(0, 0, 0);
378 vec3 v1 = make_vec3(1, 0, 0);
379 vec3 v2 = make_vec3(0, 1, 0);
380 uint UUID, UUID_cpy;
381
382 DOCTEST_CHECK_NOTHROW(UUID = context_test.addTriangle(v0, v1, v2, RGB::blue));
383 DOCTEST_CHECK_NOTHROW(UUID_cpy = context_test.copyPrimitive(UUID));
384
385 std::vector<vec3> verts_org, verts_cpy;
386 DOCTEST_CHECK_NOTHROW(verts_org = context_test.getPrimitiveVertices(UUID));
387 DOCTEST_CHECK_NOTHROW(verts_cpy = context_test.getPrimitiveVertices(UUID_cpy));
388 DOCTEST_CHECK(verts_org == verts_cpy);
389
390 vec3 shift = make_vec3(5.f, 4.f, 3.f);
391 DOCTEST_CHECK_NOTHROW(context_test.translatePrimitive(UUID_cpy, shift));
392 DOCTEST_CHECK_NOTHROW(verts_cpy = context_test.getPrimitiveVertices(UUID_cpy));
393 DOCTEST_CHECK(verts_cpy.at(0) == verts_org.at(0) + shift);
394 DOCTEST_CHECK(verts_cpy.at(1) == verts_org.at(1) + shift);
395 DOCTEST_CHECK(verts_cpy.at(2) == verts_org.at(2) + shift);
396
397 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
398 DOCTEST_CHECK(!context_test.doesPrimitiveExist(UUID));
399 }
400 SUBCASE("deletePrimitive") {
401 Context context_test;
402 uint UUID;
403 vec3 center = make_vec3(1, 2, 3);
404 vec2 size = make_vec2(1, 2);
405
406 DOCTEST_CHECK_NOTHROW(UUID = context_test.addPatch(center, size));
407
408 DOCTEST_CHECK_NOTHROW(context_test.deletePrimitive(UUID));
409
410 uint primitive_count;
411 DOCTEST_CHECK_NOTHROW(primitive_count = context_test.getPrimitiveCount(UUID));
412 DOCTEST_CHECK(primitive_count == 0);
413 DOCTEST_CHECK(!context_test.doesPrimitiveExist(UUID));
414 }
415 SUBCASE("primitive bounding box") {
416 Context context_test;
417 std::vector<uint> UUIDs;
418 UUIDs.push_back(context_test.addPatch(make_vec3(-1, 0, 0), make_vec2(0.5, 0.5)));
419 UUIDs.push_back(context_test.addPatch(make_vec3(1, 0, 0), make_vec2(0.5, 0.5)));
420
421 vec3 bmin, bmax;
422 DOCTEST_CHECK_NOTHROW(context_test.getPrimitiveBoundingBox(UUIDs, bmin, bmax));
423 DOCTEST_CHECK(bmin == make_vec3(-1.25f, -0.25f, 0.f));
424 DOCTEST_CHECK(bmax == make_vec3(1.25f, 0.25f, 0.f));
425 }
426 SUBCASE("primitive scale and data") {
427 Context context_test;
428 vec2 sz_0 = make_vec2(0.5f, 3.f);
429 float area0 = sz_0.x * sz_0.y;
430 float scale = 2.6f;
431 uint UUID = context_test.addPatch(make_vec3(0, 0, 0), sz_0);
432 context_test.scalePrimitive(UUID, make_vec3(scale, scale, scale));
433 float area1 = context_test.getPrimitiveArea(UUID);
434 DOCTEST_CHECK(area1 == doctest::Approx(scale * scale * area0).epsilon(1e-5));
435
436 float data = 5.f;
437 context_test.setPrimitiveData(UUID, "some_data", data);
438 DOCTEST_CHECK(context_test.doesPrimitiveDataExist(UUID, "some_data"));
439 float data_r;
440 context_test.getPrimitiveData(UUID, "some_data", data_r);
441 DOCTEST_CHECK(data_r == data);
442
443 std::vector<float> vec = {0, 1, 2, 3, 4};
444 context_test.setPrimitiveData(UUID, "vec_data", vec);
445 std::vector<float> vec_r;
446 context_test.getPrimitiveData(UUID, "vec_data", vec_r);
447 DOCTEST_CHECK(vec_r == vec);
448
449 std::vector<uint> UUIDs_filter;
450 std::vector<uint> UUIDs_multi;
451 for (uint i = 0; i < 4; i++) {
452 UUIDs_multi.push_back(context_test.addPatch());
453 }
454 context_test.setPrimitiveData(UUIDs_multi[0], "val", 4.f);
455 context_test.setPrimitiveData(UUIDs_multi[0], "str", "cat");
456 context_test.setPrimitiveData(UUIDs_multi[1], "val", 3.f);
457 context_test.setPrimitiveData(UUIDs_multi[1], "str", "cat");
458 context_test.setPrimitiveData(UUIDs_multi[2], "val", 2.f);
459 context_test.setPrimitiveData(UUIDs_multi[2], "str", "dog");
460 context_test.setPrimitiveData(UUIDs_multi[3], "val", 1.f);
461 context_test.setPrimitiveData(UUIDs_multi[3], "str", "dog");
462
463 UUIDs_filter = context_test.filterPrimitivesByData(UUIDs_multi, "val", 2.f, "<=");
464 DOCTEST_CHECK(UUIDs_filter.size() == 2);
465 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[2]) != UUIDs_filter.end());
466 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[3]) != UUIDs_filter.end());
467
468 UUIDs_filter = context_test.filterPrimitivesByData(UUIDs_multi, "str", "cat");
469 DOCTEST_CHECK(UUIDs_filter.size() == 2);
470 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[0]) != UUIDs_filter.end());
471 DOCTEST_CHECK(std::find(UUIDs_filter.begin(), UUIDs_filter.end(), UUIDs_multi[1]) != UUIDs_filter.end());
472 }
473 SUBCASE("texture uv and solid fraction") {
474 Context context_test;
475
476 vec2 sizep = make_vec2(2, 3);
477 const char *texture = "lib/images/disk_texture.png";
478 vec2 uv0 = make_vec2(0, 0);
479 vec2 uv1 = make_vec2(1, 0);
480 vec2 uv2 = make_vec2(1, 1);
481 vec2 uv3 = make_vec2(0, 1);
482 uint UUIDp = context_test.addPatch(make_vec3(2, 3, 4), sizep, nullrotation, texture, 0.5f * (uv0 + uv2), uv2 - uv0);
483 DOCTEST_CHECK(!context_test.getPrimitiveTextureFile(UUIDp).empty());
484 float Ap = context_test.getPrimitiveArea(UUIDp);
485 DOCTEST_CHECK(Ap == doctest::Approx(0.25f * PI_F * sizep.x * sizep.y).epsilon(0.01));
486 std::vector<vec2> uv = context_test.getPrimitiveTextureUV(UUIDp);
487 DOCTEST_CHECK(uv.size() == 4);
488 DOCTEST_CHECK(uv.at(0) == uv0);
489 DOCTEST_CHECK(uv.at(1) == uv1);
490 DOCTEST_CHECK(uv.at(2) == uv2);
491 DOCTEST_CHECK(uv.at(3) == uv3);
492
493 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));
494 float solid_fraction = context_test.getPrimitiveSolidFraction(UUIDt);
495 DOCTEST_CHECK(solid_fraction == doctest::Approx(0.5f).epsilon(errtol));
496 }
497
498 SUBCASE("advanced primitive transforms") {
499 Context ctx;
500 uint p1 = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
501 uint p2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 1));
502 std::vector<uint> ids{p1, p2};
503 ctx.markGeometryClean();
504
505 ctx.rotatePrimitive(p1, 0.5f * PI_F, "x");
506 vec3 n = ctx.getPrimitiveNormal(p1);
507 DOCTEST_CHECK(n.y == doctest::Approx(-1.f).epsilon(errtol));
508 DOCTEST_CHECK(n.z == doctest::Approx(0.f).epsilon(errtol));
509
510 ctx.rotatePrimitive(ids, PI_F, make_vec3(0, 1, 0));
511 vec3 c = ctx.getPatchCenter(p2);
512 DOCTEST_CHECK(c.x == doctest::Approx(-1.f).epsilon(errtol));
513
514 ctx.rotatePrimitive(p1, PI_F, make_vec3(0, 0, 0), make_vec3(0, 0, 1));
515
516 ctx.scalePrimitiveAboutPoint(p2, make_vec3(2.f, 2.f, 2.f), make_vec3(0, 0, 0));
517 vec2 sz = ctx.getPatchSize(p2);
518 DOCTEST_CHECK(sz.x == doctest::Approx(2.f).epsilon(errtol));
519
520 ctx.scalePrimitiveAboutPoint(ids, make_vec3(0.5f, 0.5f, 0.5f), make_vec3(0, 0, 0));
521 sz = ctx.getPatchSize(p2);
522 DOCTEST_CHECK(sz.x == doctest::Approx(1.f).epsilon(errtol));
523 }
524}
525
526TEST_CASE("Object Management") {
527 SUBCASE("addBoxObject") {
528 Context context_test;
529
530 vec3 center = make_vec3(1, 2, 3);
531 vec3 size = make_vec3(3, 2, 1);
532 int3 subdiv(1, 1, 1);
533
534 uint objID;
535 DOCTEST_CHECK_NOTHROW(objID = context_test.addBoxObject(center, size, subdiv));
536 std::vector<uint> UUIDs = context_test.getObjectPointer(objID)->getPrimitiveUUIDs();
537
538 DOCTEST_CHECK(UUIDs.size() == 6);
539 vec3 normal_r = context_test.getPrimitiveNormal(UUIDs.at(0));
540 DOCTEST_CHECK(doctest::Approx(normal_r.magnitude()).epsilon(errtol) == 1.f);
541 normal_r = context_test.getPrimitiveNormal(UUIDs.at(2));
542 DOCTEST_CHECK(doctest::Approx(normal_r.magnitude()).epsilon(errtol) == 1.f);
543
544 vec2 size_r = context_test.getPatchSize(UUIDs.at(0));
545 DOCTEST_CHECK(size_r.x == doctest::Approx(size.x).epsilon(errtol));
546 DOCTEST_CHECK(size_r.y == doctest::Approx(size.z).epsilon(errtol));
547
548 size_r = context_test.getPatchSize(UUIDs.at(2));
549 DOCTEST_CHECK(size_r.x == doctest::Approx(size.y).epsilon(errtol));
550 DOCTEST_CHECK(size_r.y == doctest::Approx(size.z).epsilon(errtol));
551
552 float volume = context_test.getBoxObjectVolume(objID);
553 DOCTEST_CHECK(volume == doctest::Approx(size.x * size.y * size.z).epsilon(errtol));
554 }
555 SUBCASE("addTileObject rotated") {
556 Context context_test;
557
558 vec3 center = make_vec3(1, 2, 3);
559 vec2 size = make_vec2(3, 2);
560 int2 subdiv(3, 3);
561 SphericalCoord rotation = make_SphericalCoord(0.25f * PI_F, 1.4f * PI_F);
562 uint objID = context_test.addTileObject(center, size, rotation, subdiv);
563
564 std::vector<uint> UUIDs = context_test.getObjectPointer(objID)->getPrimitiveUUIDs();
565 for (uint UUIDp: UUIDs) {
566 vec3 n = context_test.getPrimitiveNormal(UUIDp);
567 SphericalCoord rot = cart2sphere(n);
568 DOCTEST_CHECK(rot.zenith == doctest::Approx(rotation.zenith).epsilon(errtol));
569 DOCTEST_CHECK(rot.azimuth == doctest::Approx(rotation.azimuth).epsilon(errtol));
570 }
571 }
572 SUBCASE("textured tile area") {
573 Context context_test;
574
575 vec3 center = make_vec3(1, 2, 3);
576 vec2 size = make_vec2(3, 2);
577 int2 subdiv = make_int2(5, 5);
578 SphericalCoord rotation = make_SphericalCoord(0.1f * PI_F, 2.4f * PI_F);
579
580 uint objID = context_test.addTileObject(center, size, rotation, subdiv, "lib/images/disk_texture.png");
581 std::vector<uint> UUIDs = context_test.getObjectPointer(objID)->getPrimitiveUUIDs();
582 float area_sum = 0.f;
583 for (uint UUID: UUIDs) {
584 area_sum += context_test.getPrimitiveArea(UUID);
585 }
586 float area_exact = 0.25f * PI_F * size.x * size.y;
587 DOCTEST_CHECK(area_sum == doctest::Approx(area_exact).epsilon(5e-3));
588 }
589 SUBCASE("cone object transforms") {
590 Context context_test;
591 float r0 = 0.5f, r1 = 1.f, len = 2.f;
592 vec3 node0 = make_vec3(0, 0, 0);
593 vec3 node1 = make_vec3(0, 0, len);
594 uint cone = context_test.addConeObject(50, node0, node1, r0, r1);
595 context_test.getConeObjectPointer(cone)->translate(make_vec3(1, 1, 1));
596 std::vector<vec3> nodes = context_test.getConeObjectPointer(cone)->getNodeCoordinates();
597 DOCTEST_CHECK(nodes.at(0) == make_vec3(1, 1, 1));
598 DOCTEST_CHECK(nodes.at(1) == make_vec3(1, 1, 1 + len));
599 vec3 axis = cross(make_vec3(0, 0, 1), make_vec3(1, 0, 0));
600 float ang = acos_safe(make_vec3(1, 0, 0) * make_vec3(0, 0, 1));
601 context_test.getConeObjectPointer(cone)->translate(-nodes.at(0));
602 context_test.getConeObjectPointer(cone)->rotate(ang, axis);
603 context_test.getConeObjectPointer(cone)->translate(nodes.at(0));
604 nodes = context_test.getConeObjectPointer(cone)->getNodeCoordinates();
605 DOCTEST_CHECK(nodes.at(1).x == doctest::Approx(nodes.at(0).x + len).epsilon(errtol));
606 context_test.getConeObjectPointer(cone)->scaleLength(2.0);
607 nodes = context_test.getConeObjectPointer(cone)->getNodeCoordinates();
608 DOCTEST_CHECK(nodes.at(1).x == doctest::Approx(nodes.at(0).x + 2 * len).epsilon(errtol));
609 context_test.getConeObjectPointer(cone)->scaleGirth(2.0);
610 std::vector<float> radii = context_test.getConeObjectPointer(cone)->getNodeRadii();
611 DOCTEST_CHECK(radii.at(0) == doctest::Approx(2 * r0).epsilon(errtol));
612 DOCTEST_CHECK(radii.at(1) == doctest::Approx(2 * r1).epsilon(errtol));
613 }
614
615 SUBCASE("rotate and scale objects") {
616 Context ctx;
617 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
618 ctx.rotateObject(obj, 0.5f * PI_F, "z");
619 vec3 bmin, bmax;
620 ctx.getObjectBoundingBox(obj, bmin, bmax);
621 DOCTEST_CHECK(bmax.x == doctest::Approx(0.5f).epsilon(errtol));
622
623 ctx.scaleObjectAboutPoint(obj, make_vec3(2.f, 2.f, 2.f), make_vec3(0, 0, 0));
624 ctx.getObjectBoundingBox(obj, bmin, bmax);
625 DOCTEST_CHECK(bmax.x > 0.5f);
626 }
627
628 SUBCASE("domain bounding sphere") {
629 Context ctx;
630 std::vector<uint> ids;
631 ids.push_back(ctx.addPatch(make_vec3(-1, 0, 0), make_vec2(1, 1)));
632 ids.push_back(ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 1)));
633 vec3 c;
634 float r;
635 ctx.getDomainBoundingSphere(ids, c, r);
636 DOCTEST_CHECK(c.x == doctest::Approx(0.f).epsilon(errtol));
637 DOCTEST_CHECK(r > 1.f);
638 }
639
640 SUBCASE("copy and delete objects") {
641 Context ctx;
642 uint obj1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
643 uint obj2 = ctx.copyObject(obj1);
644 DOCTEST_CHECK(ctx.doesObjectExist(obj1));
645 DOCTEST_CHECK(ctx.doesObjectExist(obj2));
646 ctx.deleteObject(obj2);
647 DOCTEST_CHECK(!ctx.doesObjectExist(obj2));
648 }
649
650 SUBCASE("domain cropping") {
651 capture_cerr cerr_buffer;
652 Context ctx;
653 uint p1 = ctx.addPatch(make_vec3(-2.f, 0.f, 0.f), make_vec2(1, 1));
654 uint p2 = ctx.addPatch(make_vec3(2.f, 0.f, 0.f), make_vec2(1, 1));
655 uint p3 = ctx.addPatch(make_vec3(0.f, 3.f, 0.f), make_vec2(1, 1));
656 uint p4 = ctx.addPatch(make_vec3(0.f, 0.f, 3.f), make_vec2(1, 1));
657 ctx.cropDomainX(make_vec2(-1.f, 1.f));
658 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p1));
659 ctx.cropDomainY(make_vec2(-1.f, 1.f));
660 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p3));
661 ctx.cropDomainZ(make_vec2(-1.f, 1.f));
662 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p4));
663 DOCTEST_CHECK(cerr_buffer.has_output());
664 cerr_buffer.clear();
665 std::vector<uint> ids_rem = ctx.getAllUUIDs();
666 ctx.cropDomain(ids_rem, make_vec2(-0.5f, 1.f), make_vec2(-0.5f, 1.f), make_vec2(-0.5f, 1.f));
667 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p2));
668 DOCTEST_CHECK(cerr_buffer.has_output());
669 }
670}
671
672TEST_CASE("Data Management") {
673 SUBCASE("global and object data") {
674 Context context_test;
675 float gdata = 5.f;
676 context_test.setGlobalData("some_data", gdata);
677 float gdata_r;
678 DOCTEST_CHECK(context_test.doesGlobalDataExist("some_data"));
679 context_test.getGlobalData("some_data", gdata_r);
680 DOCTEST_CHECK(gdata_r == gdata);
681
682 std::vector<float> gvec{0, 1, 2, 3, 4};
683 context_test.setGlobalData("vec", gvec);
684 std::vector<float> gvec_r;
685 context_test.getGlobalData("vec", gvec_r);
686 DOCTEST_CHECK(gvec_r == gvec);
687
688 uint objID = context_test.addTileObject(make_vec3(0, 0, 0), make_vec2(3, 1), nullrotation, make_int2(3, 3));
689 float objdata = 7.f;
690 context_test.setObjectData(objID, "obj", objdata);
691 float objdata_r;
692 context_test.getObjectData(objID, "obj", objdata_r);
693 DOCTEST_CHECK(objdata_r == objdata);
694 }
695 SUBCASE("timeseries") {
696 Context ctx;
697 Date date = make_Date(12, 3, 2010);
698 ctx.setDate(date);
699 Time time0 = make_Time(13, 15, 39);
700 ctx.setTime(time0);
701 Time time1 = make_Time(time0.hour, 49, 14);
702 ctx.addTimeseriesData("ts", 302.3f, date, time0);
703 ctx.addTimeseriesData("ts", 305.3f, date, time1);
704 ctx.setCurrentTimeseriesPoint("ts", 0);
705 DOCTEST_CHECK(ctx.getTimeseriesLength("ts") == 2);
706 DOCTEST_CHECK(ctx.queryTimeseriesData("ts", 0) == doctest::Approx(302.3f));
707 DOCTEST_CHECK(ctx.queryTimeseriesData("ts", 1) == doctest::Approx(305.3f));
708 float val = ctx.queryTimeseriesData("ts", date, time1);
709 DOCTEST_CHECK(val == doctest::Approx(305.3f));
710 DOCTEST_CHECK(ctx.doesTimeseriesVariableExist("ts"));
711 std::vector<std::string> labels = ctx.listTimeseriesVariables();
712 DOCTEST_CHECK(std::find(labels.begin(), labels.end(), "ts") != labels.end());
713 DOCTEST_CHECK(ctx.queryTimeseriesData("ts", ctx.getTimeseriesLength("ts") - 1) == doctest::Approx(305.3f));
714 Time t1_r = ctx.queryTimeseriesTime("ts", 1);
715 Date d1_r = ctx.queryTimeseriesDate("ts", 1);
716 DOCTEST_CHECK(t1_r.minute == time1.minute);
717 DOCTEST_CHECK(d1_r.day == date.day);
718 ctx.setCurrentTimeseriesPoint("ts", 1);
719 DOCTEST_CHECK(ctx.queryTimeseriesData("ts") == doctest::Approx(305.3f));
720 }
721
722 SUBCASE("Primitive data") {
723 capture_cerr cerr_buffer;
724
725 Context ctx;
726 uint p = ctx.addPatch();
727 ctx.setPrimitiveData(p, "test_int", 5);
728 ctx.setPrimitiveData(p, "test_float", 3.14f);
729
730 // getPrimitiveDataType
731 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_int") == HELIOS_TYPE_INT);
732 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_float") == HELIOS_TYPE_FLOAT);
733
734 // getPrimitiveDataSize
735 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "test_int") == 1);
736
737 // clearPrimitiveData
738 ctx.clearPrimitiveData(p, "test_int");
739 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "test_int"));
740
741 // listPrimitiveData
742 std::vector<std::string> data_labels = ctx.listPrimitiveData(p);
743 DOCTEST_CHECK(std::find(data_labels.begin(), data_labels.end(), "test_float") != data_labels.end());
744
745 // getPrimitiveDataSize (doesn't exist)
746 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataSize(p, "test_int"));
747
748 // clearPrimitiveData
749 ctx.clearPrimitiveData(p, "test_int");
750 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "test_int"));
751
752 // listPrimitiveData
753 ctx.setPrimitiveData(p, "test_int", 5);
754 ctx.setPrimitiveData(p, "test_float", 3.14f);
755 std::vector<std::string> labels = ctx.listPrimitiveData(p);
756 DOCTEST_CHECK(labels.size() == 2);
757 DOCTEST_CHECK(std::find(labels.begin(), labels.end(), "test_int") != labels.end());
758 DOCTEST_CHECK(std::find(labels.begin(), labels.end(), "test_float") != labels.end());
759 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_float") == HELIOS_TYPE_FLOAT);
760 }
761}
762
763TEST_CASE("Data and Object Management") {
764
765 SUBCASE("Global data management") {
766 Context ctx;
767 ctx.setGlobalData("test_double", 1.23);
768 DOCTEST_CHECK(ctx.getGlobalDataSize("test_double") == 1);
769 DOCTEST_CHECK(ctx.getGlobalDataType("test_double") == HELIOS_TYPE_DOUBLE);
770 ctx.clearGlobalData("test_double");
771 DOCTEST_CHECK(!ctx.doesGlobalDataExist("test_double"));
772 ctx.setGlobalData("test_string", "hello");
773 std::vector<std::string> global_data_labels = ctx.listGlobalData();
774 DOCTEST_CHECK(std::find(global_data_labels.begin(), global_data_labels.end(), "test_string") != global_data_labels.end());
775 }
776
777 SUBCASE("Object data management") {
778 Context ctx;
779 uint obj = ctx.addBoxObject(nullorigin, make_vec3(1, 1, 1), make_int3(2, 3, 2));
780 ctx.setObjectData(obj, "test_vec", vec3(1, 2, 3));
781 DOCTEST_CHECK(ctx.getObjectDataSize(obj, "test_vec") == 1);
782 DOCTEST_CHECK(ctx.getObjectDataType("test_vec") == HELIOS_TYPE_VEC3);
783 ctx.clearObjectData(obj, "test_vec");
784 DOCTEST_CHECK(!ctx.doesObjectDataExist(obj, "test_vec"));
785 ctx.setObjectData(obj, "test_int", 42);
786 std::vector<std::string> object_data_labels = ctx.listObjectData(obj);
787 DOCTEST_CHECK(std::find(object_data_labels.begin(), object_data_labels.end(), "test_int") != object_data_labels.end());
788 }
789
790 SUBCASE("Object creation and manipulation") {
791 Context ctx;
792 uint disk = ctx.addDiskObject(10, make_vec3(0, 0, 0), make_vec2(1, 1));
793 DOCTEST_CHECK(ctx.getDiskObjectPointer(disk)->getObjectType() == OBJECT_TYPE_DISK);
794 DOCTEST_CHECK(ctx.getObjectArea(disk) > 0);
795 DOCTEST_CHECK(ctx.getDiskObjectCenter(disk) == make_vec3(0, 0, 0));
796 DOCTEST_CHECK(ctx.getDiskObjectSubdivisionCount(disk) == 10);
797 DOCTEST_CHECK(ctx.getDiskObjectSize(disk).x == doctest::Approx(1.f));
798
799 uint sphere = ctx.addSphereObject(10, make_vec3(1, 1, 1), 0.5f);
800 DOCTEST_CHECK(ctx.getSphereObjectPointer(sphere)->getObjectType() == OBJECT_TYPE_SPHERE);
801 DOCTEST_CHECK(ctx.getObjectArea(sphere) > 0);
802 DOCTEST_CHECK(ctx.getSphereObjectCenter(sphere) == make_vec3(1, 1, 1));
803 DOCTEST_CHECK(ctx.getSphereObjectSubdivisionCount(sphere) == 10);
804 DOCTEST_CHECK(ctx.getSphereObjectRadius(sphere).x == doctest::Approx(0.5f));
805
806 std::vector<uint> p_uuids;
807 p_uuids.push_back(ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0)));
808 uint polymesh = ctx.addPolymeshObject(p_uuids);
809 DOCTEST_CHECK(ctx.getPolymeshObjectPointer(polymesh)->getObjectType() == OBJECT_TYPE_POLYMESH);
810 DOCTEST_CHECK(ctx.getObjectArea(polymesh) > 0);
811 DOCTEST_CHECK(ctx.getObjectCenter(polymesh).z == doctest::Approx(0.f));
812
813 std::vector<vec3> nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1)};
814 std::vector<float> radii = {0.2f, 0.1f};
815 uint tube = ctx.addTubeObject(10, nodes, radii);
816 DOCTEST_CHECK(ctx.getTubeObjectPointer(tube)->getObjectType() == OBJECT_TYPE_TUBE);
817 DOCTEST_CHECK(ctx.getObjectArea(tube) > 0);
818 DOCTEST_CHECK(ctx.getObjectCenter(tube).z == doctest::Approx(0.5f));
819 DOCTEST_CHECK(ctx.getTubeObjectSubdivisionCount(tube) == 10);
820 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 2);
821 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube).size() == 2);
822 DOCTEST_CHECK(ctx.getTubeObjectNodeColors(tube).size() == 2);
823 DOCTEST_CHECK(ctx.getTubeObjectVolume(tube) > 0);
824 ctx.appendTubeSegment(tube, make_vec3(0, 0, 2), 0.05f, RGB::red);
825 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
826 ctx.scaleTubeGirth(tube, 2.f);
827 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube)[0] == doctest::Approx(0.4f));
828 std::vector<float> new_radii = {0.3f, 0.2f, 0.1f};
829 ctx.setTubeRadii(tube, new_radii);
830 DOCTEST_CHECK(ctx.getTubeObjectNodeRadii(tube)[0] == doctest::Approx(0.3f));
831 ctx.scaleTubeLength(tube, 2.f);
832 std::vector<vec3> new_nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1), make_vec3(0, 0, 2)};
833 ctx.setTubeNodes(tube, new_nodes);
834 ctx.pruneTubeNodes(tube, 1);
835 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 1);
836 }
837
838 SUBCASE("Object appearance and visibility") {
839 Context ctx;
840 uint box = ctx.addBoxObject(nullorigin, make_vec3(1, 1, 1), make_int3(2, 3, 2));
841 ctx.overrideObjectTextureColor(box);
842 // Cannot check state, only that it runs
843 ctx.useObjectTextureColor(box);
844 // Cannot check state, only that it runs
845 ctx.hideObject(box);
846 DOCTEST_CHECK(ctx.isObjectHidden(box));
847 ctx.showObject(box);
848 DOCTEST_CHECK(!ctx.isObjectHidden(box));
849
850 std::vector<uint> prims = ctx.getObjectPrimitiveUUIDs(box);
851 ctx.hidePrimitive(prims);
852 DOCTEST_CHECK(ctx.isPrimitiveHidden(prims[0]));
853 ctx.showPrimitive(prims);
854 DOCTEST_CHECK(!ctx.isPrimitiveHidden(prims[0]));
855 }
856
857 SUBCASE("Primitive color and parent object") {
858 Context ctx;
859 uint p = ctx.addPatch();
860 ctx.setPrimitiveColor(p, RGB::red);
861 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::red);
862 ctx.overridePrimitiveTextureColor(p);
863 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(p));
864 ctx.usePrimitiveTextureColor(p);
865 DOCTEST_CHECK(!ctx.isPrimitiveTextureColorOverridden(p));
866 uint obj = ctx.addBoxObject(nullorigin, make_vec3(1, 1, 1), make_int3(2, 3, 2));
867 ctx.setPrimitiveParentObjectID(p, obj);
868 DOCTEST_CHECK(ctx.getPrimitiveParentObjectID(p) == obj);
869 }
870}
871TEST_CASE("Object Management: Creation and Properties") {
872
873 SUBCASE("addSphereObject") {
874 Context ctx;
875 uint objID = ctx.addSphereObject(10, make_vec3(1, 2, 3), 5.f);
876 DOCTEST_CHECK(ctx.doesObjectExist(objID));
877 Sphere *sphere = ctx.getSphereObjectPointer(objID);
878 DOCTEST_CHECK(sphere != nullptr);
879 DOCTEST_CHECK(sphere->getCenter() == make_vec3(1, 2, 3));
880 DOCTEST_CHECK(sphere->getRadius() == make_vec3(5.f, 5.f, 5.f));
881 DOCTEST_CHECK(sphere->getSubdivisionCount() == 10);
882 }
883
884 SUBCASE("addDiskObject") {
885 Context ctx;
886 uint objID = ctx.addDiskObject(make_int2(8, 16), make_vec3(1, 2, 3), make_vec2(4, 5), nullrotation, RGB::red);
887 DOCTEST_CHECK(ctx.doesObjectExist(objID));
888 Disk *disk = ctx.getDiskObjectPointer(objID);
889 DOCTEST_CHECK(disk != nullptr);
890 DOCTEST_CHECK(disk->getCenter() == make_vec3(1, 2, 3));
891 DOCTEST_CHECK(disk->getSize() == make_vec2(4, 5));
892 DOCTEST_CHECK(disk->getSubdivisionCount() == make_int2(8, 16));
893 }
894
895 SUBCASE("addConeObject") {
896 Context ctx;
897 uint objID = ctx.addConeObject(10, make_vec3(0, 0, 0), make_vec3(0, 0, 5), 2.f, 1.f);
898 DOCTEST_CHECK(ctx.doesObjectExist(objID));
899 Cone *cone = ctx.getConeObjectPointer(objID);
900 DOCTEST_CHECK(cone != nullptr);
901 DOCTEST_CHECK(cone->getNodeCoordinate(0) == make_vec3(0, 0, 0));
902 DOCTEST_CHECK(cone->getNodeCoordinate(1) == make_vec3(0, 0, 5));
903 DOCTEST_CHECK(cone->getNodeRadius(0) == 2.f);
904 DOCTEST_CHECK(cone->getNodeRadius(1) == 1.f);
905 DOCTEST_CHECK(cone->getSubdivisionCount() == 10);
906 }
907}
908
909TEST_CASE("Global Data Management") {
910 SUBCASE("Integer Data") {
911 Context ctx;
912 ctx.setGlobalData("test_int", 123);
913 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_int"));
914 int val;
915 ctx.getGlobalData("test_int", val);
916 DOCTEST_CHECK(val == 123);
917 DOCTEST_CHECK(ctx.getGlobalDataSize("test_int") == 1);
918 DOCTEST_CHECK(ctx.getGlobalDataType("test_int") == HELIOS_TYPE_INT);
919 ctx.clearGlobalData("test_int");
920 DOCTEST_CHECK(!ctx.doesGlobalDataExist("test_int"));
921 }
922
923 SUBCASE("Vector Data") {
924 Context ctx;
925 std::vector<vec3> vec_data = {{1, 2, 3}, {4, 5, 6}};
926 ctx.setGlobalData("test_vec", vec_data);
927 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_vec"));
928 std::vector<vec3> read_vec;
929 ctx.getGlobalData("test_vec", read_vec);
930 DOCTEST_CHECK(read_vec.size() == 2);
931 DOCTEST_CHECK(read_vec[1] == make_vec3(4, 5, 6));
932 DOCTEST_CHECK(ctx.getGlobalDataSize("test_vec") == 2);
933 DOCTEST_CHECK(ctx.getGlobalDataType("test_vec") == HELIOS_TYPE_VEC3);
934 }
935
936 SUBCASE("List Data") {
937 Context ctx;
938 ctx.setGlobalData("d1", 1);
939 ctx.setGlobalData("d2", 2.f);
940 std::vector<std::string> labels = ctx.listGlobalData();
941 DOCTEST_CHECK(labels.size() == 2);
942 DOCTEST_CHECK(std::find(labels.begin(), labels.end(), "d1") != labels.end());
943 }
944}
945
946TEST_CASE("Context primitive data management") {
947 Context ctx;
948 uint p1 = ctx.addPatch();
949 uint p2 = ctx.addPatch();
950 ctx.setPrimitiveData(p1, "my_data", 10);
951
952 // copyPrimitiveData
953 ctx.copyPrimitiveData(p1, p2);
954 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2, "my_data"));
955 int val;
956 ctx.getPrimitiveData(p2, "my_data", val);
957 DOCTEST_CHECK(val == 10);
958
959 // renamePrimitiveData
960 ctx.renamePrimitiveData(p1, "my_data", "new_data_name");
961 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p1, "my_data"));
962 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1, "new_data_name"));
963
964 // duplicatePrimitiveData
965 ctx.duplicatePrimitiveData(p2, "my_data", "my_data_copy");
966 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2, "my_data_copy"));
967 ctx.getPrimitiveData(p2, "my_data_copy", val);
968 DOCTEST_CHECK(val == 10);
969
970 // duplicatePrimitiveData (all primitives)
971 ctx.setPrimitiveData(p1, "global_copy_test", 5.5f);
972 ctx.duplicatePrimitiveData("global_copy_test", "global_copy_test_new");
973 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1, "global_copy_test_new"));
974 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p2, "global_copy_test_new")); // p2 doesn't have original
975
976 ctx.clearPrimitiveData(p1, "new_data_name");
977 ctx.setPrimitiveData(p2, "my_data_copy", 15);
978 ctx.setPrimitiveData(p2, "my_data_copy", 20);
979 ctx.clearPrimitiveData(p2, "my_data_copy");
980 std::vector<std::string> all_labels = ctx.listAllPrimitiveDataLabels();
981 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "my_data") != all_labels.end());
982 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "my_data_copy") == all_labels.end());
983 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "new_data_name") == all_labels.end());
984}
985
986TEST_CASE("Context primitive data calculations") {
987 Context ctx;
988 std::vector<uint> uuids;
989 for (int i = 0; i < 5; ++i) {
990 uint p = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
991 ctx.setPrimitiveData(p, "float_val", (float) i);
992 ctx.setPrimitiveData(p, "double_val", (double) i);
993 ctx.setPrimitiveData(p, "vec2_val", make_vec2((float) i, (float) i));
994 uuids.push_back(p);
995 }
996
997 // calculatePrimitiveDataMean
998 float float_mean;
999 ctx.calculatePrimitiveDataMean(uuids, "float_val", float_mean);
1000 DOCTEST_CHECK(float_mean == doctest::Approx(2.0f));
1001 double double_mean;
1002 ctx.calculatePrimitiveDataMean(uuids, "double_val", double_mean);
1003 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
1004 vec2 vec2_mean;
1005 ctx.calculatePrimitiveDataMean(uuids, "vec2_val", vec2_mean);
1006 DOCTEST_CHECK(vec2_mean.x == doctest::Approx(2.0f));
1007
1008 // calculatePrimitiveDataAreaWeightedMean
1009 float awt_mean_f;
1010 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "float_val", awt_mean_f);
1011 DOCTEST_CHECK(awt_mean_f == doctest::Approx(2.0f)); // Area is 1 for all
1012
1013 // calculatePrimitiveDataSum
1014 float float_sum;
1015 ctx.calculatePrimitiveDataSum(uuids, "float_val", float_sum);
1016 DOCTEST_CHECK(float_sum == doctest::Approx(10.0f));
1017
1018 // calculatePrimitiveDataAreaWeightedSum
1019 float awt_sum_f;
1020 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "float_val", awt_sum_f);
1021 DOCTEST_CHECK(awt_sum_f == doctest::Approx(10.0f));
1022
1023 // scalePrimitiveData
1024 ctx.scalePrimitiveData(uuids, "float_val", 2.0f);
1025 ctx.getPrimitiveData(uuids[2], "float_val", float_mean);
1026 DOCTEST_CHECK(float_mean == doctest::Approx(4.0f));
1027 ctx.scalePrimitiveData("double_val", 0.5f);
1028 ctx.getPrimitiveData(uuids[4], "double_val", double_mean);
1029 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
1030
1031 // incrementPrimitiveData
1032 ctx.setPrimitiveData(uuids, "int_val", 10);
1033 ctx.incrementPrimitiveData(uuids, "int_val", 5);
1034 int int_val;
1035 ctx.getPrimitiveData(uuids[0], "int_val", int_val);
1036 DOCTEST_CHECK(int_val == 15);
1037 capture_cerr cerr_buffer;
1038 ctx.incrementPrimitiveData(uuids, "float_val", 1); // Wrong type, should warn
1039 DOCTEST_CHECK(cerr_buffer.has_output());
1040}
1041
1042TEST_CASE("Context primitive data aggregation and filtering") {
1043 Context ctx;
1044 std::vector<uint> uuids;
1045 for (int i = 0; i < 3; ++i) {
1046 uint p = ctx.addPatch();
1047 ctx.setPrimitiveData(p, "d1", (float) i);
1048 ctx.setPrimitiveData(p, "d2", (float) i * 2.0f);
1049 ctx.setPrimitiveData(p, "d3", (float) i * 3.0f);
1050 ctx.setPrimitiveData(p, "filter_me", i);
1051 uuids.push_back(p);
1052 }
1053
1054 // aggregatePrimitiveDataSum
1055 std::vector<std::string> labels = {"d1", "d2", "d3"};
1056 ctx.aggregatePrimitiveDataSum(uuids, labels, "sum_data");
1057 float sum_val;
1058 ctx.getPrimitiveData(uuids[1], "sum_data", sum_val);
1059 DOCTEST_CHECK(sum_val == doctest::Approx(1.f + 2.f + 3.f));
1060
1061 // aggregatePrimitiveDataProduct
1062 ctx.aggregatePrimitiveDataProduct(uuids, labels, "prod_data");
1063 float prod_val;
1064 ctx.getPrimitiveData(uuids[2], "prod_data", prod_val);
1065 DOCTEST_CHECK(prod_val == doctest::Approx(2.f * 4.f * 6.f));
1066
1067 // filterPrimitivesByData
1068 std::vector<uint> filtered = ctx.filterPrimitivesByData(uuids, "filter_me", 1, ">=");
1069 DOCTEST_CHECK(filtered.size() == 2);
1070 filtered = ctx.filterPrimitivesByData(uuids, "filter_me", 1, "==");
1071 DOCTEST_CHECK(filtered.size() == 1);
1072 DOCTEST_CHECK(filtered[0] == uuids[1]);
1073 capture_cerr cerr_buffer;
1074 DOCTEST_CHECK_THROWS_AS(filtered = ctx.filterPrimitivesByData(uuids, "filter_me", 1, "!!"), std::runtime_error);
1075}
1076
1077TEST_CASE("Object data") {
1078 Context ctx;
1079 uint o = ctx.addTileObject(nullorigin, make_vec2(1, 1), nullrotation, make_int2(2, 2));
1080 ctx.setObjectData(o, "test_int", 5);
1081 ctx.setObjectData(o, "test_float", 3.14f);
1082
1083 // getObjectDataType
1084 DOCTEST_CHECK(ctx.getObjectDataType("test_int") == HELIOS_TYPE_INT);
1085#ifdef HELIOS_DEBUG
1086 capture_cerr cerr_buffer;
1087 DOCTEST_CHECK_THROWS_AS(ctx.getObjectDataType("non_existent"), std::runtime_error);
1088#endif
1089
1090 // getObjectDataSize
1091 DOCTEST_CHECK(ctx.getObjectDataSize(o, "test_int") == 1);
1092
1093 // clearObjectData
1094 ctx.clearObjectData(o, "test_int");
1095 DOCTEST_CHECK(!ctx.doesObjectDataExist(o, "test_int"));
1096
1097 // listObjectData
1098 std::vector<std::string> data_labels = ctx.listObjectData(o);
1099 DOCTEST_CHECK(std::find(data_labels.begin(), data_labels.end(), "test_float") != data_labels.end());
1100}
1101
1102TEST_CASE("Context object data management") {
1103 Context ctx;
1104 uint o1 = ctx.addTileObject(nullorigin, make_vec2(1, 1), nullrotation, make_int2(2, 2));
1105 uint o2 = ctx.addTileObject(nullorigin, make_vec2(1, 1), nullrotation, make_int2(2, 2));
1106 ctx.setObjectData(o1, "my_data", 10);
1107
1108 // copyObjectData
1109 ctx.copyObjectData(o1, o2);
1110 DOCTEST_CHECK(ctx.doesObjectDataExist(o2, "my_data"));
1111
1112 // renameObjectData
1113 ctx.renameObjectData(o1, "my_data", "new_name");
1114 DOCTEST_CHECK(!ctx.doesObjectDataExist(o1, "my_data"));
1115 DOCTEST_CHECK(ctx.doesObjectDataExist(o1, "new_name"));
1116
1117 // duplicateObjectData
1118 ctx.duplicateObjectData(o2, "my_data", "my_data_copy");
1119 DOCTEST_CHECK(ctx.doesObjectDataExist(o2, "my_data_copy"));
1120
1121 std::vector<std::string> all_obj_labels = ctx.listAllObjectDataLabels();
1122 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "my_data") != all_obj_labels.end());
1123 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "my_data_copy") != all_obj_labels.end());
1124 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "new_name") != all_obj_labels.end());
1125}
1126
1127TEST_CASE("Global data") {
1128 Context ctx;
1129 ctx.setGlobalData("g_int", 5);
1130 ctx.setGlobalData("g_float", 3.14f);
1131
1132 // getGlobalDataType/Size/Exists
1133 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int"));
1134 DOCTEST_CHECK(ctx.getGlobalDataType("g_int") == HELIOS_TYPE_INT);
1135 DOCTEST_CHECK(ctx.getGlobalDataSize("g_int") == 1);
1136
1137 // rename/duplicate/clear
1138 ctx.duplicateGlobalData("g_int", "g_int_copy");
1139 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int_copy"));
1140 ctx.renameGlobalData("g_int", "g_int_new");
1141 DOCTEST_CHECK(!ctx.doesGlobalDataExist("g_int"));
1142 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int_new"));
1143 ctx.clearGlobalData("g_int_new");
1144 DOCTEST_CHECK(!ctx.doesGlobalDataExist("g_int_new"));
1145
1146 // listGlobalData
1147 std::vector<std::string> g_labels = ctx.listGlobalData();
1148 DOCTEST_CHECK(g_labels.size() > 0);
1149
1150 // incrementGlobalData
1151 ctx.setGlobalData("inc_me", 10);
1152 ctx.incrementGlobalData("inc_me", 5);
1153 int val;
1154 ctx.getGlobalData("inc_me", val);
1155 DOCTEST_CHECK(val == 15);
1156 capture_cerr cerr_buffer;
1157 ctx.incrementGlobalData("g_float", 1); // Wrong type
1158 DOCTEST_CHECK(cerr_buffer.has_output());
1159}
1160
1161TEST_CASE("Voxel Management") {
1162 SUBCASE("addVoxel and voxel properties") {
1163 Context ctx;
1164
1165 vec3 center = make_vec3(1, 2, 3);
1166 vec3 size = make_vec3(2, 4, 6);
1167 float rotation = 0.5f * PI_F;
1168
1169 uint vox1 = ctx.addVoxel(center, size);
1170 DOCTEST_CHECK(ctx.getPrimitiveType(vox1) == PRIMITIVE_TYPE_VOXEL);
1171 DOCTEST_CHECK(ctx.getVoxelCenter(vox1) == center);
1172 DOCTEST_CHECK(ctx.getVoxelSize(vox1) == size);
1173
1174 uint vox2 = ctx.addVoxel(center, size, rotation);
1175 DOCTEST_CHECK(ctx.getVoxelCenter(vox2) == center);
1176 DOCTEST_CHECK(ctx.getVoxelSize(vox2) == size);
1177
1178 uint vox3 = ctx.addVoxel(center, size, rotation, RGB::red);
1179 DOCTEST_CHECK(ctx.getPrimitiveColor(vox3) == RGB::red);
1180
1181 uint vox4 = ctx.addVoxel(center, size, rotation, RGBA::red);
1182 RGBAcolor color_rgba = ctx.getPrimitiveColorRGBA(vox4);
1183 DOCTEST_CHECK(color_rgba.r == RGBA::red.r);
1184 DOCTEST_CHECK(color_rgba.a == RGBA::red.a);
1185
1186 DOCTEST_CHECK(ctx.getPrimitiveCount() >= 4);
1187
1188 float area = ctx.getPrimitiveArea(vox1);
1189 DOCTEST_CHECK(area == doctest::Approx(2.f * (size.x * size.y + size.y * size.z + size.x * size.z)));
1190 }
1191}
1192
1193TEST_CASE("Texture Management") {
1194 SUBCASE("texture validation and properties") {
1195 Context ctx;
1196
1197 uint patch = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, "lib/images/solid.jpg");
1198 ctx.setPrimitiveTextureFile(patch, "lib/images/solid.jpg");
1199 DOCTEST_CHECK(ctx.getPrimitiveTextureFile(patch) == "lib/images/solid.jpg");
1200 DOCTEST_CHECK(!ctx.primitiveTextureHasTransparencyChannel(patch));
1201
1202 Texture tex("lib/images/solid.jpg");
1203 std::vector<vec2> uv = {{0, 0}, {1, 0}, {1, 1}};
1204 float solid_frac = tex.getSolidFraction(uv);
1205 DOCTEST_CHECK(solid_frac == doctest::Approx(1.f));
1206 }
1207}
1208
1209TEST_CASE("Triangle Management") {
1210 SUBCASE("setTriangleVertices") {
1211 Context ctx;
1212 vec3 v0 = make_vec3(0, 0, 0);
1213 vec3 v1 = make_vec3(1, 0, 0);
1214 vec3 v2 = make_vec3(0, 1, 0);
1215 uint tri = ctx.addTriangle(v0, v1, v2);
1216
1217 vec3 new_v0 = make_vec3(1, 1, 1);
1218 vec3 new_v1 = make_vec3(2, 1, 1);
1219 vec3 new_v2 = make_vec3(1, 2, 1);
1220 ctx.setTriangleVertices(tri, new_v0, new_v1, new_v2);
1221
1222 std::vector<vec3> vertices = ctx.getPrimitiveVertices(tri);
1223 DOCTEST_CHECK(vertices[0] == new_v0);
1224 DOCTEST_CHECK(vertices[1] == new_v1);
1225 DOCTEST_CHECK(vertices[2] == new_v2);
1226 }
1227}
1228
1229TEST_CASE("UUID and Object Management") {
1230 SUBCASE("getAllUUIDs and cleanDeletedUUIDs") {
1231 Context ctx;
1232 uint p1 = ctx.addPatch();
1233 uint p2 = ctx.addPatch();
1234 uint p3 = ctx.addPatch();
1235
1236 std::vector<uint> all_uuids = ctx.getAllUUIDs();
1237 DOCTEST_CHECK(all_uuids.size() == 3);
1238 DOCTEST_CHECK(std::find(all_uuids.begin(), all_uuids.end(), p1) != all_uuids.end());
1239
1240 ctx.deletePrimitive(p2);
1241 std::vector<uint> uuids_with_deleted = {p1, p2, p3};
1242 ctx.cleanDeletedUUIDs(uuids_with_deleted);
1243 DOCTEST_CHECK(uuids_with_deleted.size() == 2);
1244 DOCTEST_CHECK(std::find(uuids_with_deleted.begin(), uuids_with_deleted.end(), p2) == uuids_with_deleted.end());
1245
1246 std::vector<std::vector<uint>> nested_uuids = {{p1, p2}, {p3, p2}};
1247 ctx.cleanDeletedUUIDs(nested_uuids);
1248 DOCTEST_CHECK(nested_uuids[0].size() == 1);
1249 DOCTEST_CHECK(nested_uuids[1].size() == 1);
1250
1251 std::vector<std::vector<std::vector<uint>>> triple_nested = {{{p1, p2, p3}}};
1252 ctx.cleanDeletedUUIDs(triple_nested);
1253 DOCTEST_CHECK(triple_nested[0][0].size() == 2);
1254 }
1255
1256 SUBCASE("object management utilities") {
1257 Context ctx;
1258 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1259
1260 DOCTEST_CHECK(ctx.areObjectPrimitivesComplete(obj));
1261
1262 std::vector<uint> obj_ids = {obj, 999};
1263 ctx.cleanDeletedObjectIDs(obj_ids);
1264 DOCTEST_CHECK(obj_ids.size() == 1);
1265 DOCTEST_CHECK(obj_ids[0] == obj);
1266
1267 std::vector<std::vector<uint>> nested_obj_ids = {{obj, 999}, {obj}};
1268 ctx.cleanDeletedObjectIDs(nested_obj_ids);
1269 DOCTEST_CHECK(nested_obj_ids[0].size() == 1);
1270 DOCTEST_CHECK(nested_obj_ids[1].size() == 1);
1271
1272 std::vector<std::vector<std::vector<uint>>> triple_nested_obj = {{{obj, 999}}};
1273 ctx.cleanDeletedObjectIDs(triple_nested_obj);
1274 DOCTEST_CHECK(triple_nested_obj[0][0].size() == 1);
1275
1276 Box *box_ptr = ctx.getBoxObjectPointer(obj);
1277 DOCTEST_CHECK(box_ptr != nullptr);
1278
1279 vec3 new_origin = make_vec3(5, 5, 5);
1280 ctx.setObjectOrigin(obj, new_origin);
1281
1282 vec3 new_normal = make_vec3(0, 1, 0);
1283 ctx.setObjectAverageNormal(obj, make_vec3(0, 0, 0), new_normal);
1284 }
1285}
1286
1287TEST_CASE("Tile Object Advanced Features") {
1288 SUBCASE("tile object subdivision management") {
1289 Context ctx;
1290 uint tile = ctx.addTileObject(make_vec3(0, 0, 0), make_vec2(4, 4), nullrotation, make_int2(2, 2));
1291
1292 float area_ratio = ctx.getTileObjectAreaRatio(tile);
1293 DOCTEST_CHECK(area_ratio > 0.f);
1294
1295 ctx.setTileObjectSubdivisionCount({tile}, make_int2(4, 4));
1296
1297 ctx.setTileObjectSubdivisionCount({tile}, 0.5f);
1298 }
1299}
1300
1301TEST_CASE("Pseudocolor Visualization") {
1302 SUBCASE("colorPrimitiveByDataPseudocolor") {
1303 Context ctx;
1304 std::vector<uint> patches;
1305 for (int i = 0; i < 5; i++) {
1306 uint p = ctx.addPatch();
1307 ctx.setPrimitiveData(p, "value", float(i));
1308 patches.push_back(p);
1309 }
1310
1311 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches, "value", "hot", 10));
1312 DOCTEST_CHECK_NOTHROW(ctx.colorPrimitiveByDataPseudocolor(patches, "value", "rainbow", 5, 0.f, 4.f));
1313 }
1314}
1315
1316TEST_CASE("Date and Time Extensions") {
1317 SUBCASE("getMonthString") {
1318 Context ctx;
1319 ctx.setDate(15, 1, 2025);
1320 DOCTEST_CHECK(strcmp(ctx.getMonthString(), "JAN") == 0);
1321 ctx.setDate(15, 2, 2025);
1322 DOCTEST_CHECK(strcmp(ctx.getMonthString(), "FEB") == 0);
1323 ctx.setDate(15, 12, 2025);
1324 DOCTEST_CHECK(strcmp(ctx.getMonthString(), "DEC") == 0);
1325 }
1326}
1327
1328TEST_CASE("Tube Object Management") {
1329 SUBCASE("appendTubeSegment with texture") {
1330 Context ctx;
1331 std::vector<vec3> nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1)};
1332 std::vector<float> radii = {0.2f, 0.1f};
1333 uint tube = ctx.addTubeObject(10, nodes, radii);
1334
1335 ctx.appendTubeSegment(tube, make_vec3(0, 0, 2), 0.05f, "lib/images/solid.jpg", make_vec2(0.5f, 1.0f));
1336 DOCTEST_CHECK(ctx.getTubeObjectNodeCount(tube) == 3);
1337 }
1338}
1339
1340TEST_CASE("Edge Cases and Additional Coverage") {
1341 SUBCASE("Julian date edge cases") {
1342 Context ctx;
1343 ctx.setDate(1, 1, 2025);
1344 DOCTEST_CHECK(ctx.getJulianDate() == 1);
1345
1346 ctx.setDate(31, 12, 2025);
1347 DOCTEST_CHECK(ctx.getJulianDate() == 365);
1348
1349 ctx.setDate(100, 2025);
1350 Date d = ctx.getDate();
1351 DOCTEST_CHECK(d.day == 10);
1352 DOCTEST_CHECK(d.month == 4);
1353 }
1354
1355 SUBCASE("time edge cases") {
1356 Context ctx;
1357 ctx.setTime(0, 0, 0);
1358 Time t = ctx.getTime();
1359 DOCTEST_CHECK(t.hour == 0);
1360 DOCTEST_CHECK(t.minute == 0);
1361 DOCTEST_CHECK(t.second == 0);
1362
1363 ctx.setTime(59, 59, 23);
1364 t = ctx.getTime();
1365 DOCTEST_CHECK(t.hour == 23);
1366 DOCTEST_CHECK(t.minute == 59);
1367 DOCTEST_CHECK(t.second == 59);
1368 }
1369
1370 SUBCASE("random number edge cases") {
1371 Context ctx;
1372 ctx.seedRandomGenerator(0);
1373
1374 float r1 = ctx.randu(5.f, 5.f);
1375 DOCTEST_CHECK(r1 == doctest::Approx(5.f));
1376
1377 int ri = ctx.randu(10, 10);
1378 DOCTEST_CHECK(ri == 10);
1379
1380 float rn = ctx.randn(0.f, 0.f);
1381 DOCTEST_CHECK(rn == doctest::Approx(0.f));
1382 }
1383
1384 SUBCASE("texture edge cases") {
1385 Context ctx;
1386 uint patch = ctx.addPatch();
1387
1388 ctx.overridePrimitiveTextureColor(patch);
1389 ctx.usePrimitiveTextureColor(patch);
1390
1391 std::vector<uint> patches = {patch};
1392 ctx.overridePrimitiveTextureColor(patches);
1393 ctx.usePrimitiveTextureColor(patches);
1394
1395 DOCTEST_CHECK(!ctx.isPrimitiveTextureColorOverridden(patch));
1396 }
1397
1398 SUBCASE("primitive existence checks") {
1399 Context ctx;
1400 uint p1 = ctx.addPatch();
1401 uint p2 = ctx.addPatch();
1402
1403 DOCTEST_CHECK(ctx.doesPrimitiveExist(p1));
1404 DOCTEST_CHECK(ctx.doesPrimitiveExist({p1, p2}));
1405
1406 ctx.deletePrimitive(p1);
1407 DOCTEST_CHECK(!ctx.doesPrimitiveExist(p1));
1408 DOCTEST_CHECK(!ctx.doesPrimitiveExist({p1, p2}));
1409 DOCTEST_CHECK(ctx.doesPrimitiveExist(std::vector<uint>{p2}));
1410 }
1411
1412 SUBCASE("object containment checks") {
1413 Context ctx;
1414 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1415 std::vector<uint> prims = ctx.getObjectPrimitiveUUIDs(obj);
1416
1417 DOCTEST_CHECK(ctx.doesObjectContainPrimitive(obj, prims[0]));
1418
1419 uint independent_patch = ctx.addPatch();
1420 DOCTEST_CHECK(!ctx.doesObjectContainPrimitive(obj, independent_patch));
1421 }
1422
1423 SUBCASE("transformation matrix operations") {
1424 Context ctx;
1425 uint p = ctx.addPatch();
1426
1427 float identity[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
1428 ctx.setPrimitiveTransformationMatrix(p, identity);
1429
1430 std::vector<uint> patches = {p};
1431 ctx.setPrimitiveTransformationMatrix(patches, identity);
1432
1433 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1434 ctx.setObjectTransformationMatrix(obj, identity);
1435
1436 std::vector<uint> objs = {obj};
1437 ctx.setObjectTransformationMatrix(objs, identity);
1438
1439 float retrieved[16];
1440 ctx.getObjectTransformationMatrix(obj, retrieved);
1441 for (int i = 0; i < 16; i++) {
1442 DOCTEST_CHECK(retrieved[i] == doctest::Approx(identity[i]));
1443 }
1444 }
1445
1446 SUBCASE("object type and texture checks") {
1447 Context ctx;
1448 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1449
1450 DOCTEST_CHECK(!ctx.objectHasTexture(obj));
1451
1452 uint textured_obj = ctx.addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, make_int2(2, 2), "lib/images/solid.jpg");
1453 DOCTEST_CHECK(ctx.objectHasTexture(textured_obj));
1454 }
1455
1456 SUBCASE("tube object segment operations") {
1457 Context ctx;
1458 std::vector<vec3> nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1), make_vec3(0, 0, 2)};
1459 std::vector<float> radii = {0.2f, 0.15f, 0.1f};
1460 uint tube = ctx.addTubeObject(10, nodes, radii);
1461
1462 float seg_volume = ctx.getTubeObjectSegmentVolume(tube, 0);
1463 DOCTEST_CHECK(seg_volume > 0.f);
1464
1465 seg_volume = ctx.getTubeObjectSegmentVolume(tube, 1);
1466 DOCTEST_CHECK(seg_volume > 0.f);
1467 }
1468
1469 SUBCASE("cone object advanced properties") {
1470 Context ctx;
1471 uint cone = ctx.addConeObject(10, make_vec3(0, 0, 0), make_vec3(0, 0, 2), 1.f, 0.5f);
1472
1473 float radius0 = ctx.getConeObjectNodeRadius(cone, 0);
1474 DOCTEST_CHECK(radius0 == doctest::Approx(1.f));
1475
1476 float radius1 = ctx.getConeObjectNodeRadius(cone, 1);
1477 DOCTEST_CHECK(radius1 == doctest::Approx(0.5f));
1478
1479 float length = ctx.getConeObjectLength(cone);
1480 DOCTEST_CHECK(length == doctest::Approx(2.f));
1481
1482 DOCTEST_CHECK(ctx.getConeObjectSubdivisionCount(cone) == 10);
1483 }
1484
1485 SUBCASE("primitive color operations") {
1486 Context ctx;
1487 uint p = ctx.addPatch();
1488
1489 ctx.setPrimitiveColor(p, RGB::blue);
1490 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::blue);
1491
1492 ctx.setPrimitiveColor(p, RGBA::green);
1493 RGBAcolor rgba = ctx.getPrimitiveColorRGBA(p);
1494 DOCTEST_CHECK(rgba.r == RGBA::green.r);
1495 DOCTEST_CHECK(rgba.a == RGBA::green.a);
1496
1497 std::vector<uint> patches = {p};
1498 ctx.setPrimitiveColor(patches, RGB::red);
1499 DOCTEST_CHECK(ctx.getPrimitiveColor(p) == RGB::red);
1500
1501 ctx.setPrimitiveColor(patches, RGBA::yellow);
1502 rgba = ctx.getPrimitiveColorRGBA(p);
1503 DOCTEST_CHECK(rgba.r == RGBA::yellow.r);
1504 }
1505
1506 SUBCASE("object color operations") {
1507 Context ctx;
1508 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1509
1510 ctx.setObjectColor(obj, RGB::cyan);
1511 ctx.setObjectColor(obj, RGBA::magenta);
1512
1513 std::vector<uint> objs = {obj};
1514 ctx.setObjectColor(objs, RGB::white);
1515 ctx.setObjectColor(objs, RGBA::black);
1516 }
1517}
1518
1519TEST_CASE("Print and Information Functions") {
1520 SUBCASE("printPrimitiveInfo and printObjectInfo") {
1521 Context ctx;
1522 uint patch = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
1523 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1524
1525 // Capture stdout output from these functions
1526 capture_cout cout_buffer;
1527 DOCTEST_CHECK_NOTHROW(ctx.printPrimitiveInfo(patch));
1528 DOCTEST_CHECK_NOTHROW(ctx.printObjectInfo(obj));
1529
1530 // Verify that output was captured (functions should produce output)
1531 DOCTEST_CHECK(cout_buffer.has_output());
1532 std::string output = cout_buffer.get_captured_output();
1533 DOCTEST_CHECK(output.find("Info for UUID") != std::string::npos);
1534 DOCTEST_CHECK(output.find("Info for ObjID") != std::string::npos);
1535 }
1536}
1537
1538TEST_CASE("Object Pointer Access") {
1539 SUBCASE("getObjectPointer functions") {
1540 Context ctx;
1541
1542 uint box = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1543 Box *box_ptr = ctx.getBoxObjectPointer(box);
1544 DOCTEST_CHECK(box_ptr != nullptr);
1545
1546 uint disk = ctx.addDiskObject(10, make_vec3(0, 0, 0), make_vec2(1, 1));
1547 Disk *disk_ptr = ctx.getDiskObjectPointer(disk);
1548 DOCTEST_CHECK(disk_ptr != nullptr);
1549
1550 uint sphere = ctx.addSphereObject(10, make_vec3(0, 0, 0), 1.f);
1551 Sphere *sphere_ptr = ctx.getSphereObjectPointer(sphere);
1552 DOCTEST_CHECK(sphere_ptr != nullptr);
1553
1554 std::vector<vec3> nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1)};
1555 std::vector<float> radii = {0.2f, 0.1f};
1556 uint tube = ctx.addTubeObject(10, nodes, radii);
1557 Tube *tube_ptr = ctx.getTubeObjectPointer(tube);
1558 DOCTEST_CHECK(tube_ptr != nullptr);
1559
1560 uint cone = ctx.addConeObject(10, make_vec3(0, 0, 0), make_vec3(0, 0, 1), 0.5f, 0.3f);
1561 Cone *cone_ptr = ctx.getConeObjectPointer(cone);
1562 DOCTEST_CHECK(cone_ptr != nullptr);
1563
1564 std::vector<uint> prim_uuids = {ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0))};
1565 uint polymesh = ctx.addPolymeshObject(prim_uuids);
1566 Polymesh *polymesh_ptr = ctx.getPolymeshObjectPointer(polymesh);
1567 DOCTEST_CHECK(polymesh_ptr != nullptr);
1568 }
1569}
1570
1571TEST_CASE("Advanced Primitive Operations") {
1572 SUBCASE("primitive visibility and print operations") {
1573 Context ctx;
1574 uint p1 = ctx.addPatch();
1575 uint p2 = ctx.addPatch();
1576
1577 // Test hiding/showing primitives
1578 ctx.hidePrimitive(p1);
1579 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
1580 ctx.showPrimitive(p1);
1581 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
1582
1583 std::vector<uint> patches = {p1, p2};
1584 ctx.hidePrimitive(patches);
1585 DOCTEST_CHECK(ctx.isPrimitiveHidden(p1));
1586 DOCTEST_CHECK(ctx.isPrimitiveHidden(p2));
1587 ctx.showPrimitive(patches);
1588 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p1));
1589 DOCTEST_CHECK(!ctx.isPrimitiveHidden(p2));
1590 }
1591
1592 SUBCASE("primitive counts by type") {
1593 Context ctx;
1594 uint initial_patch_count = ctx.getPatchCount();
1595 uint initial_triangle_count = ctx.getTriangleCount();
1596
1597 uint p1 = ctx.addPatch();
1598 uint p2 = ctx.addPatch();
1599 uint tri = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0));
1600
1601 DOCTEST_CHECK(ctx.getPatchCount() == initial_patch_count + 2);
1602 DOCTEST_CHECK(ctx.getTriangleCount() == initial_triangle_count + 1);
1603
1604 // Test with hidden primitives
1605 ctx.hidePrimitive(p1);
1606 DOCTEST_CHECK(ctx.getPatchCount(false) == initial_patch_count + 1); // exclude hidden
1607 DOCTEST_CHECK(ctx.getPatchCount(true) == initial_patch_count + 2); // include hidden
1608 }
1609}
1610
1611TEST_CASE("Data Type and Size Functions") {
1612 SUBCASE("primitive data type operations") {
1613 Context ctx;
1614 uint p = ctx.addPatch();
1615
1616 ctx.setPrimitiveData(p, "test_int", 42);
1617 ctx.setPrimitiveData(p, "test_float", 3.14f);
1618 ctx.setPrimitiveData(p, "test_vec3", make_vec3(1, 2, 3));
1619
1620 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_int") == HELIOS_TYPE_INT);
1621 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_float") == HELIOS_TYPE_FLOAT);
1622 DOCTEST_CHECK(ctx.getPrimitiveDataType("test_vec3") == HELIOS_TYPE_VEC3);
1623
1624 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "test_int") == 1);
1625 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "test_vec3") == 1);
1626
1627 std::vector<float> vec_data = {1.0f, 2.0f, 3.0f};
1628 ctx.setPrimitiveData(p, "test_vector", vec_data);
1629 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "test_vector") == 3);
1630 }
1631}
1632
1633TEST_CASE("Additional Missing Coverage") {
1634 SUBCASE("getDirtyUUIDs function") {
1635 Context ctx;
1636 uint p1 = ctx.addPatch();
1637 uint p2 = ctx.addPatch();
1638
1639 ctx.markGeometryClean();
1640 std::vector<uint> dirty_uuids = ctx.getDirtyUUIDs();
1641 DOCTEST_CHECK(dirty_uuids.empty());
1642
1643 ctx.markPrimitiveDirty(p1);
1644 dirty_uuids = ctx.getDirtyUUIDs();
1645 DOCTEST_CHECK(dirty_uuids.size() == 1);
1646 DOCTEST_CHECK(std::find(dirty_uuids.begin(), dirty_uuids.end(), p1) != dirty_uuids.end());
1647 }
1648}
1649
1650TEST_CASE("Advanced Object Operations") {
1651 SUBCASE("object primitive count and area calculations") {
1652 Context ctx;
1653 uint obj = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(2, 3, 4), make_int3(1, 1, 1));
1654
1655 DOCTEST_CHECK(ctx.getObjectPrimitiveCount(obj) == 6); // 6 faces of a box
1656
1657 float area = ctx.getObjectArea(obj);
1658 float expected_area = 2 * (2 * 3 + 3 * 4 + 2 * 4); // surface area of box
1659 DOCTEST_CHECK(area == doctest::Approx(expected_area).epsilon(0.01));
1660 }
1661
1662 SUBCASE("object bounding box operations") {
1663 Context ctx;
1664 uint obj = ctx.addBoxObject(make_vec3(1, 2, 3), make_vec3(2, 4, 6), make_int3(1, 1, 1));
1665
1666 vec3 min_corner, max_corner;
1667 ctx.getObjectBoundingBox(obj, min_corner, max_corner);
1668
1669 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
1670 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
1671 DOCTEST_CHECK(min_corner.y == doctest::Approx(0.f).epsilon(0.01));
1672 DOCTEST_CHECK(max_corner.y == doctest::Approx(4.f).epsilon(0.01));
1673
1674 std::vector<uint> objs = {obj};
1675 ctx.getObjectBoundingBox(objs, min_corner, max_corner);
1676 DOCTEST_CHECK(min_corner.x == doctest::Approx(0.f).epsilon(0.01));
1677 DOCTEST_CHECK(max_corner.x == doctest::Approx(2.f).epsilon(0.01));
1678 }
1679}
1680
1681TEST_CASE("Additional Object Features") {
1682 SUBCASE("getAllObjectIDs") {
1683 Context ctx;
1684 uint obj1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1685 uint obj2 = ctx.addSphereObject(10, make_vec3(0, 0, 0), 1.f);
1686
1687 std::vector<uint> all_ids = ctx.getAllObjectIDs();
1688 DOCTEST_CHECK(all_ids.size() >= 2);
1689 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj1) != all_ids.end());
1690 DOCTEST_CHECK(std::find(all_ids.begin(), all_ids.end(), obj2) != all_ids.end());
1691 }
1692
1693 SUBCASE("object type checks") {
1694 Context ctx;
1695 uint box = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1696 uint sphere = ctx.addSphereObject(10, make_vec3(0, 0, 0), 1.f);
1697 uint disk = ctx.addDiskObject(10, make_vec3(0, 0, 0), make_vec2(1, 1));
1698
1699 DOCTEST_CHECK(ctx.getObjectType(box) == OBJECT_TYPE_BOX);
1700 DOCTEST_CHECK(ctx.getObjectType(sphere) == OBJECT_TYPE_SPHERE);
1701 DOCTEST_CHECK(ctx.getObjectType(disk) == OBJECT_TYPE_DISK);
1702 }
1703}
1704
1705TEST_CASE("Comprehensive Object Property Tests") {
1706 SUBCASE("rotation operations on objects") {
1707 Context ctx;
1708 std::vector<uint> objs;
1709 objs.push_back(ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1710 objs.push_back(ctx.addBoxObject(make_vec3(1, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1711
1712 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f * PI_F, "z"));
1713 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f * PI_F, make_vec3(0, 0, 1)));
1714 DOCTEST_CHECK_NOTHROW(ctx.rotateObject(objs, 0.5f * PI_F, make_vec3(0, 0, 0), make_vec3(0, 0, 1)));
1715 DOCTEST_CHECK_NOTHROW(ctx.rotateObjectAboutOrigin(objs, 0.5f * PI_F, make_vec3(0, 0, 1)));
1716 }
1717
1718 SUBCASE("scaling operations on objects") {
1719 Context ctx;
1720 std::vector<uint> objs;
1721 objs.push_back(ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1722
1723 DOCTEST_CHECK_NOTHROW(ctx.scaleObject(objs, make_vec3(2, 2, 2)));
1724 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutCenter(objs, make_vec3(0.5f, 0.5f, 0.5f)));
1725 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutPoint(objs, make_vec3(2, 2, 2), make_vec3(0, 0, 0)));
1726 DOCTEST_CHECK_NOTHROW(ctx.scaleObjectAboutOrigin(objs, make_vec3(0.5f, 0.5f, 0.5f)));
1727 }
1728
1729 SUBCASE("translation operations on objects") {
1730 Context ctx;
1731 std::vector<uint> objs;
1732 objs.push_back(ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1733
1734 DOCTEST_CHECK_NOTHROW(ctx.translateObject(objs, make_vec3(1, 2, 3)));
1735 }
1736}
1737
1738TEST_CASE("Domain and Bounding Operations") {
1739 SUBCASE("domain bounding sphere") {
1740 Context ctx;
1741 ctx.addPatch(make_vec3(-2, 0, 0), make_vec2(1, 1));
1742 ctx.addPatch(make_vec3(2, 0, 0), make_vec2(1, 1));
1743
1744 vec3 center;
1745 float radius;
1746 ctx.getDomainBoundingSphere(center, radius);
1747 DOCTEST_CHECK(center.x == doctest::Approx(0.f).epsilon(0.1));
1748 DOCTEST_CHECK(radius > 2.f);
1749 }
1750}
1751
1752TEST_CASE("Missing Data and State Functions") {
1753 SUBCASE("listTimeseriesVariables") {
1754 Context ctx;
1755 Date date = make_Date(1, 1, 2025);
1756 Time time = make_Time(0, 0, 12);
1757
1758 ctx.addTimeseriesData("temp", 25.5f, date, time);
1759 ctx.addTimeseriesData("humidity", 60.0f, date, time);
1760
1761 std::vector<std::string> vars = ctx.listTimeseriesVariables();
1762 DOCTEST_CHECK(vars.size() >= 2);
1763 DOCTEST_CHECK(std::find(vars.begin(), vars.end(), "temp") != vars.end());
1764 DOCTEST_CHECK(std::find(vars.begin(), vars.end(), "humidity") != vars.end());
1765 }
1766
1767 SUBCASE("getUniquePrimitiveParentObjectIDs") {
1768 Context ctx;
1769 uint obj1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1770 uint obj2 = ctx.addSphereObject(10, make_vec3(0, 0, 0), 1.f);
1771
1772 std::vector<uint> all_prims = ctx.getAllUUIDs();
1773 std::vector<uint> obj_ids = ctx.getUniquePrimitiveParentObjectIDs(all_prims);
1774 DOCTEST_CHECK(obj_ids.size() >= 2);
1775 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj1) != obj_ids.end());
1776 DOCTEST_CHECK(std::find(obj_ids.begin(), obj_ids.end(), obj2) != obj_ids.end());
1777 }
1778}
1779
1780TEST_CASE("Comprehensive Coverage Tests") {
1781 SUBCASE("additional object operations with vectors") {
1782 Context ctx;
1783 std::vector<uint> obj_ids;
1784 obj_ids.push_back(ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1785 obj_ids.push_back(ctx.addBoxObject(make_vec3(2, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1)));
1786
1787 std::vector<uint> all_uuids = ctx.getObjectPrimitiveUUIDs(obj_ids);
1788 DOCTEST_CHECK(all_uuids.size() == 12); // 6 faces per box * 2 boxes
1789
1790 std::vector<std::vector<uint>> nested_obj_ids = {{obj_ids[0]}, {obj_ids[1]}};
1791 std::vector<uint> nested_uuids = ctx.getObjectPrimitiveUUIDs(nested_obj_ids);
1792 DOCTEST_CHECK(nested_uuids.size() == 12);
1793
1794 ctx.hideObject(obj_ids);
1795 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[0]));
1796 DOCTEST_CHECK(ctx.isObjectHidden(obj_ids[1]));
1797
1798 ctx.showObject(obj_ids);
1799 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[0]));
1800 DOCTEST_CHECK(!ctx.isObjectHidden(obj_ids[1]));
1801 }
1802
1803 SUBCASE("object texture color overrides") {
1804 Context ctx;
1805 std::vector<uint> obj_ids;
1806 obj_ids.push_back(ctx.addTileObject(make_vec3(0, 0, 0), make_vec2(1, 1), nullrotation, make_int2(2, 2), "lib/images/solid.jpg"));
1807
1808 ctx.overrideObjectTextureColor(obj_ids);
1809 ctx.useObjectTextureColor(obj_ids);
1810 }
1811}
1812
1813TEST_CASE("getAllUUIDs Cache Performance") {
1814 SUBCASE("Cache invalidation on primitive add/delete") {
1815 Context ctx;
1816
1817 // Initial empty state
1818 std::vector<uint> empty_uuids = ctx.getAllUUIDs();
1819 DOCTEST_CHECK(empty_uuids.empty());
1820
1821 // Add primitives and test cache invalidation
1822 uint p1 = ctx.addPatch();
1823 std::vector<uint> one_uuid = ctx.getAllUUIDs();
1824 DOCTEST_CHECK(one_uuid.size() == 1);
1825 DOCTEST_CHECK(one_uuid[0] == p1);
1826
1827 // Test cache consistency - repeated calls should return same result
1828 std::vector<uint> same_uuid = ctx.getAllUUIDs();
1829 DOCTEST_CHECK(same_uuid.size() == 1);
1830 DOCTEST_CHECK(same_uuid[0] == p1);
1831
1832 // Add more primitives
1833 uint t1 = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0));
1834 uint v1 = ctx.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1835
1836 std::vector<uint> three_uuids = ctx.getAllUUIDs();
1837 DOCTEST_CHECK(three_uuids.size() == 3);
1838 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), p1) != three_uuids.end());
1839 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), t1) != three_uuids.end());
1840 DOCTEST_CHECK(std::find(three_uuids.begin(), three_uuids.end(), v1) != three_uuids.end());
1841
1842 // Test delete invalidation
1843 ctx.deletePrimitive(t1);
1844 std::vector<uint> two_uuids = ctx.getAllUUIDs();
1845 DOCTEST_CHECK(two_uuids.size() == 2);
1846 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), t1) == two_uuids.end());
1847 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), p1) != two_uuids.end());
1848 DOCTEST_CHECK(std::find(two_uuids.begin(), two_uuids.end(), v1) != two_uuids.end());
1849 }
1850
1851 SUBCASE("Cache invalidation on hide/show primitives") {
1852 Context ctx;
1853 uint p1 = ctx.addPatch();
1854 uint p2 = ctx.addPatch();
1855 uint p3 = ctx.addPatch();
1856
1857 // All visible initially
1858 std::vector<uint> all_visible = ctx.getAllUUIDs();
1859 DOCTEST_CHECK(all_visible.size() == 3);
1860
1861 // Hide one primitive
1862 ctx.hidePrimitive(p2);
1863 std::vector<uint> two_visible = ctx.getAllUUIDs();
1864 DOCTEST_CHECK(two_visible.size() == 2);
1865 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p2) == two_visible.end());
1866 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p1) != two_visible.end());
1867 DOCTEST_CHECK(std::find(two_visible.begin(), two_visible.end(), p3) != two_visible.end());
1868
1869 // Hide multiple primitives
1870 std::vector<uint> to_hide = {p1, p3};
1871 ctx.hidePrimitive(to_hide);
1872 std::vector<uint> none_visible = ctx.getAllUUIDs();
1873 DOCTEST_CHECK(none_visible.empty());
1874
1875 // Show one primitive back
1876 ctx.showPrimitive(p1);
1877 std::vector<uint> one_visible = ctx.getAllUUIDs();
1878 DOCTEST_CHECK(one_visible.size() == 1);
1879 DOCTEST_CHECK(one_visible[0] == p1);
1880
1881 // Show all primitives back
1882 std::vector<uint> to_show = {p2, p3};
1883 ctx.showPrimitive(to_show);
1884 std::vector<uint> all_back = ctx.getAllUUIDs();
1885 DOCTEST_CHECK(all_back.size() == 3);
1886 }
1887
1888 SUBCASE("Cache invalidation on copy primitives") {
1889 Context ctx;
1890 uint original = ctx.addPatch();
1891
1892 std::vector<uint> before_copy = ctx.getAllUUIDs();
1893 DOCTEST_CHECK(before_copy.size() == 1);
1894
1895 uint copied = ctx.copyPrimitive(original);
1896 std::vector<uint> after_copy = ctx.getAllUUIDs();
1897 DOCTEST_CHECK(after_copy.size() == 2);
1898 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), original) != after_copy.end());
1899 DOCTEST_CHECK(std::find(after_copy.begin(), after_copy.end(), copied) != after_copy.end());
1900
1901 // Test multiple copy
1902 std::vector<uint> originals = {original, copied};
1903 std::vector<uint> copies = ctx.copyPrimitive(originals);
1904 std::vector<uint> after_multi_copy = ctx.getAllUUIDs();
1905 DOCTEST_CHECK(after_multi_copy.size() == 4);
1906 for (uint copy_id: copies) {
1907 DOCTEST_CHECK(std::find(after_multi_copy.begin(), after_multi_copy.end(), copy_id) != after_multi_copy.end());
1908 }
1909 }
1910
1911 SUBCASE("Cache consistency across mixed operations") {
1912 Context ctx;
1913
1914 // Complex sequence of operations
1915 uint p1 = ctx.addPatch();
1916 uint p2 = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0));
1917
1918 std::vector<uint> step1 = ctx.getAllUUIDs();
1919 DOCTEST_CHECK(step1.size() == 2);
1920
1921 ctx.hidePrimitive(p1);
1922 std::vector<uint> step2 = ctx.getAllUUIDs();
1923 DOCTEST_CHECK(step2.size() == 1);
1924 DOCTEST_CHECK(step2[0] == p2);
1925
1926 uint p3 = ctx.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1927 std::vector<uint> step3 = ctx.getAllUUIDs();
1928 DOCTEST_CHECK(step3.size() == 2);
1929
1930 ctx.showPrimitive(p1);
1931 std::vector<uint> step4 = ctx.getAllUUIDs();
1932 DOCTEST_CHECK(step4.size() == 3);
1933
1934 ctx.deletePrimitive(p2);
1935 std::vector<uint> step5 = ctx.getAllUUIDs();
1936 DOCTEST_CHECK(step5.size() == 2);
1937 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p2) == step5.end());
1938 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p1) != step5.end());
1939 DOCTEST_CHECK(std::find(step5.begin(), step5.end(), p3) != step5.end());
1940 }
1941}
1942
1943TEST_CASE("Error Handling") {
1944 SUBCASE("Context error handling") {
1945 Context context_test;
1946 uint tri = context_test.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0), RGB::green);
1947 capture_cerr cerr_buffer;
1948 vec3 center;
1949#ifdef HELIOS_DEBUG
1950 DOCTEST_CHECK_THROWS_AS(center = context_test.getPatchCenter(tri), std::runtime_error);
1951#endif
1952
1953 uint vox = context_test.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1954 std::vector<uint> vlist{vox};
1955 DOCTEST_CHECK_THROWS_AS(context_test.rotatePrimitive(vlist, PI_F / 4.f, "a"), std::runtime_error);
1956 }
1957}
1958
1959TEST_CASE("Zero Area Triangle Detection") {
1960 SUBCASE("addTubeObject with nearly identical vertices should not create zero-area triangles") {
1961 Context ctx;
1962
1963 // Test case based on problematic vertices from plant architecture
1964 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)};
1965
1966 std::vector<float> radii = {0.000500000024f, 0.000500000024f, 0.000500000024f};
1967 std::vector<RGBcolor> colors = {RGB::green, RGB::green, RGB::green};
1968
1969 // Use exact same parameters as failing case: Ndiv_internode_radius = 7
1970 uint tube_obj = ctx.addTubeObject(7, nodes, radii, colors);
1971
1972 // Verify the tube object was created
1973 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
1974
1975 // Get all primitives in the tube and check their areas
1976 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
1977 DOCTEST_CHECK(tube_primitives.size() > 0);
1978
1979 for (uint uuid: tube_primitives) {
1980 float area = ctx.getPrimitiveArea(uuid);
1981 DOCTEST_CHECK(area > 0.0f); // No zero-area triangles
1982 DOCTEST_CHECK(area > 1e-12f); // Area should be reasonably above precision limit
1983 }
1984 }
1985
1986 SUBCASE("addTubeObject with extremely small displacements") {
1987 Context ctx;
1988
1989 // Even more extreme case - displacements on the order of 1e-5
1990 std::vector<vec3> nodes = {make_vec3(0.0f, 0.0f, 0.0f), make_vec3(1e-5f, 1e-5f, 1e-3f), make_vec3(2e-5f, 2e-5f, 2e-3f)};
1991
1992 std::vector<float> radii = {1e-4f, 1e-4f, 1e-4f};
1993
1994 uint tube_obj = ctx.addTubeObject(6, nodes, radii);
1995 DOCTEST_CHECK(ctx.doesObjectExist(tube_obj));
1996
1997 std::vector<uint> tube_primitives = ctx.getObjectPrimitiveUUIDs(tube_obj);
1998 for (uint uuid: tube_primitives) {
1999 float area = ctx.getPrimitiveArea(uuid);
2000 DOCTEST_CHECK(area > 0.0f);
2001 }
2002 }
2003}
2004
2005TEST_CASE("Transparent Texture Zero Area Validation") {
2006 SUBCASE("addSphere with transparent texture should filter zero-area triangles") {
2007 Context ctx;
2008
2009 // Test with diamond texture (has transparency)
2010 std::vector<uint> sphere_uuids = ctx.addSphere(20, make_vec3(0, 0, 0), 1.0f, "lib/images/diamond_texture.png");
2011
2012 // All returned primitives should have positive area
2013 DOCTEST_CHECK(sphere_uuids.size() > 0);
2014 for (uint uuid: sphere_uuids) {
2015 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2016 float area = ctx.getPrimitiveArea(uuid);
2017 DOCTEST_CHECK(area > 0.0f);
2018 }
2019
2020 // Test with disk texture (more transparency)
2021 std::vector<uint> sphere_disk_uuids = ctx.addSphere(30, make_vec3(2, 0, 0), 1.0f, "lib/images/disk_texture.png");
2022
2023 DOCTEST_CHECK(sphere_disk_uuids.size() > 0);
2024 for (uint uuid: sphere_disk_uuids) {
2025 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2026 float area = ctx.getPrimitiveArea(uuid);
2027 DOCTEST_CHECK(area > 0.0f);
2028 }
2029
2030 // Verify ALL primitives in each sphere have positive area
2031 int zero_area_count_diamond = 0;
2032 for (uint uuid: sphere_uuids) {
2033 float area = ctx.getPrimitiveArea(uuid);
2034 if (area <= 0.0f) {
2035 zero_area_count_diamond++;
2036 }
2037 }
2038 DOCTEST_CHECK(zero_area_count_diamond == 0);
2039
2040 int zero_area_count_disk = 0;
2041 for (uint uuid: sphere_disk_uuids) {
2042 float area = ctx.getPrimitiveArea(uuid);
2043 if (area <= 0.0f) {
2044 zero_area_count_disk++;
2045 }
2046 }
2047 DOCTEST_CHECK(zero_area_count_disk == 0);
2048
2049 // Compare with solid sphere for reference
2050 std::vector<uint> solid_sphere_uuids = ctx.addSphere(20, make_vec3(4, 0, 0), 1.0f, RGB::green);
2051
2052 int zero_area_count_solid = 0;
2053 for (uint uuid: solid_sphere_uuids) {
2054 float area = ctx.getPrimitiveArea(uuid);
2055 if (area <= 0.0f) {
2056 zero_area_count_solid++;
2057 }
2058 }
2059 DOCTEST_CHECK(zero_area_count_solid == 0);
2060 }
2061
2062 SUBCASE("texture transparency validation preserves object integrity") {
2063 Context ctx;
2064
2065 // Create textured sphere and verify all returned UUIDs are valid
2066 std::vector<uint> sphere_uuids = ctx.addSphere(15, make_vec3(0, 0, 0), 1.0f, "lib/images/diamond_texture.png");
2067
2068 // Check that all returned primitives exist and have positive area
2069 for (uint uuid: sphere_uuids) {
2070 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
2071 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
2072
2073 float area = ctx.getPrimitiveArea(uuid);
2074 DOCTEST_CHECK(area > 0.0f);
2075 DOCTEST_CHECK(area > 1e-10f); // Should be significantly above precision threshold
2076
2077 // Verify solid fraction is reasonable (not exactly 0 or 1)
2078 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2079 DOCTEST_CHECK(solid_fraction > 0.0f);
2080 DOCTEST_CHECK(solid_fraction <= 1.0f);
2081 }
2082
2083 // Comprehensive check: verify no zero-area primitives exist anywhere in context
2084 std::vector<uint> all_uuids = ctx.getAllUUIDs();
2085 int total_zero_area = 0;
2086 int total_negative_area = 0;
2087
2088 for (uint uuid: all_uuids) {
2089 float area = ctx.getPrimitiveArea(uuid);
2090 if (area == 0.0f) {
2091 total_zero_area++;
2092 }
2093 if (area < 0.0f) {
2094 total_negative_area++;
2095 }
2096 }
2097
2098 // No zero or negative area primitives should exist
2099 DOCTEST_CHECK(total_zero_area == 0);
2100 DOCTEST_CHECK(total_negative_area == 0);
2101
2102 // Additional validation: check that all primitives have reasonable solid fractions
2103 for (uint uuid: sphere_uuids) {
2104 float solid_fraction = ctx.getPrimitiveSolidFraction(uuid);
2105 DOCTEST_CHECK(solid_fraction >= 0.0f);
2106 DOCTEST_CHECK(solid_fraction <= 1.0f);
2107
2108 // For textured primitives, effective area should be geometric_area * solid_fraction
2109 if (ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE) {
2110 vec3 v0 = ctx.getTriangleVertex(uuid, 0);
2111 vec3 v1 = ctx.getTriangleVertex(uuid, 1);
2112 vec3 v2 = ctx.getTriangleVertex(uuid, 2);
2113 float geometric_area = calculateTriangleArea(v0, v1, v2);
2114 float effective_area = ctx.getPrimitiveArea(uuid);
2115
2116 // Effective area should be <= geometric area (due to solid fraction)
2117 DOCTEST_CHECK(effective_area <= geometric_area + 1e-6f); // Allow small numerical tolerance
2118 DOCTEST_CHECK(effective_area > 0.0f);
2119 }
2120 }
2121
2122 // Test zero-area validation for other primitive methods (addTube, addDisk, addCone)
2123 DOCTEST_SUBCASE("Test Other Primitive Methods Zero Area Validation") {
2124 Context ctx_other;
2125
2126 // Test addTube with transparent texture
2127 std::vector<vec3> tube_nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1), make_vec3(0, 0, 2)};
2128 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2129 std::vector<uint> tube_uuids = ctx_other.addTube(8, tube_nodes, tube_radii, "lib/images/diamond_texture.png");
2130
2131 // All returned UUIDs should have positive area
2132 int tube_positive_area = 0, tube_zero_area = 0;
2133 for (uint uuid: tube_uuids) {
2134 float area = ctx_other.getPrimitiveArea(uuid);
2135 DOCTEST_CHECK(area >= 0.0f);
2136 if (area > 0.0f) {
2137 tube_positive_area++;
2138 } else {
2139 tube_zero_area++;
2140 }
2141 }
2142
2143 DOCTEST_CHECK(tube_positive_area > 0); // Should have some positive area triangles
2144 DOCTEST_CHECK(tube_zero_area == 0); // Should have no zero area triangles
2145
2146 // Test addDisk with transparent texture
2147 std::vector<uint> disk_uuids = ctx_other.addDisk(make_int2(4, 3), make_vec3(0, 0, 0), make_vec2(1.0f, 1.0f), make_SphericalCoord(0, 0), "lib/images/disk_texture.png");
2148
2149 // All returned UUIDs should have positive area
2150 int disk_positive_area = 0, disk_zero_area = 0;
2151 for (uint uuid: disk_uuids) {
2152 float area = ctx_other.getPrimitiveArea(uuid);
2153 DOCTEST_CHECK(area >= 0.0f);
2154 if (area > 0.0f) {
2155 disk_positive_area++;
2156 } else {
2157 disk_zero_area++;
2158 }
2159 }
2160
2161 DOCTEST_CHECK(disk_positive_area > 0); // Should have some positive area triangles
2162 DOCTEST_CHECK(disk_zero_area == 0); // Should have no zero area triangles
2163
2164 // Test addCone with transparent texture
2165 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");
2166
2167 // All returned UUIDs should have positive area
2168 int cone_positive_area = 0, cone_zero_area = 0;
2169 for (uint uuid: cone_uuids) {
2170 float area = ctx_other.getPrimitiveArea(uuid);
2171 DOCTEST_CHECK(area >= 0.0f);
2172 if (area > 0.0f) {
2173 cone_positive_area++;
2174 } else {
2175 cone_zero_area++;
2176 }
2177 }
2178
2179 DOCTEST_CHECK(cone_positive_area > 0); // Should have some positive area triangles
2180 DOCTEST_CHECK(cone_zero_area == 0); // Should have no zero area triangles
2181
2182 // Test addTile with transparent texture (should already work, but verify)
2183 std::vector<uint> tile_uuids = ctx_other.addTile(make_vec3(0, 0, 0), make_vec2(1.0f, 1.0f), make_SphericalCoord(0, 0), make_int2(4, 4), "lib/images/diamond_texture.png");
2184
2185 // All returned UUIDs should have positive area
2186 int tile_positive_area = 0, tile_zero_area = 0;
2187 for (uint uuid: tile_uuids) {
2188 float area = ctx_other.getPrimitiveArea(uuid);
2189 DOCTEST_CHECK(area >= 0.0f);
2190 if (area > 0.0f) {
2191 tile_positive_area++;
2192 } else {
2193 tile_zero_area++;
2194 }
2195 }
2196
2197 DOCTEST_CHECK(tile_positive_area > 0); // Should have some positive area triangles
2198 DOCTEST_CHECK(tile_zero_area == 0); // Should have no zero area triangles
2199 }
2200
2201 // Test zero-area validation for compound object methods
2202 DOCTEST_SUBCASE("Test Compound Object Methods Zero Area Validation") {
2203 Context ctx_compound;
2204
2205 // Test addSphereObject with transparent texture
2206 uint sphere_obj = ctx_compound.addSphereObject(8, make_vec3(0, 0, 0), 0.5f, "lib/images/diamond_texture.png");
2207 std::vector<uint> sphere_primitives = ctx_compound.getObjectPrimitiveUUIDs(sphere_obj);
2208
2209 // All primitives should have positive area
2210 int sphere_positive_area = 0, sphere_zero_area = 0;
2211 for (uint uuid: sphere_primitives) {
2212 float area = ctx_compound.getPrimitiveArea(uuid);
2213 DOCTEST_CHECK(area >= 0.0f);
2214 if (area > 0.0f) {
2215 sphere_positive_area++;
2216 } else {
2217 sphere_zero_area++;
2218 }
2219 }
2220
2221 DOCTEST_CHECK(sphere_positive_area > 0); // Should have some positive area triangles
2222 DOCTEST_CHECK(sphere_zero_area == 0); // Should have no zero area triangles
2223
2224 // Test addTubeObject with transparent texture
2225 std::vector<vec3> tube_nodes = {make_vec3(0, 0, 0), make_vec3(0, 0, 1), make_vec3(0, 0, 2)};
2226 std::vector<float> tube_radii = {0.1f, 0.15f, 0.1f};
2227 uint tube_obj = ctx_compound.addTubeObject(8, tube_nodes, tube_radii, "lib/images/diamond_texture.png");
2228 std::vector<uint> tube_primitives = ctx_compound.getObjectPrimitiveUUIDs(tube_obj);
2229
2230 // All primitives should have positive area
2231 int tube_positive_area = 0, tube_zero_area = 0;
2232 for (uint uuid: tube_primitives) {
2233 float area = ctx_compound.getPrimitiveArea(uuid);
2234 DOCTEST_CHECK(area >= 0.0f);
2235 if (area > 0.0f) {
2236 tube_positive_area++;
2237 } else {
2238 tube_zero_area++;
2239 }
2240 }
2241
2242 DOCTEST_CHECK(tube_positive_area > 0); // Should have some positive area triangles
2243 DOCTEST_CHECK(tube_zero_area == 0); // Should have no zero area triangles
2244
2245 // Test addDiskObject with transparent texture
2246 uint disk_obj = ctx_compound.addDiskObject(make_int2(4, 3), make_vec3(0, 0, 0), make_vec2(1.0f, 1.0f), make_SphericalCoord(0, 0), "lib/images/disk_texture.png");
2247 std::vector<uint> disk_primitives = ctx_compound.getObjectPrimitiveUUIDs(disk_obj);
2248
2249 // All primitives should have positive area
2250 int disk_positive_area = 0, disk_zero_area = 0;
2251 for (uint uuid: disk_primitives) {
2252 float area = ctx_compound.getPrimitiveArea(uuid);
2253 DOCTEST_CHECK(area >= 0.0f);
2254 if (area > 0.0f) {
2255 disk_positive_area++;
2256 } else {
2257 disk_zero_area++;
2258 }
2259 }
2260
2261 DOCTEST_CHECK(disk_positive_area > 0); // Should have some positive area triangles
2262 DOCTEST_CHECK(disk_zero_area == 0); // Should have no zero area triangles
2263
2264 // Test addConeObject with transparent texture
2265 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");
2266 std::vector<uint> cone_primitives = ctx_compound.getObjectPrimitiveUUIDs(cone_obj);
2267
2268 // All primitives should have positive area
2269 int cone_positive_area = 0, cone_zero_area = 0;
2270 for (uint uuid: cone_primitives) {
2271 float area = ctx_compound.getPrimitiveArea(uuid);
2272 DOCTEST_CHECK(area >= 0.0f);
2273 if (area > 0.0f) {
2274 cone_positive_area++;
2275 } else {
2276 cone_zero_area++;
2277 }
2278 }
2279
2280 DOCTEST_CHECK(cone_positive_area > 0); // Should have some positive area triangles
2281 DOCTEST_CHECK(cone_zero_area == 0); // Should have no zero area triangles
2282 }
2283 }
2284}
2285
2286TEST_CASE("File path resolution priority") {
2287 SUBCASE("resolveFilePath current directory priority") {
2288 // Test that the new file resolution logic checks current directory first,
2289 // then falls back to HELIOS_BUILD directory
2290
2291 // Create a test texture file in the current directory
2292 std::string testFileName = "test_file_resolution.jpg";
2293 std::filesystem::path currentDirFile = std::filesystem::current_path() / testFileName;
2294
2295 // Copy the existing texture for our test
2296 std::filesystem::path sourceTexture = "core/lib/models/texture.jpg";
2297
2298 if (std::filesystem::exists(sourceTexture)) {
2299 // Copy to current directory
2300 std::filesystem::copy_file(sourceTexture, currentDirFile, std::filesystem::copy_options::overwrite_existing);
2301 DOCTEST_CHECK(std::filesystem::exists(currentDirFile));
2302
2303 // Test resolveFilePath function directly
2304 std::filesystem::path resolved = helios::resolveFilePath(testFileName);
2305 DOCTEST_CHECK(resolved == std::filesystem::canonical(currentDirFile));
2306
2307 // Clean up
2308 std::filesystem::remove(currentDirFile);
2309 }
2310 }
2311
2312 SUBCASE("addPatch with texture from current directory") {
2313 Context ctx;
2314
2315 // Create test directory structure in current working directory
2316 std::filesystem::create_directories("test_models");
2317 std::string testTexture = "test_models/test_texture.jpg";
2318 std::filesystem::path testTexturePath = std::filesystem::current_path() / testTexture;
2319
2320 // Copy source texture
2321 std::filesystem::path sourceTexture = "core/lib/models/texture.jpg";
2322
2323 if (std::filesystem::exists(sourceTexture)) {
2324 std::filesystem::copy_file(sourceTexture, testTexturePath, std::filesystem::copy_options::overwrite_existing);
2325
2326 // This should work with the fix - loads from current directory first
2327 // addPatch uses resolveFilePath internally for texture loading
2328 SphericalCoord rotation = make_SphericalCoord(0, 0);
2329 uint patch_id;
2330 DOCTEST_CHECK_NOTHROW({ patch_id = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1), rotation, testTexture.c_str()); });
2331 DOCTEST_CHECK(patch_id > 0);
2332
2333 // Verify the texture loaded correctly
2334 bool has_transparency = ctx.primitiveTextureHasTransparencyChannel(patch_id);
2335 DOCTEST_CHECK((has_transparency || !has_transparency)); // Just verify it's a boolean (texture loaded)
2336
2337 // Clean up
2338 std::filesystem::remove(testTexturePath);
2339 std::filesystem::remove("test_models");
2340 }
2341 }
2342}