1.3.49
 
Loading...
Searching...
No Matches
Test_data.h
1#pragma once
2// =================================================================================
3// Suite 5: Context Data
4//
5// Tests for managing primitive, object, and global data within the Context class.
6// =================================================================================
7TEST_CASE("Global Data") {
8 Context ctx;
9
10 SUBCASE("Set, Get, Exists, List, Clear") {
11 ctx.setGlobalData("test_int", 123);
12 ctx.setGlobalData("test_float", 456.789f);
13 ctx.setGlobalData("test_string", "hello");
14 std::vector<double> double_vec = {1.1, 2.2, 3.3};
15 ctx.setGlobalData("test_double_vec", double_vec);
16
17 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_int"));
18 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_float"));
19 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_string"));
20 DOCTEST_CHECK(ctx.doesGlobalDataExist("test_double_vec"));
21 DOCTEST_CHECK(!ctx.doesGlobalDataExist("non_existent"));
22
23 int i;
24 ctx.getGlobalData("test_int", i);
25 DOCTEST_CHECK(i == 123);
26
27 float f;
28 ctx.getGlobalData("test_float", f);
29 DOCTEST_CHECK(f == doctest::Approx(456.789f));
30
31 std::string s;
32 ctx.getGlobalData("test_string", s);
33 DOCTEST_CHECK(s == "hello");
34
35 std::vector<double> dv;
36 ctx.getGlobalData("test_double_vec", dv);
37 DOCTEST_CHECK(dv.size() == 3);
38 DOCTEST_CHECK(dv[1] == doctest::Approx(2.2));
39
40 std::vector<std::string> labels = ctx.listGlobalData();
41 DOCTEST_CHECK(labels.size() == 4);
42
43 ctx.clearGlobalData("test_int");
44 DOCTEST_CHECK(!ctx.doesGlobalDataExist("test_int"));
45 }
46
47 SUBCASE("Management") {
48 ctx.setGlobalData("g_int", 5);
49 DOCTEST_CHECK(ctx.getGlobalDataType("g_int") == HELIOS_TYPE_INT);
50 DOCTEST_CHECK(ctx.getGlobalDataSize("g_int") == 1);
51
52 ctx.duplicateGlobalData("g_int", "g_int_copy");
53 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int_copy"));
54
55 ctx.renameGlobalData("g_int", "g_int_new");
56 DOCTEST_CHECK(!ctx.doesGlobalDataExist("g_int"));
57 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int_new"));
58 }
59
60 SUBCASE("Increment") {
61 ctx.setGlobalData("inc_me_int", 10);
62 ctx.incrementGlobalData("inc_me_int", 5);
63 int val_i;
64 ctx.getGlobalData("inc_me_int", val_i);
65 DOCTEST_CHECK(val_i == 15);
66
67 ctx.setGlobalData("inc_me_uint", std::vector<uint>{1});
68 ctx.incrementGlobalData("inc_me_uint", (uint) 1);
69 std::vector<uint> val_u;
70 ctx.getGlobalData("inc_me_uint", val_u);
71 DOCTEST_CHECK(val_u[0] == 2);
72
73 ctx.setGlobalData("inc_me_float", std::vector<float>{1.f});
74 ctx.incrementGlobalData("inc_me_float", 1.f);
75 std::vector<float> val_f;
76 ctx.getGlobalData("inc_me_float", val_f);
77 DOCTEST_CHECK(val_f[0] == doctest::Approx(2.f));
78
79 ctx.setGlobalData("inc_me_double", std::vector<double>{1.0});
80 ctx.incrementGlobalData("inc_me_double", 1.0);
81 std::vector<double> val_d;
82 ctx.getGlobalData("inc_me_double", val_d);
83 DOCTEST_CHECK(val_d[0] == doctest::Approx(2.0));
84 }
85
86 SUBCASE("All Vector Types") {
87 ctx.setGlobalData("g_uint_vec", std::vector<uint>{1});
88 ctx.setGlobalData("g_vec2_vec", std::vector<vec2>{make_vec2(1, 1)});
89 ctx.setGlobalData("g_vec3_vec", std::vector<vec3>{make_vec3(1, 1, 1)});
90 ctx.setGlobalData("g_vec4_vec", std::vector<vec4>{make_vec4(1, 1, 1, 1)});
91 ctx.setGlobalData("g_int2_vec", std::vector<int2>{make_int2(1, 1)});
92 ctx.setGlobalData("g_int3_vec", std::vector<int3>{make_int3(1, 1, 1)});
93 ctx.setGlobalData("g_int4_vec", std::vector<int4>{make_int4(1, 1, 1, 1)});
94 ctx.setGlobalData("g_string_vec", std::vector<std::string>{"hello"});
95
96 ctx.duplicateGlobalData("g_uint_vec", "g_uint_vec_copy");
97 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_uint_vec_copy"));
98 ctx.duplicateGlobalData("g_vec2_vec", "g_vec2_vec_copy");
99 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec2_vec_copy"));
100 ctx.duplicateGlobalData("g_vec3_vec", "g_vec3_vec_copy");
101 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec3_vec_copy"));
102 ctx.duplicateGlobalData("g_vec4_vec", "g_vec4_vec_copy");
103 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec4_vec_copy"));
104 ctx.duplicateGlobalData("g_int2_vec", "g_int2_vec_copy");
105 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int2_vec_copy"));
106 ctx.duplicateGlobalData("g_int3_vec", "g_int3_vec_copy");
107 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int3_vec_copy"));
108 ctx.duplicateGlobalData("g_int4_vec", "g_int4_vec_copy");
109 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int4_vec_copy"));
110 ctx.duplicateGlobalData("g_string_vec", "g_string_vec_copy");
111 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_string_vec_copy"));
112 }
113
114 SUBCASE("Global data error handling and all types") {
115 Context ctx;
116 capture_cerr cerr_buffer;
117 DOCTEST_CHECK_THROWS(ctx.getGlobalDataType("non_existent"));
118 DOCTEST_CHECK_THROWS(ctx.getGlobalDataSize("non_existent"));
119 DOCTEST_CHECK_THROWS(ctx.renameGlobalData("non_existent", "new"));
120 DOCTEST_CHECK_THROWS(ctx.duplicateGlobalData("non_existent", "new"));
121 DOCTEST_CHECK_THROWS(ctx.incrementGlobalData("non_existent", 1));
122
123 ctx.setGlobalData("g_uint", std::vector<uint>{1});
124 ctx.setGlobalData("g_float", std::vector<float>{1.f});
125 ctx.setGlobalData("g_double", std::vector<double>{1.0});
126 ctx.setGlobalData("g_vec2", std::vector<vec2>{make_vec2(1, 1)});
127 ctx.setGlobalData("g_vec3", std::vector<vec3>{make_vec3(1, 1, 1)});
128 ctx.setGlobalData("g_vec4", std::vector<vec4>{make_vec4(1, 1, 1, 1)});
129 ctx.setGlobalData("g_int2", std::vector<int2>{make_int2(1, 1)});
130 ctx.setGlobalData("g_int3", std::vector<int3>{make_int3(1, 1, 1)});
131 ctx.setGlobalData("g_int4", std::vector<int4>{make_int4(1, 1, 1, 1)});
132 ctx.setGlobalData("g_string", std::vector<std::string>{"hello"});
133
134 ctx.duplicateGlobalData("g_uint", "g_uint_copy");
135 ctx.duplicateGlobalData("g_float", "g_float_copy");
136 ctx.duplicateGlobalData("g_double", "g_double_copy");
137 ctx.duplicateGlobalData("g_vec2", "g_vec2_copy");
138 ctx.duplicateGlobalData("g_vec3", "g_vec3_copy");
139 ctx.duplicateGlobalData("g_vec4", "g_vec4_copy");
140 ctx.duplicateGlobalData("g_int2", "g_int2_copy");
141 ctx.duplicateGlobalData("g_int3", "g_int3_copy");
142 ctx.duplicateGlobalData("g_int4", "g_int4_copy");
143 ctx.duplicateGlobalData("g_string", "g_string_copy");
144
145 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_uint_copy"));
146 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_float_copy"));
147 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_double_copy"));
148 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec2_copy"));
149 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec3_copy"));
150 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_vec4_copy"));
151 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int2_copy"));
152 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int3_copy"));
153 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_int4_copy"));
154 DOCTEST_CHECK(ctx.doesGlobalDataExist("g_string_copy"));
155
156 ctx.incrementGlobalData("g_uint", (uint) 1);
157 ctx.incrementGlobalData("g_float", 1.f);
158 ctx.incrementGlobalData("g_double", 1.0);
159
160 std::vector<uint> g_uint;
161 ctx.getGlobalData("g_uint", g_uint);
162 DOCTEST_CHECK(g_uint[0] == 2);
163 }
164}
165
166TEST_CASE("Object Data") {
167 Context ctx;
168 uint o1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
169 uint o2 = ctx.addTileObject(nullorigin, make_vec2(1, 1), nullrotation, make_int2(2, 2));
170
171 SUBCASE("Set, Get, Exists, List, Clear") {
172 ctx.setObjectData(o1, "test_uint", (uint) 42);
173 std::vector<vec3> vec_data = {make_vec3(1, 2, 3), make_vec3(4, 5, 6)};
174 ctx.setObjectData(o1, "test_vec3_vec", vec_data);
175
176 DOCTEST_CHECK(ctx.doesObjectDataExist(o1, "test_uint"));
177 DOCTEST_CHECK(ctx.doesObjectDataExist(o1, "test_vec3_vec"));
178 DOCTEST_CHECK(!ctx.doesObjectDataExist(o1, "non_existent"));
179
180 uint ui;
181 ctx.getObjectData(o1, "test_uint", ui);
182 DOCTEST_CHECK(ui == 42);
183
184 std::vector<vec3> v_r;
185 ctx.getObjectData(o1, "test_vec3_vec", v_r);
186 DOCTEST_CHECK(v_r.size() == 2);
187 DOCTEST_CHECK(v_r[1].y == doctest::Approx(5.0));
188
189 DOCTEST_CHECK(ctx.getObjectDataType("test_uint") == HELIOS_TYPE_UINT);
190 DOCTEST_CHECK(ctx.getObjectDataSize(o1, "test_vec3_vec") == 2);
191
192 std::vector<std::string> labels = ctx.listObjectData(o1);
193 DOCTEST_CHECK(labels.size() == 2);
194
195 ctx.clearObjectData(o1, "test_uint");
196 DOCTEST_CHECK(!ctx.doesObjectDataExist(o1, "test_uint"));
197 }
198
199 SUBCASE("Management") {
200 ctx.setObjectData(o1, "my_data", 10);
201
202 ctx.copyObjectData(o1, o2);
203 DOCTEST_CHECK(ctx.doesObjectDataExist(o2, "my_data"));
204
205 ctx.renameObjectData(o1, "my_data", "new_name");
206 DOCTEST_CHECK(!ctx.doesObjectDataExist(o1, "my_data"));
207 DOCTEST_CHECK(ctx.doesObjectDataExist(o1, "new_name"));
208
209 ctx.duplicateObjectData(o2, "my_data", "my_data_copy");
210 DOCTEST_CHECK(ctx.doesObjectDataExist(o2, "my_data_copy"));
211
212 std::vector<std::string> all_obj_labels = ctx.listAllObjectDataLabels();
213 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "my_data") != all_obj_labels.end());
214 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "new_name") != all_obj_labels.end());
215 DOCTEST_CHECK(std::find(all_obj_labels.begin(), all_obj_labels.end(), "my_data_copy") != all_obj_labels.end());
216 }
217
218 SUBCASE("Object data error handling") {
219 Context ctx;
220 capture_cerr cerr_buffer;
221 uint bad_oid = 999;
222 DOCTEST_CHECK_THROWS(ctx.getObjectDataType("test"));
223 DOCTEST_CHECK_THROWS(ctx.getObjectDataSize(bad_oid, "test"));
224 DOCTEST_CHECK_THROWS(ctx.doesObjectDataExist(bad_oid, "test"));
225 DOCTEST_CHECK_THROWS(ctx.clearObjectData(bad_oid, "test"));
226 DOCTEST_CHECK_THROWS(ctx.copyObjectData(0, bad_oid));
227 DOCTEST_CHECK_THROWS(ctx.copyObjectData(bad_oid, 0));
228 DOCTEST_CHECK_THROWS(ctx.renameObjectData(bad_oid, "old", "new"));
229 DOCTEST_CHECK_THROWS(ctx.duplicateObjectData(bad_oid, "old", "new"));
230 }
231}
232
233TEST_CASE("Primitive Data") {
234 Context ctx;
235 uint p1 = ctx.addPatch();
236 uint p2 = ctx.addPatch();
237 std::vector<uint> uuids;
238 for (int i = 0; i < 5; ++i) {
239 uint p = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
240 uuids.push_back(p);
241 }
242
243 SUBCASE("Set and Get All Data Types") {
244 // Test all supported scalar types
245 ctx.setPrimitiveData(p1, "test_int", 1);
246 ctx.setPrimitiveData(p1, "test_uint", 1u);
247 ctx.setPrimitiveData(p1, "test_float", 2.f);
248 ctx.setPrimitiveData(p1, "test_double", 2.0);
249 ctx.setPrimitiveData(p1, "test_vec2", make_vec2(1, 2));
250 ctx.setPrimitiveData(p1, "test_vec3", make_vec3(1, 2, 3));
251 ctx.setPrimitiveData(p1, "test_vec4", make_vec4(1, 2, 3, 4));
252 ctx.setPrimitiveData(p1, "test_int2", make_int2(1, 2));
253 ctx.setPrimitiveData(p1, "test_int3", make_int3(1, 2, 3));
254 ctx.setPrimitiveData(p1, "test_int4", make_int4(1, 2, 3, 4));
255 ctx.setPrimitiveData(p1, "test_string", "world");
256
257 int r_i;
258 ctx.getPrimitiveData(p1, "test_int", r_i);
259 DOCTEST_CHECK(r_i == 1);
260 uint r_ui;
261 ctx.getPrimitiveData(p1, "test_uint", r_ui);
262 DOCTEST_CHECK(r_ui == 1);
263 float r_f;
264 ctx.getPrimitiveData(p1, "test_float", r_f);
265 DOCTEST_CHECK(r_f == doctest::Approx(2.0));
266 double r_d;
267 ctx.getPrimitiveData(p1, "test_double", r_d);
268 DOCTEST_CHECK(r_d == doctest::Approx(2.0));
269 vec2 r_v2;
270 ctx.getPrimitiveData(p1, "test_vec2", r_v2);
271 DOCTEST_CHECK(r_v2 == make_vec2(1, 2));
272 vec3 r_v3;
273 ctx.getPrimitiveData(p1, "test_vec3", r_v3);
274 DOCTEST_CHECK(r_v3 == make_vec3(1, 2, 3));
275 vec4 r_v4;
276 ctx.getPrimitiveData(p1, "test_vec4", r_v4);
277 DOCTEST_CHECK(r_v4 == make_vec4(1, 2, 3, 4));
278 int2 r_i2;
279 ctx.getPrimitiveData(p1, "test_int2", r_i2);
280 DOCTEST_CHECK(r_i2 == make_int2(1, 2));
281 int3 r_i3;
282 ctx.getPrimitiveData(p1, "test_int3", r_i3);
283 DOCTEST_CHECK(r_i3 == make_int3(1, 2, 3));
284 int4 r_i4;
285 ctx.getPrimitiveData(p1, "test_int4", r_i4);
286 DOCTEST_CHECK(r_i4 == make_int4(1, 2, 3, 4));
287 std::string r_s;
288 ctx.getPrimitiveData(p1, "test_string", r_s);
289 DOCTEST_CHECK(r_s == "world");
290
291 // Test all supported vector types
292 std::vector<int> v_i = {1, 2, 3};
293 ctx.setPrimitiveData(p1, "test_v_int", v_i);
294 std::vector<int> r_v_i;
295 ctx.getPrimitiveData(p1, "test_v_int", r_v_i);
296 DOCTEST_CHECK(r_v_i == v_i);
297 std::vector<uint> v_ui = {4, 5, 6};
298 ctx.setPrimitiveData(p1, "test_v_uint", v_ui);
299 std::vector<uint> r_v_ui;
300 ctx.getPrimitiveData(p1, "test_v_uint", r_v_ui);
301 DOCTEST_CHECK(r_v_ui == v_ui);
302 std::vector<float> v_f = {1.1, 2.2};
303 ctx.setPrimitiveData(p1, "test_v_float", v_f);
304 std::vector<float> r_v_f;
305 ctx.getPrimitiveData(p1, "test_v_float", r_v_f);
306 DOCTEST_CHECK(r_v_f == v_f);
307 std::vector<double> v_d = {1.1, 2.2};
308 ctx.setPrimitiveData(p1, "test_v_double", v_d);
309 std::vector<double> r_v_d;
310 ctx.getPrimitiveData(p1, "test_v_double", r_v_d);
311 DOCTEST_CHECK(r_v_d == v_d);
312 std::vector<vec2> v_v2 = {make_vec2(1, 2), make_vec2(3, 4)};
313 ctx.setPrimitiveData(p1, "test_v_vec2", v_v2);
314 std::vector<vec2> r_v_v2;
315 ctx.getPrimitiveData(p1, "test_v_vec2", r_v_v2);
316 DOCTEST_CHECK(r_v_v2 == v_v2);
317 std::vector<vec3> v_v3 = {make_vec3(1, 2, 3), make_vec3(4, 5, 6)};
318 ctx.setPrimitiveData(p1, "test_v_vec3", v_v3);
319 std::vector<vec3> r_v_v3;
320 ctx.getPrimitiveData(p1, "test_v_vec3", r_v_v3);
321 DOCTEST_CHECK(r_v_v3 == v_v3);
322 std::vector<vec4> v_v4 = {make_vec4(1, 2, 3, 4), make_vec4(5, 6, 7, 8)};
323 ctx.setPrimitiveData(p1, "test_v_vec4", v_v4);
324 std::vector<vec4> r_v_v4;
325 ctx.getPrimitiveData(p1, "test_v_vec4", r_v_v4);
326 DOCTEST_CHECK(r_v_v4 == v_v4);
327 std::vector<int2> v_i2 = {make_int2(1, 2), make_int2(3, 4)};
328 ctx.setPrimitiveData(p1, "test_v_int2", v_i2);
329 std::vector<int2> r_v_i2;
330 ctx.getPrimitiveData(p1, "test_v_int2", r_v_i2);
331 DOCTEST_CHECK(r_v_i2 == v_i2);
332 std::vector<int3> v_i3 = {make_int3(1, 2, 3), make_int3(4, 5, 6)};
333 ctx.setPrimitiveData(p1, "test_v_int3", v_i3);
334 std::vector<int3> r_v_i3;
335 ctx.getPrimitiveData(p1, "test_v_int3", r_v_i3);
336 DOCTEST_CHECK(r_v_i3 == v_i3);
337 std::vector<int4> v_i4 = {make_int4(1, 2, 3, 4), make_int4(5, 6, 7, 8)};
338 ctx.setPrimitiveData(p1, "test_v_int4", v_i4);
339 std::vector<int4> r_v_i4;
340 ctx.getPrimitiveData(p1, "test_v_int4", r_v_i4);
341 DOCTEST_CHECK(r_v_i4 == v_i4);
342 std::vector<std::string> v_s = {"hello", "world"};
343 ctx.setPrimitiveData(p1, "test_v_string", v_s);
344 std::vector<std::string> r_v_s;
345 ctx.getPrimitiveData(p1, "test_v_string", r_v_s);
346 DOCTEST_CHECK(r_v_s == v_s);
347 }
348
349 SUBCASE("Management") {
350 ctx.setPrimitiveData(p1, "my_data", 10);
351 ctx.copyPrimitiveData(p1, p2);
352 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2, "my_data"));
353 int val;
354 ctx.getPrimitiveData(p2, "my_data", val);
355 DOCTEST_CHECK(val == 10);
356
357 ctx.renamePrimitiveData(p1, "my_data", "new_data_name");
358 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p1, "my_data"));
359 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1, "new_data_name"));
360
361 ctx.duplicatePrimitiveData(p2, "my_data", "my_data_copy");
362 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p2, "my_data_copy"));
363 ctx.getPrimitiveData(p2, "my_data_copy", val);
364 DOCTEST_CHECK(val == 10);
365
366 ctx.setPrimitiveData(p1, "global_copy_test", 5.5f);
367 ctx.duplicatePrimitiveData("global_copy_test", "global_copy_test_new");
368 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p1, "global_copy_test_new"));
369 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p2, "global_copy_test_new"));
370
371 std::vector<std::string> all_labels = ctx.listAllPrimitiveDataLabels();
372 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "my_data") != all_labels.end());
373 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "my_data_copy") != all_labels.end());
374 DOCTEST_CHECK(std::find(all_labels.begin(), all_labels.end(), "new_data_name") != all_labels.end());
375
376 ctx.clearPrimitiveData(p1, "new_data_name");
377 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p1, "new_data_name"));
378 ctx.clearPrimitiveData(uuids, "global_copy_test");
379 }
380
381 SUBCASE("Calculations") {
382 for (int i = 0; i < 5; ++i) {
383 ctx.setPrimitiveData(uuids[i], "float_val", (float) i);
384 ctx.setPrimitiveData(uuids[i], "double_val", (double) i);
385 ctx.setPrimitiveData(uuids[i], "vec2_val", make_vec2((float) i, (float) i));
386 }
387
388 float float_mean;
389 ctx.calculatePrimitiveDataMean(uuids, "float_val", float_mean);
390 DOCTEST_CHECK(float_mean == doctest::Approx(2.0f));
391 double double_mean;
392 ctx.calculatePrimitiveDataMean(uuids, "double_val", double_mean);
393 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
394 vec2 vec2_mean;
395 ctx.calculatePrimitiveDataMean(uuids, "vec2_val", vec2_mean);
396 DOCTEST_CHECK(vec2_mean.x == doctest::Approx(2.0f));
397
398 float awt_mean_f;
399 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "float_val", awt_mean_f);
400 DOCTEST_CHECK(awt_mean_f == doctest::Approx(2.0f));
401
402 float float_sum;
403 ctx.calculatePrimitiveDataSum(uuids, "float_val", float_sum);
404 DOCTEST_CHECK(float_sum == doctest::Approx(10.0f));
405
406 float awt_sum_f;
407 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "float_val", awt_sum_f);
408 DOCTEST_CHECK(awt_sum_f == doctest::Approx(10.0f));
409
410 ctx.scalePrimitiveData(uuids, "float_val", 2.0f);
411 ctx.getPrimitiveData(uuids[2], "float_val", float_mean);
412 DOCTEST_CHECK(float_mean == doctest::Approx(4.0f));
413 capture_cerr cerr_buffer;
414 ctx.scalePrimitiveData("double_val", 0.5f);
415 ctx.getPrimitiveData(uuids[4], "double_val", double_mean);
416 DOCTEST_CHECK(double_mean == doctest::Approx(2.0));
417 DOCTEST_CHECK(cerr_buffer.has_output());
418
419 ctx.setPrimitiveData(uuids, "int_val", 10);
420 ctx.incrementPrimitiveData(uuids, "int_val", 5);
421 int int_val;
422 ctx.getPrimitiveData(uuids[0], "int_val", int_val);
423 DOCTEST_CHECK(int_val == 15);
424 }
425
426 SUBCASE("Context::calculatePrimitiveDataMean empty list") {
427 Context ctx;
428 std::vector<uint> uuids;
429 float float_mean;
430 capture_cerr cerr_buffer;
431 ctx.calculatePrimitiveDataMean(uuids, "non_existent", float_mean);
432 DOCTEST_CHECK(float_mean == 0.f);
433 DOCTEST_CHECK(cerr_buffer.has_output());
434 }
435
436 SUBCASE("Context::calculatePrimitiveDataMean vec3/vec4") {
437 Context ctx;
438 uint p1 = ctx.addPatch();
439 ctx.setPrimitiveData(p1, "v3", make_vec3(1, 2, 3));
440 ctx.setPrimitiveData(p1, "v4", make_vec4(1, 2, 3, 4));
441 std::vector<uint> uuids = {p1};
442 vec3 v3_mean;
443 vec4 v4_mean;
444 ctx.calculatePrimitiveDataMean(uuids, "v3", v3_mean);
445 ctx.calculatePrimitiveDataMean(uuids, "v4", v4_mean);
446 DOCTEST_CHECK(v3_mean == make_vec3(1, 2, 3));
447 DOCTEST_CHECK(v4_mean == make_vec4(1, 2, 3, 4));
448 }
449
450 SUBCASE("Context::calculatePrimitiveDataAreaWeightedMean all types") {
451 Context ctx;
452 uint p = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(2, 2)); // Area = 4
453 ctx.setPrimitiveData(p, "double", 2.5);
454 ctx.setPrimitiveData(p, "vec2", make_vec2(1, 2));
455 ctx.setPrimitiveData(p, "vec3", make_vec3(1, 2, 3));
456 ctx.setPrimitiveData(p, "vec4", make_vec4(1, 2, 3, 4));
457 std::vector<uint> uuids = {p};
458
459 double d_mean;
460 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "double", d_mean);
461 DOCTEST_CHECK(d_mean == doctest::Approx(2.5));
462
463 vec2 v2_mean;
464 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec2", v2_mean);
465 DOCTEST_CHECK(v2_mean == make_vec2(1, 2));
466
467 vec3 v3_mean;
468 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec3", v3_mean);
469 DOCTEST_CHECK(v3_mean == make_vec3(1, 2, 3));
470
471 vec4 v4_mean;
472 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec4", v4_mean);
473 DOCTEST_CHECK(v4_mean == make_vec4(1, 2, 3, 4));
474 }
475
476 SUBCASE("Context::calculatePrimitiveDataSum all types") {
477 Context ctx;
478 uint p = ctx.addPatch();
479 ctx.setPrimitiveData(p, "double", 2.5);
480 ctx.setPrimitiveData(p, "vec2", make_vec2(1, 2));
481 ctx.setPrimitiveData(p, "vec3", make_vec3(1, 2, 3));
482 ctx.setPrimitiveData(p, "vec4", make_vec4(1, 2, 3, 4));
483 std::vector<uint> uuids = {p};
484
485 double d_sum;
486 ctx.calculatePrimitiveDataSum(uuids, "double", d_sum);
487 DOCTEST_CHECK(d_sum == doctest::Approx(2.5));
488
489 vec2 v2_sum;
490 ctx.calculatePrimitiveDataSum(uuids, "vec2", v2_sum);
491 DOCTEST_CHECK(v2_sum == make_vec2(1, 2));
492
493 vec3 v3_sum;
494 ctx.calculatePrimitiveDataSum(uuids, "vec3", v3_sum);
495 DOCTEST_CHECK(v3_sum == make_vec3(1, 2, 3));
496
497 vec4 v4_sum;
498 ctx.calculatePrimitiveDataSum(uuids, "vec4", v4_sum);
499 DOCTEST_CHECK(v4_sum == make_vec4(1, 2, 3, 4));
500 }
501
502 SUBCASE("Context::incrementPrimitiveData all types") {
503 Context ctx;
504 uint p = ctx.addPatch();
505 ctx.setPrimitiveData(p, "uint", (uint) 1);
506 ctx.setPrimitiveData(p, "float", 1.f);
507 ctx.setPrimitiveData(p, "double", 1.0);
508 std::vector<uint> uuids = {p};
509
510 ctx.incrementPrimitiveData(uuids, "uint", (uint) 2);
511 uint u_val;
512 ctx.getPrimitiveData(p, "uint", u_val);
513 DOCTEST_CHECK(u_val == 3);
514
515 ctx.incrementPrimitiveData(uuids, "float", 2.f);
516 float f_val;
517 ctx.getPrimitiveData(p, "float", f_val);
518 DOCTEST_CHECK(f_val == doctest::Approx(3.f));
519
520 ctx.incrementPrimitiveData(uuids, "double", 2.0);
521 double d_val;
522 ctx.getPrimitiveData(p, "double", d_val);
523 DOCTEST_CHECK(d_val == doctest::Approx(3.0));
524 }
525
526 SUBCASE("Aggregation and Filtering") {
527 std::vector<uint> agg_uuids;
528 for (int i = 0; i < 3; ++i) {
529 uint p = ctx.addPatch();
530 ctx.setPrimitiveData(p, "d1", (float) i);
531 ctx.setPrimitiveData(p, "d2", (float) i * 2.0f);
532 ctx.setPrimitiveData(p, "d3", (float) i * 3.0f);
533 ctx.setPrimitiveData(p, "filter_me", i);
534 agg_uuids.push_back(p);
535 }
536
537 std::vector<std::string> labels = {"d1", "d2", "d3"};
538 ctx.aggregatePrimitiveDataSum(agg_uuids, labels, "sum_data");
539 float sum_val;
540 ctx.getPrimitiveData(agg_uuids[1], "sum_data", sum_val);
541 DOCTEST_CHECK(sum_val == doctest::Approx(1.f + 2.f + 3.f));
542
543 ctx.aggregatePrimitiveDataProduct(agg_uuids, labels, "prod_data");
544 float prod_val;
545 ctx.getPrimitiveData(agg_uuids[2], "prod_data", prod_val);
546 DOCTEST_CHECK(prod_val == doctest::Approx(2.f * 4.f * 6.f));
547
548 std::vector<uint> filtered = ctx.filterPrimitivesByData(agg_uuids, "filter_me", 1, ">=");
549 DOCTEST_CHECK(filtered.size() == 2);
550 filtered = ctx.filterPrimitivesByData(agg_uuids, "filter_me", 1, "==");
551 DOCTEST_CHECK(filtered.size() == 1);
552 DOCTEST_CHECK(filtered[0] == agg_uuids[1]);
553 }
554
555 SUBCASE("Context::filterPrimitivesByData all types") {
556 Context ctx;
557 uint p = ctx.addPatch();
558 ctx.setPrimitiveData(p, "double", 1.0);
559 ctx.setPrimitiveData(p, "uint", (uint) 10);
560 std::vector<uint> uuids = {p};
561
562 auto filtered_d = ctx.filterPrimitivesByData(uuids, "double", 0.5, ">");
563 DOCTEST_CHECK(filtered_d.size() == 1);
564
565 auto filtered_u = ctx.filterPrimitivesByData(uuids, "uint", (uint) 5, ">");
566 DOCTEST_CHECK(filtered_u.size() == 1);
567 }
568
569 SUBCASE("Error Handling") {
570 capture_cerr cerr_buffer;
571 uint bad_uuid = 999;
572 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataType("test"));
573 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataSize(bad_uuid, "test"));
574 DOCTEST_CHECK_THROWS(ctx.doesPrimitiveDataExist(bad_uuid, "test"));
575 DOCTEST_CHECK_THROWS(ctx.clearPrimitiveData(bad_uuid, "test"));
576 DOCTEST_CHECK_THROWS(ctx.copyPrimitiveData(p1, bad_uuid));
577 DOCTEST_CHECK_THROWS(ctx.copyPrimitiveData(bad_uuid, p1));
578 DOCTEST_CHECK_THROWS(ctx.renamePrimitiveData(bad_uuid, "old", "new"));
579 DOCTEST_CHECK_THROWS(ctx.duplicatePrimitiveData(bad_uuid, "old", "new"));
580
581 std::vector<uint> empty_uuids;
582 float float_mean;
583 ctx.calculatePrimitiveDataMean(empty_uuids, "non_existent", float_mean);
584 DOCTEST_CHECK(float_mean == 0.f);
585 DOCTEST_CHECK(cerr_buffer.has_output());
586
587 ctx.setPrimitiveData(p1, "float_val_err", 1.f);
588 ctx.incrementPrimitiveData(std::vector<uint>{p1}, "float_val_err", 1); // Wrong type
589 DOCTEST_CHECK(cerr_buffer.has_output());
590
591 std::vector<uint> filtered_UUIDs;
592 DOCTEST_CHECK_THROWS_AS(filtered_UUIDs = ctx.filterPrimitivesByData(uuids, "filter_me", 1, "!!"), std::runtime_error);
593 }
594
595 SUBCASE("Primitive::getPrimitiveDataSize all types") {
596 Context ctx;
597 uint p = ctx.addPatch();
598 ctx.setPrimitiveData(p, "uint", (uint) 1);
599 ctx.setPrimitiveData(p, "double", 1.0);
600 ctx.setPrimitiveData(p, "vec2", make_vec2(1, 1));
601 ctx.setPrimitiveData(p, "vec3", make_vec3(1, 1, 1));
602 ctx.setPrimitiveData(p, "vec4", make_vec4(1, 1, 1, 1));
603 ctx.setPrimitiveData(p, "int2", make_int2(1, 1));
604 ctx.setPrimitiveData(p, "int3", make_int3(1, 1, 1));
605 ctx.setPrimitiveData(p, "int4", make_int4(1, 1, 1, 1));
606 ctx.setPrimitiveData(p, "string", "hello");
607
608 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "uint") == 1);
609 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "double") == 1);
610 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec2") == 1);
611 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec3") == 1);
612 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec4") == 1);
613 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "int2") == 1);
614 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "int3") == 1);
615 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "int4") == 1);
616 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "string") == 1);
617 }
618
619 SUBCASE("Primitive::clearPrimitiveData all types") {
620 Context ctx;
621 uint p = ctx.addPatch();
622 ctx.setPrimitiveData(p, "uint", (uint) 1);
623 ctx.setPrimitiveData(p, "double", 1.0);
624 ctx.setPrimitiveData(p, "vec2", make_vec2(1, 1));
625 ctx.setPrimitiveData(p, "vec3", make_vec3(1, 1, 1));
626 ctx.setPrimitiveData(p, "vec4", make_vec4(1, 1, 1, 1));
627 ctx.setPrimitiveData(p, "int2", make_int2(1, 1));
628 ctx.setPrimitiveData(p, "int3", make_int3(1, 1, 1));
629 ctx.setPrimitiveData(p, "int4", make_int4(1, 1, 1, 1));
630 ctx.setPrimitiveData(p, "string", "hello");
631
632 ctx.clearPrimitiveData(p, "uint");
633 ctx.clearPrimitiveData(p, "double");
634 ctx.clearPrimitiveData(p, "vec2");
635 ctx.clearPrimitiveData(p, "vec3");
636 ctx.clearPrimitiveData(p, "vec4");
637 ctx.clearPrimitiveData(p, "int2");
638 ctx.clearPrimitiveData(p, "int3");
639 ctx.clearPrimitiveData(p, "int4");
640 ctx.clearPrimitiveData(p, "string");
641
642 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "uint"));
643 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "double"));
644 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "vec2"));
645 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "vec3"));
646 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "vec4"));
647 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "int2"));
648 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "int3"));
649 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "int4"));
650 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p, "string"));
651 }
652
653 SUBCASE("Context data functions with invalid UUID") {
654 Context ctx;
655 capture_cerr cerr_buffer;
656 uint bad_uuid = 999;
657 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataType("test"));
658 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataSize(bad_uuid, "test"));
659 DOCTEST_CHECK_THROWS(ctx.doesPrimitiveDataExist(bad_uuid, "test"));
660 DOCTEST_CHECK_THROWS(ctx.clearPrimitiveData(bad_uuid, "test"));
661 DOCTEST_CHECK_THROWS(ctx.copyPrimitiveData(0, bad_uuid));
662 DOCTEST_CHECK_THROWS(ctx.copyPrimitiveData(bad_uuid, 0));
663 DOCTEST_CHECK_THROWS(ctx.renamePrimitiveData(bad_uuid, "old", "new"));
664 DOCTEST_CHECK_THROWS(ctx.duplicatePrimitiveData(bad_uuid, "old", "new"));
665 }
666
667 SUBCASE("Context::clearPrimitiveData for multiple UUIDs") {
668 Context ctx;
669 uint p1 = ctx.addPatch();
670 uint p2 = ctx.addPatch();
671 ctx.setPrimitiveData(p1, "data", 1);
672 ctx.setPrimitiveData(p2, "data", 2);
673 std::vector<uint> uuids = {p1, p2};
674 ctx.clearPrimitiveData(uuids, "data");
675 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p1, "data"));
676 DOCTEST_CHECK(!ctx.doesPrimitiveDataExist(p2, "data"));
677 }
678
679 SUBCASE("Context::duplicatePrimitiveData all types") {
680 Context ctx;
681 uint p = ctx.addPatch();
682 ctx.setPrimitiveData(p, "uint", (uint) 1);
683 ctx.setPrimitiveData(p, "double", 1.0);
684 ctx.setPrimitiveData(p, "vec2", make_vec2(1, 1));
685 ctx.setPrimitiveData(p, "vec3", make_vec3(1, 1, 1));
686 ctx.setPrimitiveData(p, "vec4", make_vec4(1, 1, 1, 1));
687 ctx.setPrimitiveData(p, "int2", make_int2(1, 1));
688 ctx.setPrimitiveData(p, "int3", make_int3(1, 1, 1));
689 ctx.setPrimitiveData(p, "int4", make_int4(1, 1, 1, 1));
690 ctx.setPrimitiveData(p, "string", "hello");
691
692 ctx.duplicatePrimitiveData(p, "uint", "uint_copy");
693 ctx.duplicatePrimitiveData(p, "double", "double_copy");
694 ctx.duplicatePrimitiveData(p, "vec2", "vec2_copy");
695 ctx.duplicatePrimitiveData(p, "vec3", "vec3_copy");
696 ctx.duplicatePrimitiveData(p, "vec4", "vec4_copy");
697 ctx.duplicatePrimitiveData(p, "int2", "int2_copy");
698 ctx.duplicatePrimitiveData(p, "int3", "int3_copy");
699 ctx.duplicatePrimitiveData(p, "int4", "int4_copy");
700 ctx.duplicatePrimitiveData(p, "string", "string_copy");
701
702 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "uint_copy"));
703 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "double_copy"));
704 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "vec2_copy"));
705 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "vec3_copy"));
706 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "vec4_copy"));
707 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "int2_copy"));
708 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "int3_copy"));
709 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "int4_copy"));
710 DOCTEST_CHECK(ctx.doesPrimitiveDataExist(p, "string_copy"));
711 }
712
713
714 SUBCASE("Generic Value-Level Caching System") {
715 Context ctx_test;
716
717 // Test error handling - should throw when caching not enabled
718 std::vector<std::string> test_values;
719 bool threw_error = false;
720 try {
721 ctx_test.getUniquePrimitiveDataValues("uncached_label", test_values);
722 } catch (const std::runtime_error &e) {
723 threw_error = true;
724 DOCTEST_CHECK(std::string(e.what()).find("Value-level caching is not enabled") != std::string::npos);
725 }
726 DOCTEST_CHECK(threw_error);
727
728 threw_error = false;
729 try {
730 ctx_test.getUniqueObjectDataValues("uncached_label", test_values);
731 } catch (const std::runtime_error &e) {
732 threw_error = true;
733 DOCTEST_CHECK(std::string(e.what()).find("Value-level caching is not enabled") != std::string::npos);
734 }
735 DOCTEST_CHECK(threw_error);
736
737 // Test cache configuration
738 DOCTEST_CHECK(!ctx_test.isPrimitiveDataValueCachingEnabled("test_string"));
739 DOCTEST_CHECK(!ctx_test.isObjectDataValueCachingEnabled("test_string"));
740
741 ctx_test.enablePrimitiveDataValueCaching("test_string");
742 ctx_test.enablePrimitiveDataValueCaching("test_int");
743 ctx_test.enablePrimitiveDataValueCaching("test_uint");
744 ctx_test.enableObjectDataValueCaching("obj_string");
745 ctx_test.enableObjectDataValueCaching("obj_int");
746 ctx_test.enableObjectDataValueCaching("obj_uint");
747
748 DOCTEST_CHECK(ctx_test.isPrimitiveDataValueCachingEnabled("test_string"));
749 DOCTEST_CHECK(ctx_test.isPrimitiveDataValueCachingEnabled("test_int"));
750 DOCTEST_CHECK(ctx_test.isPrimitiveDataValueCachingEnabled("test_uint"));
751 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("obj_string"));
752 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("obj_int"));
753 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("obj_uint"));
754
755 // Create test primitives and objects
756 uint patch1 = ctx_test.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
757 uint patch2 = ctx_test.addPatch(make_vec3(1, 0, 0), make_vec2(1, 1));
758 uint patch3 = ctx_test.addPatch(make_vec3(2, 0, 0), make_vec2(1, 1));
759 uint obj1 = ctx_test.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
760 uint obj2 = ctx_test.addTileObject(make_vec3(1, 0, 0), make_vec2(1, 1), nullrotation, make_int2(1, 1));
761
762 // Test primitive string value caching
763 ctx_test.setPrimitiveData(patch1, "test_string", std::string("apple"));
764 ctx_test.setPrimitiveData(patch2, "test_string", std::string("banana"));
765 ctx_test.setPrimitiveData(patch3, "test_string", std::string("apple"));
766
767 std::vector<std::string> string_values;
768 ctx_test.getUniquePrimitiveDataValues("test_string", string_values);
769 DOCTEST_CHECK(string_values.size() == 2);
770 std::sort(string_values.begin(), string_values.end());
771 DOCTEST_CHECK(string_values[0] == "apple");
772 DOCTEST_CHECK(string_values[1] == "banana");
773
774 // Test primitive int value caching
775 ctx_test.setPrimitiveData(patch1, "test_int", 10);
776 ctx_test.setPrimitiveData(patch2, "test_int", 20);
777 ctx_test.setPrimitiveData(patch3, "test_int", 10);
778
779 std::vector<int> int_values;
780 ctx_test.getUniquePrimitiveDataValues("test_int", int_values);
781 DOCTEST_CHECK(int_values.size() == 2);
782 std::sort(int_values.begin(), int_values.end());
783 DOCTEST_CHECK(int_values[0] == 10);
784 DOCTEST_CHECK(int_values[1] == 20);
785
786 // Test primitive uint value caching
787 ctx_test.setPrimitiveData(patch1, "test_uint", 100u);
788 ctx_test.setPrimitiveData(patch2, "test_uint", 200u);
789 ctx_test.setPrimitiveData(patch3, "test_uint", 100u);
790
791 std::vector<uint> uint_values;
792 ctx_test.getUniquePrimitiveDataValues("test_uint", uint_values);
793 DOCTEST_CHECK(uint_values.size() == 2);
794 std::sort(uint_values.begin(), uint_values.end());
795 DOCTEST_CHECK(uint_values[0] == 100u);
796 DOCTEST_CHECK(uint_values[1] == 200u);
797
798 // Test object string value caching
799 ctx_test.setObjectData(obj1, "obj_string", std::string("circle"));
800 ctx_test.setObjectData(obj2, "obj_string", std::string("square"));
801
802 std::vector<std::string> obj_string_values;
803 ctx_test.getUniqueObjectDataValues("obj_string", obj_string_values);
804 DOCTEST_CHECK(obj_string_values.size() == 2);
805 std::sort(obj_string_values.begin(), obj_string_values.end());
806 DOCTEST_CHECK(obj_string_values[0] == "circle");
807 DOCTEST_CHECK(obj_string_values[1] == "square");
808
809 // Test object int value caching
810 ctx_test.setObjectData(obj1, "obj_int", 5);
811 ctx_test.setObjectData(obj2, "obj_int", 15);
812
813 std::vector<int> obj_int_values;
814 ctx_test.getUniqueObjectDataValues("obj_int", obj_int_values);
815 DOCTEST_CHECK(obj_int_values.size() == 2);
816 std::sort(obj_int_values.begin(), obj_int_values.end());
817 DOCTEST_CHECK(obj_int_values[0] == 5);
818 DOCTEST_CHECK(obj_int_values[1] == 15);
819
820 // Test object uint value caching
821 ctx_test.setObjectData(obj1, "obj_uint", 50u);
822 ctx_test.setObjectData(obj2, "obj_uint", 150u);
823
824 std::vector<uint> obj_uint_values;
825 ctx_test.getUniqueObjectDataValues("obj_uint", obj_uint_values);
826 DOCTEST_CHECK(obj_uint_values.size() == 2);
827 std::sort(obj_uint_values.begin(), obj_uint_values.end());
828 DOCTEST_CHECK(obj_uint_values[0] == 50u);
829 DOCTEST_CHECK(obj_uint_values[1] == 150u);
830
831 // Test value update (cache maintenance)
832 ctx_test.setPrimitiveData(patch1, "test_string", std::string("cherry"));
833 ctx_test.getUniquePrimitiveDataValues("test_string", string_values);
834 DOCTEST_CHECK(string_values.size() == 3);
835 std::sort(string_values.begin(), string_values.end());
836 DOCTEST_CHECK(string_values[0] == "apple");
837 DOCTEST_CHECK(string_values[1] == "banana");
838 DOCTEST_CHECK(string_values[2] == "cherry");
839
840 // Test clearing data (cache maintenance)
841 ctx_test.clearPrimitiveData(patch2, "test_string");
842 ctx_test.getUniquePrimitiveDataValues("test_string", string_values);
843 DOCTEST_CHECK(string_values.size() == 2);
844 std::sort(string_values.begin(), string_values.end());
845 DOCTEST_CHECK(string_values[0] == "apple");
846 DOCTEST_CHECK(string_values[1] == "cherry");
847
848 ctx_test.clearObjectData(obj1, "obj_string");
849 ctx_test.getUniqueObjectDataValues("obj_string", obj_string_values);
850 DOCTEST_CHECK(obj_string_values.size() == 1);
851 DOCTEST_CHECK(obj_string_values[0] == "square");
852
853 // Test disabling cache - should throw error after disabling
854 ctx_test.disablePrimitiveDataValueCaching("test_string");
855 DOCTEST_CHECK(!ctx_test.isPrimitiveDataValueCachingEnabled("test_string"));
856
857 threw_error = false;
858 try {
859 ctx_test.getUniquePrimitiveDataValues("test_string", string_values);
860 } catch (const std::runtime_error &e) {
861 threw_error = true;
862 }
863 DOCTEST_CHECK(threw_error);
864
865 ctx_test.disableObjectDataValueCaching("obj_string");
866 DOCTEST_CHECK(!ctx_test.isObjectDataValueCachingEnabled("obj_string"));
867
868 threw_error = false;
869 try {
870 ctx_test.getUniqueObjectDataValues("obj_string", obj_string_values);
871 } catch (const std::runtime_error &e) {
872 threw_error = true;
873 }
874 DOCTEST_CHECK(threw_error);
875
876 // Test empty results when no data exists (but caching is enabled)
877 ctx_test.enablePrimitiveDataValueCaching("empty_label");
878 std::vector<std::string> empty_values;
879 ctx_test.getUniquePrimitiveDataValues("empty_label", empty_values);
880 DOCTEST_CHECK(empty_values.empty());
881 }
882}
883
884TEST_CASE("Vector setObjectData Value Caching") {
885 Context ctx_test;
886
887 // Enable caching for test labels
888 ctx_test.enableObjectDataValueCaching("vec_string");
889 ctx_test.enableObjectDataValueCaching("vec_int");
890 ctx_test.enableObjectDataValueCaching("vec_uint");
891
892 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("vec_string"));
893 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("vec_int"));
894 DOCTEST_CHECK(ctx_test.isObjectDataValueCachingEnabled("vec_uint"));
895
896 // Create test objects
897 uint obj1 = ctx_test.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
898 uint obj2 = ctx_test.addTileObject(make_vec3(1, 0, 0), make_vec2(1, 1), nullrotation, make_int2(1, 1));
899 uint obj3 = ctx_test.addSphereObject(1, make_vec3(2, 0, 0), 1.0f);
900 uint obj4 = ctx_test.addConeObject(1, make_vec3(3, 0, 0), make_vec3(3, 0, 2), 1.0f, 2.0f);
901
902 SUBCASE("Vector<uint> string caching") {
903 // Test vector version with string data
904 std::vector<uint> obj_vec = {obj1, obj2};
905 ctx_test.setObjectData(obj_vec, "vec_string", std::string("apple"));
906
907 // Single object gets different value
908 ctx_test.setObjectData(obj3, "vec_string", std::string("banana"));
909
910 std::vector<std::string> string_values;
911 ctx_test.getUniqueObjectDataValues("vec_string", string_values);
912 DOCTEST_CHECK(string_values.size() == 2);
913 std::sort(string_values.begin(), string_values.end());
914 DOCTEST_CHECK(string_values[0] == "apple");
915 DOCTEST_CHECK(string_values[1] == "banana");
916 }
917
918 SUBCASE("Vector<uint> int caching") {
919 // Test vector version with int data
920 std::vector<uint> obj_vec = {obj1, obj2, obj3};
921 ctx_test.setObjectData(obj_vec, "vec_int", 100);
922
923 // Single object gets different value
924 ctx_test.setObjectData(obj4, "vec_int", 200);
925
926 std::vector<int> int_values;
927 ctx_test.getUniqueObjectDataValues("vec_int", int_values);
928 DOCTEST_CHECK(int_values.size() == 2);
929 std::sort(int_values.begin(), int_values.end());
930 DOCTEST_CHECK(int_values[0] == 100);
931 DOCTEST_CHECK(int_values[1] == 200);
932 }
933
934 SUBCASE("Vector<uint> uint caching") {
935 // Test vector version with uint data
936 std::vector<uint> obj_vec = {obj1, obj2};
937 ctx_test.setObjectData(obj_vec, "vec_uint", 50u);
938
939 // Another vector with same value (should not increase unique count)
940 std::vector<uint> obj_vec2 = {obj3};
941 ctx_test.setObjectData(obj_vec2, "vec_uint", 50u);
942
943 // Single object gets different value
944 ctx_test.setObjectData(obj4, "vec_uint", 150u);
945
946 std::vector<uint> uint_values;
947 ctx_test.getUniqueObjectDataValues("vec_uint", uint_values);
948 DOCTEST_CHECK(uint_values.size() == 2);
949 std::sort(uint_values.begin(), uint_values.end());
950 DOCTEST_CHECK(uint_values[0] == 50u);
951 DOCTEST_CHECK(uint_values[1] == 150u);
952 }
953
954 SUBCASE("Vector<vector<uint>> caching") {
955 // Test nested vector version
956 std::vector<std::vector<uint>> nested_vec = {{obj1, obj2}, {obj3}};
957 ctx_test.setObjectData(nested_vec, "vec_string", std::string("cherry"));
958
959 // Single object gets different value
960 ctx_test.setObjectData(obj4, "vec_string", std::string("date"));
961
962 std::vector<std::string> string_values;
963 ctx_test.getUniqueObjectDataValues("vec_string", string_values);
964 DOCTEST_CHECK(string_values.size() == 2);
965 std::sort(string_values.begin(), string_values.end());
966 DOCTEST_CHECK(string_values[0] == "cherry");
967 DOCTEST_CHECK(string_values[1] == "date");
968 }
969
970 SUBCASE("Vector<vector<vector<uint>>> caching") {
971 // Test triple nested vector version
972 std::vector<std::vector<std::vector<uint>>> triple_vec = {{{obj1}, {obj2}}, {{obj3}}};
973 ctx_test.setObjectData(triple_vec, "vec_int", 300);
974
975 // Single object gets different value
976 ctx_test.setObjectData(obj4, "vec_int", 400);
977
978 std::vector<int> int_values;
979 ctx_test.getUniqueObjectDataValues("vec_int", int_values);
980 DOCTEST_CHECK(int_values.size() == 2);
981 std::sort(int_values.begin(), int_values.end());
982 DOCTEST_CHECK(int_values[0] == 300);
983 DOCTEST_CHECK(int_values[1] == 400);
984 }
985
986 SUBCASE("Vector cache update behavior") {
987 // Test that updating existing data via vector properly maintains cache
988 std::vector<uint> obj_vec = {obj1, obj2};
989
990 // Set initial values
991 ctx_test.setObjectData(obj_vec, "vec_string", std::string("old_value"));
992 ctx_test.setObjectData(obj3, "vec_string", std::string("other_value"));
993
994 // Verify initial state
995 std::vector<std::string> string_values;
996 ctx_test.getUniqueObjectDataValues("vec_string", string_values);
997 DOCTEST_CHECK(string_values.size() == 2);
998
999 // Update values via vector
1000 ctx_test.setObjectData(obj_vec, "vec_string", std::string("new_value"));
1001
1002 // Verify cache is properly updated
1003 ctx_test.getUniqueObjectDataValues("vec_string", string_values);
1004 DOCTEST_CHECK(string_values.size() == 2);
1005 std::sort(string_values.begin(), string_values.end());
1006 DOCTEST_CHECK(string_values[0] == "new_value");
1007 DOCTEST_CHECK(string_values[1] == "other_value");
1008 }
1009
1010 SUBCASE("Mixed single and vector operations") {
1011 // Test mixing single objID and vector objID operations
1012 ctx_test.setObjectData(obj1, "vec_int", 10);
1013
1014 std::vector<uint> obj_vec = {obj2, obj3};
1015 ctx_test.setObjectData(obj_vec, "vec_int", 10); // Same value
1016
1017 ctx_test.setObjectData(obj4, "vec_int", 20); // Different value
1018
1019 std::vector<int> int_values;
1020 ctx_test.getUniqueObjectDataValues("vec_int", int_values);
1021 DOCTEST_CHECK(int_values.size() == 2);
1022 std::sort(int_values.begin(), int_values.end());
1023 DOCTEST_CHECK(int_values[0] == 10);
1024 DOCTEST_CHECK(int_values[1] == 20);
1025 }
1026}
1027
1028TEST_CASE("Object Data Filtering") {
1029 Context ctx;
1030 uint o1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1031 uint o2 = ctx.addTileObject(make_vec3(1, 0, 0), make_vec2(2, 2), nullrotation, make_int2(1, 1));
1032 uint o3 = ctx.addConeObject(5, make_vec3(2, 0, 0), make_vec3(2, 0, 2), 1.0f, 2.0f);
1033
1034 SUBCASE("filterObjectsByData for all types") {
1035 // Set up test data
1036 ctx.setObjectData(o1, "float_val", 1.5f);
1037 ctx.setObjectData(o2, "float_val", 2.5f);
1038 ctx.setObjectData(o3, "float_val", 0.5f);
1039
1040 ctx.setObjectData(o1, "double_val", 1.5);
1041 ctx.setObjectData(o2, "double_val", 2.5);
1042 ctx.setObjectData(o3, "double_val", 0.5);
1043
1044 ctx.setObjectData(o1, "int_val", 1);
1045 ctx.setObjectData(o2, "int_val", 2);
1046 ctx.setObjectData(o3, "int_val", 0);
1047
1048 ctx.setObjectData(o1, "uint_val", (uint) 1);
1049 ctx.setObjectData(o2, "uint_val", (uint) 2);
1050 ctx.setObjectData(o3, "uint_val", (uint) 0);
1051
1052 ctx.setObjectData(o1, "string_val", std::string("apple"));
1053 ctx.setObjectData(o2, "string_val", std::string("banana"));
1054 ctx.setObjectData(o3, "string_val", std::string("cherry"));
1055
1056 std::vector<uint> all_objects = {o1, o2, o3};
1057
1058 // Test float filtering - use std::string for parameter names to avoid ambiguity
1059 auto filtered_f = ctx.filterObjectsByData(all_objects, std::string("float_val"), 1.0f, std::string(">"));
1060 DOCTEST_CHECK(filtered_f.size() == 2);
1061 DOCTEST_CHECK(std::find(filtered_f.begin(), filtered_f.end(), o1) != filtered_f.end());
1062 DOCTEST_CHECK(std::find(filtered_f.begin(), filtered_f.end(), o2) != filtered_f.end());
1063
1064 filtered_f = ctx.filterObjectsByData(all_objects, std::string("float_val"), 2.5f, std::string("=="));
1065 DOCTEST_CHECK(filtered_f.size() == 1);
1066 DOCTEST_CHECK(filtered_f[0] == o2);
1067
1068 // Test double filtering
1069 auto filtered_d = ctx.filterObjectsByData(all_objects, std::string("double_val"), 1.0, std::string(">="));
1070 DOCTEST_CHECK(filtered_d.size() == 2);
1071
1072 // Test int filtering
1073 auto filtered_i = ctx.filterObjectsByData(all_objects, std::string("int_val"), 1, std::string("<"));
1074 DOCTEST_CHECK(filtered_i.size() == 1);
1075 DOCTEST_CHECK(filtered_i[0] == o3);
1076
1077 // Test uint filtering
1078 auto filtered_u = ctx.filterObjectsByData(all_objects, std::string("uint_val"), (uint) 0, std::string(">"));
1079 DOCTEST_CHECK(filtered_u.size() == 2);
1080
1081 // Note: String filtering may not be supported by all filterObjectsByData overloads
1082 // Test with string data but focus on supported data types
1083 }
1084
1085 SUBCASE("filterObjectsByData error handling") {
1086 std::vector<uint> objects = {o1};
1087 capture_cerr cerr_buffer;
1088
1089 // Test invalid comparison operator
1090 std::vector<uint> filtered_f;
1091 DOCTEST_CHECK_THROWS_AS(filtered_f = ctx.filterObjectsByData(objects, std::string("non_existent"), 1.0f, std::string("!!")), std::runtime_error);
1092
1093 // Test non-existent data
1094 auto empty_result = ctx.filterObjectsByData(objects, std::string("non_existent"), 1.0f, std::string(">"));
1095 DOCTEST_CHECK(empty_result.empty());
1096 }
1097}
1098
1099TEST_CASE("Surface Area Calculations") {
1100 Context ctx;
1101
1102 SUBCASE("sumPrimitiveSurfaceArea") {
1103 uint p1 = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(2, 3)); // Area = 6
1104 uint p2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 4)); // Area = 4
1105 uint t1 = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(2, 0, 0), make_vec3(0, 2, 0)); // Area = 2
1106
1107 std::vector<uint> patches = {p1, p2};
1108 std::vector<uint> triangles = {t1};
1109 std::vector<uint> all_prims = {p1, p2, t1};
1110
1111 float patch_area = ctx.sumPrimitiveSurfaceArea(patches);
1112 DOCTEST_CHECK(patch_area == doctest::Approx(10.0f));
1113
1114 float triangle_area = ctx.sumPrimitiveSurfaceArea(triangles);
1115 DOCTEST_CHECK(triangle_area == doctest::Approx(2.0f));
1116
1117 float total_area = ctx.sumPrimitiveSurfaceArea(all_prims);
1118 DOCTEST_CHECK(total_area == doctest::Approx(12.0f));
1119
1120 // Test empty list
1121 std::vector<uint> empty_list;
1122 float zero_area = ctx.sumPrimitiveSurfaceArea(empty_list);
1123 DOCTEST_CHECK(zero_area == doctest::Approx(0.0f));
1124 }
1125}
1126
1127TEST_CASE("Advanced Area-Weighted Calculations") {
1128 Context ctx;
1129
1130 SUBCASE("calculatePrimitiveDataAreaWeightedMean comprehensive") {
1131 // Create primitives with different areas
1132 uint p1 = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1)); // Area = 1
1133 uint p2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(2, 2)); // Area = 4
1134 uint p3 = ctx.addPatch(make_vec3(2, 0, 0), make_vec2(3, 1)); // Area = 3
1135
1136 // Set data for different types
1137 ctx.setPrimitiveData(p1, "double_val", 10.0);
1138 ctx.setPrimitiveData(p2, "double_val", 20.0);
1139 ctx.setPrimitiveData(p3, "double_val", 30.0);
1140
1141 ctx.setPrimitiveData(p1, "vec2_val", make_vec2(1, 2));
1142 ctx.setPrimitiveData(p2, "vec2_val", make_vec2(3, 4));
1143 ctx.setPrimitiveData(p3, "vec2_val", make_vec2(5, 6));
1144
1145 ctx.setPrimitiveData(p1, "vec3_val", make_vec3(1, 2, 3));
1146 ctx.setPrimitiveData(p2, "vec3_val", make_vec3(4, 5, 6));
1147 ctx.setPrimitiveData(p3, "vec3_val", make_vec3(7, 8, 9));
1148
1149 ctx.setPrimitiveData(p1, "vec4_val", make_vec4(1, 2, 3, 4));
1150 ctx.setPrimitiveData(p2, "vec4_val", make_vec4(5, 6, 7, 8));
1151 ctx.setPrimitiveData(p3, "vec4_val", make_vec4(9, 10, 11, 12));
1152
1153 std::vector<uint> uuids = {p1, p2, p3};
1154
1155 // Test double area-weighted mean
1156 // Expected: (10*1 + 20*4 + 30*3) / (1+4+3) = 180/8 = 22.5
1157 double d_mean;
1158 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "double_val", d_mean);
1159 DOCTEST_CHECK(d_mean == doctest::Approx(22.5));
1160
1161 // Test vec2 area-weighted mean
1162 // Expected x: (1*1 + 3*4 + 5*3) / 8 = 28/8 = 3.5
1163 // Expected y: (2*1 + 4*4 + 6*3) / 8 = 36/8 = 4.5
1164 vec2 v2_mean;
1165 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec2_val", v2_mean);
1166 DOCTEST_CHECK(v2_mean.x == doctest::Approx(3.5f));
1167 DOCTEST_CHECK(v2_mean.y == doctest::Approx(4.5f));
1168
1169 // Test vec3 area-weighted mean
1170 vec3 v3_mean;
1171 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec3_val", v3_mean);
1172 DOCTEST_CHECK(v3_mean.x == doctest::Approx(4.75f)); // (1*1 + 4*4 + 7*3) / 8 = 38/8 = 4.75
1173 DOCTEST_CHECK(v3_mean.y == doctest::Approx(5.75f)); // (2*1 + 5*4 + 8*3) / 8 = 46/8 = 5.75
1174 DOCTEST_CHECK(v3_mean.z == doctest::Approx(6.75f)); // (3*1 + 6*4 + 9*3) / 8 = 54/8 = 6.75
1175
1176 // Test vec4 area-weighted mean
1177 vec4 v4_mean;
1178 ctx.calculatePrimitiveDataAreaWeightedMean(uuids, "vec4_val", v4_mean);
1179 DOCTEST_CHECK(v4_mean.x == doctest::Approx(6.0f)); // (1*1 + 5*4 + 9*3) / 8 = 48/8 = 6.0
1180 DOCTEST_CHECK(v4_mean.y == doctest::Approx(7.0f)); // (2*1 + 6*4 + 10*3) / 8 = 56/8 = 7.0
1181 DOCTEST_CHECK(v4_mean.z == doctest::Approx(8.0f)); // (3*1 + 7*4 + 11*3) / 8 = 64/8 = 8.0
1182 DOCTEST_CHECK(v4_mean.w == doctest::Approx(9.0f)); // (4*1 + 8*4 + 12*3) / 8 = 72/8 = 9.0
1183 }
1184
1185 SUBCASE("calculatePrimitiveDataAreaWeightedSum comprehensive") {
1186 uint p1 = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(2, 1)); // Area = 2
1187 uint p2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 3)); // Area = 3
1188
1189 ctx.setPrimitiveData(p1, "double_val", 5.0);
1190 ctx.setPrimitiveData(p2, "double_val", 10.0);
1191
1192 ctx.setPrimitiveData(p1, "vec2_val", make_vec2(1, 2));
1193 ctx.setPrimitiveData(p2, "vec2_val", make_vec2(3, 4));
1194
1195 ctx.setPrimitiveData(p1, "vec3_val", make_vec3(1, 2, 3));
1196 ctx.setPrimitiveData(p2, "vec3_val", make_vec3(4, 5, 6));
1197
1198 ctx.setPrimitiveData(p1, "vec4_val", make_vec4(1, 2, 3, 4));
1199 ctx.setPrimitiveData(p2, "vec4_val", make_vec4(5, 6, 7, 8));
1200
1201 std::vector<uint> uuids = {p1, p2};
1202
1203 // Test double area-weighted sum: 5*2 + 10*3 = 40
1204 double d_sum;
1205 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "double_val", d_sum);
1206 DOCTEST_CHECK(d_sum == doctest::Approx(40.0));
1207
1208 // Test vec2 area-weighted sum
1209 vec2 v2_sum;
1210 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "vec2_val", v2_sum);
1211 DOCTEST_CHECK(v2_sum.x == doctest::Approx(11.0f)); // 1*2 + 3*3 = 11
1212 DOCTEST_CHECK(v2_sum.y == doctest::Approx(16.0f)); // 2*2 + 4*3 = 16
1213
1214 // Test vec3 area-weighted sum
1215 vec3 v3_sum;
1216 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "vec3_val", v3_sum);
1217 DOCTEST_CHECK(v3_sum.x == doctest::Approx(14.0f)); // 1*2 + 4*3 = 14
1218 DOCTEST_CHECK(v3_sum.y == doctest::Approx(19.0f)); // 2*2 + 5*3 = 19
1219 DOCTEST_CHECK(v3_sum.z == doctest::Approx(24.0f)); // 3*2 + 6*3 = 24
1220
1221 // Test vec4 area-weighted sum
1222 vec4 v4_sum;
1223 ctx.calculatePrimitiveDataAreaWeightedSum(uuids, "vec4_val", v4_sum);
1224 DOCTEST_CHECK(v4_sum.x == doctest::Approx(17.0f)); // 1*2 + 5*3 = 17
1225 DOCTEST_CHECK(v4_sum.y == doctest::Approx(22.0f)); // 2*2 + 6*3 = 22
1226 DOCTEST_CHECK(v4_sum.z == doctest::Approx(27.0f)); // 3*2 + 7*3 = 27
1227 DOCTEST_CHECK(v4_sum.w == doctest::Approx(32.0f)); // 4*2 + 8*3 = 32
1228 }
1229}
1230
1231TEST_CASE("Global Data Scaling") {
1232 Context ctx;
1233
1234 SUBCASE("scalePrimitiveData with label") {
1235 uint p1 = ctx.addPatch();
1236 uint p2 = ctx.addPatch();
1237 uint p3 = ctx.addPatch();
1238
1239 // Set data on primitives
1240 ctx.setPrimitiveData(p1, "scale_me", 10.0f);
1241 ctx.setPrimitiveData(p2, "scale_me", 20.0f);
1242 ctx.setPrimitiveData(p3, "other_data", 5.0f);
1243
1244 // Scale all primitives with "scale_me" label
1245 capture_cerr cerr_buffer;
1246 ctx.scalePrimitiveData("scale_me", 2.0f);
1247
1248 // Check scaled values
1249 float val1, val2, val3;
1250 ctx.getPrimitiveData(p1, "scale_me", val1);
1251 ctx.getPrimitiveData(p2, "scale_me", val2);
1252 ctx.getPrimitiveData(p3, "other_data", val3);
1253
1254 DOCTEST_CHECK(val1 == doctest::Approx(20.0f));
1255 DOCTEST_CHECK(val2 == doctest::Approx(40.0f));
1256 DOCTEST_CHECK(val3 == doctest::Approx(5.0f)); // Should be unchanged
1257
1258 // Test with non-existent label
1259 ctx.scalePrimitiveData("non_existent", 3.0f);
1260 DOCTEST_CHECK(cerr_buffer.has_output());
1261 }
1262}
1263
1264TEST_CASE("Aggregate Function Edge Cases") {
1265 Context ctx;
1266
1267 SUBCASE("aggregatePrimitiveDataProduct edge cases") {
1268 uint p1 = ctx.addPatch();
1269 uint p2 = ctx.addPatch();
1270 uint p3 = ctx.addPatch();
1271
1272 // Test with zeros
1273 ctx.setPrimitiveData(p1, "zero_test", 0.0f);
1274 ctx.setPrimitiveData(p1, "normal", 5.0f);
1275 ctx.setPrimitiveData(p2, "zero_test", 10.0f);
1276 ctx.setPrimitiveData(p2, "normal", 2.0f);
1277
1278 std::vector<uint> uuids = {p1, p2};
1279 std::vector<std::string> labels = {"zero_test", "normal"};
1280
1281 ctx.aggregatePrimitiveDataProduct(uuids, labels, "product_result");
1282
1283 float result;
1284 ctx.getPrimitiveData(p1, "product_result", result);
1285 DOCTEST_CHECK(result == doctest::Approx(0.0f)); // 0 * 5 = 0
1286
1287 ctx.getPrimitiveData(p2, "product_result", result);
1288 DOCTEST_CHECK(result == doctest::Approx(20.0f)); // 10 * 2 = 20
1289
1290 // Test with negative values
1291 ctx.setPrimitiveData(p1, "neg1", -2.0f);
1292 ctx.setPrimitiveData(p1, "neg2", -3.0f);
1293 ctx.setPrimitiveData(p2, "neg1", 4.0f);
1294 ctx.setPrimitiveData(p2, "neg2", -1.0f);
1295
1296 std::vector<std::string> neg_labels = {"neg1", "neg2"};
1297 ctx.aggregatePrimitiveDataProduct(uuids, neg_labels, "neg_product");
1298
1299 ctx.getPrimitiveData(p1, "neg_product", result);
1300 DOCTEST_CHECK(result == doctest::Approx(6.0f)); // (-2) * (-3) = 6
1301
1302 ctx.getPrimitiveData(p2, "neg_product", result);
1303 DOCTEST_CHECK(result == doctest::Approx(-4.0f)); // 4 * (-1) = -4
1304 }
1305}
1306
1307TEST_CASE("Data Type Coverage Extensions") {
1308 Context ctx;
1309
1310 SUBCASE("Primitive data with int2, int3, int4 vectors") {
1311 uint p = ctx.addPatch();
1312
1313 // Test vector of int2, int3, int4
1314 std::vector<int2> v_i2 = {make_int2(1, 2), make_int2(3, 4)};
1315 std::vector<int3> v_i3 = {make_int3(1, 2, 3), make_int3(4, 5, 6)};
1316 std::vector<int4> v_i4 = {make_int4(1, 2, 3, 4), make_int4(5, 6, 7, 8)};
1317
1318 ctx.setPrimitiveData(p, "vec_int2", v_i2);
1319 ctx.setPrimitiveData(p, "vec_int3", v_i3);
1320 ctx.setPrimitiveData(p, "vec_int4", v_i4);
1321
1322 DOCTEST_CHECK(ctx.getPrimitiveDataType("vec_int2") == HELIOS_TYPE_INT2);
1323 DOCTEST_CHECK(ctx.getPrimitiveDataType("vec_int3") == HELIOS_TYPE_INT3);
1324 DOCTEST_CHECK(ctx.getPrimitiveDataType("vec_int4") == HELIOS_TYPE_INT4);
1325
1326 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec_int2") == 2);
1327 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec_int3") == 2);
1328 DOCTEST_CHECK(ctx.getPrimitiveDataSize(p, "vec_int4") == 2);
1329
1330 // Test retrieval
1331 std::vector<int2> r_i2;
1332 std::vector<int3> r_i3;
1333 std::vector<int4> r_i4;
1334
1335 ctx.getPrimitiveData(p, "vec_int2", r_i2);
1336 ctx.getPrimitiveData(p, "vec_int3", r_i3);
1337 ctx.getPrimitiveData(p, "vec_int4", r_i4);
1338
1339 DOCTEST_CHECK(r_i2 == v_i2);
1340 DOCTEST_CHECK(r_i3 == v_i3);
1341 DOCTEST_CHECK(r_i4 == v_i4);
1342 }
1343}
1344
1345TEST_CASE("Error Handling Edge Cases") {
1346 Context ctx;
1347
1348 SUBCASE("Data operations with mixed primitive types") {
1349 uint patch = ctx.addPatch();
1350 uint triangle = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0));
1351 uint voxel = ctx.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1352
1353 // Set data on different primitive types
1354 ctx.setPrimitiveData(patch, "mixed_data", 1.0f);
1355 ctx.setPrimitiveData(triangle, "mixed_data", 2.0f);
1356 ctx.setPrimitiveData(voxel, "mixed_data", 3.0f);
1357
1358 std::vector<uint> mixed_prims = {patch, triangle, voxel};
1359
1360 // Test calculations work with mixed primitive types
1361 float mean_val;
1362 ctx.calculatePrimitiveDataMean(mixed_prims, "mixed_data", mean_val);
1363 DOCTEST_CHECK(mean_val == doctest::Approx(2.0f));
1364
1365 float sum_val;
1366 ctx.calculatePrimitiveDataSum(mixed_prims, "mixed_data", sum_val);
1367 DOCTEST_CHECK(sum_val == doctest::Approx(6.0f));
1368 }
1369
1370 SUBCASE("Large dataset stress test") {
1371 std::vector<uint> large_dataset;
1372
1373 // Create many primitives with data
1374 for (int i = 0; i < 1000; ++i) {
1375 uint p = ctx.addPatch();
1376 ctx.setPrimitiveData(p, "stress_data", (float) i);
1377 large_dataset.push_back(p);
1378 }
1379
1380 // Test operations on large dataset
1381 float mean_val, sum_val;
1382 ctx.calculatePrimitiveDataMean(large_dataset, "stress_data", mean_val);
1383 ctx.calculatePrimitiveDataSum(large_dataset, "stress_data", sum_val);
1384
1385 DOCTEST_CHECK(mean_val == doctest::Approx(499.5f)); // Mean of 0-999
1386 DOCTEST_CHECK(sum_val == doctest::Approx(499500.0f)); // Sum of 0-999
1387
1388 // Test filtering on large dataset
1389 auto filtered = ctx.filterPrimitivesByData(large_dataset, "stress_data", 500.0f, ">");
1390 DOCTEST_CHECK(filtered.size() == 499); // Values 501-999
1391 }
1392}
1393
1394TEST_CASE("General Error Handling") {
1395 Context ctx;
1396 uint tri = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0), RGB::green);
1397 capture_cerr cerr_buffer;
1398 vec3 center;
1399#ifdef HELIOS_DEBUG
1400 DOCTEST_CHECK_THROWS_AS(center = ctx.getPatchCenter(tri), std::runtime_error);
1401#endif
1402
1403 uint vox = ctx.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1404 std::vector<uint> vlist{vox};
1405 DOCTEST_CHECK_THROWS_AS(ctx.rotatePrimitive(vlist, PI_F / 4.f, "a"), std::runtime_error);
1406}
1407
1408TEST_CASE("Data Type Consistency and Caching") {
1409 Context ctx;
1410
1411 SUBCASE("Primitive data type caching") {
1412 // Initially, registry should be empty
1413 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataType("test_label"));
1414
1415 // Create some primitives and set data
1416 uint patch1 = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
1417 uint patch2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 1));
1418
1419 // Set data with consistent type
1420 ctx.setPrimitiveData(patch1, "temperature", 25.5f);
1421 ctx.setPrimitiveData(patch2, "temperature", 30.2f);
1422
1423 // Check cached type lookup
1424 DOCTEST_CHECK(ctx.getPrimitiveDataType("temperature") == HELIOS_TYPE_FLOAT);
1425
1426 // Set data with different label
1427 ctx.setPrimitiveData(patch1, "leaf_id", 123);
1428 DOCTEST_CHECK(ctx.getPrimitiveDataType("leaf_id") == HELIOS_TYPE_INT);
1429
1430 // Non-existent label should throw an exception
1431 DOCTEST_CHECK_THROWS(ctx.getPrimitiveDataType("nonexistent"));
1432 }
1433
1434 SUBCASE("Object data type caching") {
1435 // Initially, registry should be empty
1436 DOCTEST_CHECK_THROWS(ctx.getObjectDataType("test_obj_label"));
1437
1438 // Create objects and set data
1439 uint box1 = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1440 uint box2 = ctx.addBoxObject(make_vec3(2, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1441
1442 // Set data with consistent type
1443 ctx.setObjectData(box1, "volume", 1.0);
1444 ctx.setObjectData(box2, "volume", 2.5);
1445
1446 // Check cached type lookup
1447 DOCTEST_CHECK(ctx.getObjectDataType("volume") == HELIOS_TYPE_DOUBLE);
1448
1449 // Set data with different label
1450 ctx.setObjectData(box1, "material_id", static_cast<uint>(42));
1451 DOCTEST_CHECK(ctx.getObjectDataType("material_id") == HELIOS_TYPE_UINT);
1452
1453 // Non-existent label should throw an exception
1454 DOCTEST_CHECK_THROWS(ctx.getObjectDataType("nonexistent"));
1455 }
1456
1457 SUBCASE("Type consistency enforcement - compatible numeric casting") {
1458 uint patch = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
1459
1460 // Set initial data as float
1461 ctx.setPrimitiveData(patch, "value", 10.5f);
1462 DOCTEST_CHECK(ctx.getPrimitiveDataType("value") == HELIOS_TYPE_FLOAT);
1463
1464 // Create another primitive and try to set compatible numeric type
1465 uint patch2 = ctx.addPatch(make_vec3(1, 0, 0), make_vec2(1, 1));
1466
1467 // This should generate a warning but work for compatible numeric types
1468 capture_cerr cerr_buffer;
1469 ctx.setPrimitiveData(patch2, "value", 20); // int to float should work with warning
1470
1471 // Check that both primitives have the data and it's stored correctly
1472 float val1, val2;
1473 ctx.getPrimitiveData(patch, "value", val1);
1474 ctx.getPrimitiveData(patch2, "value", val2);
1475 DOCTEST_CHECK(val1 == doctest::Approx(10.5f));
1476 DOCTEST_CHECK(val2 == doctest::Approx(20.0f));
1477
1478 // Should have generated a warning about type casting (captured by cerr_buffer)
1479 DOCTEST_CHECK(cerr_buffer.get_captured_output().find("WARNING") != std::string::npos);
1480 DOCTEST_CHECK(cerr_buffer.get_captured_output().find("Type casting") != std::string::npos);
1481 }
1482
1483 SUBCASE("Type consistency enforcement - incompatible types") {
1484 uint box = ctx.addBoxObject(make_vec3(0, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1485
1486 // Set initial data as string
1487 ctx.setObjectData(box, "material", std::string("wood"));
1488 DOCTEST_CHECK(ctx.getObjectDataType("material") == HELIOS_TYPE_STRING);
1489
1490 // Create another object and try to set incompatible type
1491 uint box2 = ctx.addBoxObject(make_vec3(2, 0, 0), make_vec3(1, 1, 1), make_int3(1, 1, 1));
1492
1493 // This should throw an error because string and float are incompatible
1494 DOCTEST_CHECK_THROWS_AS(ctx.setObjectData(box2, "material", 3.14f), std::runtime_error);
1495 }
1496
1497 SUBCASE("Multiple data labels per primitive/object") {
1498 uint tri = ctx.addTriangle(make_vec3(0, 0, 0), make_vec3(1, 0, 0), make_vec3(0, 1, 0));
1499
1500 // Set multiple different data types
1501 ctx.setPrimitiveData(tri, "temperature", 25.0f);
1502 ctx.setPrimitiveData(tri, "id", 100);
1503 ctx.setPrimitiveData(tri, "name", std::string("leaf1"));
1504 ctx.setPrimitiveData(tri, "position", make_vec3(0.5f, 0.5f, 0.0f));
1505
1506 // Check all types are correctly cached
1507 DOCTEST_CHECK(ctx.getPrimitiveDataType("temperature") == HELIOS_TYPE_FLOAT);
1508 DOCTEST_CHECK(ctx.getPrimitiveDataType("id") == HELIOS_TYPE_INT);
1509 DOCTEST_CHECK(ctx.getPrimitiveDataType("name") == HELIOS_TYPE_STRING);
1510 DOCTEST_CHECK(ctx.getPrimitiveDataType("position") == HELIOS_TYPE_VEC3);
1511
1512 // Add another primitive with the same labels - should maintain consistency
1513 uint tri2 = ctx.addTriangle(make_vec3(1, 0, 0), make_vec3(2, 0, 0), make_vec3(1, 1, 0));
1514 ctx.setPrimitiveData(tri2, "temperature", 30.0f);
1515 ctx.setPrimitiveData(tri2, "id", 101);
1516 ctx.setPrimitiveData(tri2, "name", std::string("leaf2"));
1517 ctx.setPrimitiveData(tri2, "position", make_vec3(1.5f, 0.5f, 0.0f));
1518
1519 // Verify data can be retrieved correctly
1520 float temp;
1521 int id;
1522 std::string name;
1523 vec3 pos;
1524
1525 ctx.getPrimitiveData(tri2, "temperature", temp);
1526 ctx.getPrimitiveData(tri2, "id", id);
1527 ctx.getPrimitiveData(tri2, "name", name);
1528 ctx.getPrimitiveData(tri2, "position", pos);
1529
1530 DOCTEST_CHECK(temp == doctest::Approx(30.0f));
1531 DOCTEST_CHECK(id == 101);
1532 DOCTEST_CHECK(name == "leaf2");
1533 DOCTEST_CHECK(pos.x == doctest::Approx(1.5f));
1534 }
1535
1536 SUBCASE("Cross-contamination prevention") {
1537 // Primitive and object data registries should be separate
1538 uint patch = ctx.addPatch(make_vec3(0, 0, 0), make_vec2(1, 1));
1539 uint sphere = ctx.addSphereObject(10, make_vec3(0, 0, 0), 1.0f);
1540
1541 // Set same label with different types on primitive vs object
1542 ctx.setPrimitiveData(patch, "value", 42); // int
1543 ctx.setObjectData(sphere, "value", 3.14f); // float
1544
1545 // Registries should track them separately
1546 DOCTEST_CHECK(ctx.getPrimitiveDataType("value") == HELIOS_TYPE_INT);
1547 DOCTEST_CHECK(ctx.getObjectDataType("value") == HELIOS_TYPE_FLOAT);
1548 }
1549
1550 SUBCASE("Vector data type consistency") {
1551 uint vox1 = ctx.addVoxel(make_vec3(0, 0, 0), make_vec3(1, 1, 1));
1552 uint vox2 = ctx.addVoxel(make_vec3(1, 0, 0), make_vec3(1, 1, 1));
1553
1554 // Set vector data
1555 std::vector<float> temps1 = {20.0f, 25.0f, 30.0f};
1556 std::vector<float> temps2 = {22.0f, 27.0f, 32.0f};
1557
1558 ctx.setPrimitiveData(vox1, "temperature_profile", temps1);
1559 ctx.setPrimitiveData(vox2, "temperature_profile", temps2);
1560
1561 // Should register as HELIOS_TYPE_FLOAT (vector types use base type)
1562 DOCTEST_CHECK(ctx.getPrimitiveDataType("temperature_profile") == HELIOS_TYPE_FLOAT);
1563 }
1564}