13TEST_CASE(
"OBJ File Loading - Basic Functionality") {
14 SUBCASE(
"Load simple triangle") {
16 std::vector<uint> UUIDs;
18 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_triangle_simple.obj",
true));
19 DOCTEST_CHECK(UUIDs.size() == 1);
20 DOCTEST_CHECK(ctx.getPrimitiveCount() == 1);
23 DOCTEST_CHECK(ctx.doesPrimitiveExist(UUIDs[0]));
24 DOCTEST_CHECK(ctx.getPrimitiveType(UUIDs[0]) == PRIMITIVE_TYPE_TRIANGLE);
25 DOCTEST_CHECK(ctx.getPrimitiveArea(UUIDs[0]) > 0.0f);
28 std::vector<vec3> vertices = ctx.getPrimitiveVertices(UUIDs[0]);
29 DOCTEST_CHECK(vertices.size() == 3);
30 DOCTEST_CHECK(vertices[0].x == doctest::Approx(0.0f));
31 DOCTEST_CHECK(vertices[0].y == doctest::Approx(0.0f));
32 DOCTEST_CHECK(vertices[0].z == doctest::Approx(0.0f));
33 DOCTEST_CHECK(vertices[1].x == doctest::Approx(1.0f));
34 DOCTEST_CHECK(vertices[1].y == doctest::Approx(0.0f));
35 DOCTEST_CHECK(vertices[1].z == doctest::Approx(0.0f));
36 DOCTEST_CHECK(vertices[2].x == doctest::Approx(0.5f));
37 DOCTEST_CHECK(vertices[2].y == doctest::Approx(1.0f));
38 DOCTEST_CHECK(vertices[2].z == doctest::Approx(0.0f));
41 SUBCASE(
"Load cube with materials") {
43 std::vector<uint> UUIDs;
45 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
46 DOCTEST_CHECK(UUIDs.size() == 12);
47 DOCTEST_CHECK(ctx.getPrimitiveCount() == 12);
50 for (
uint uuid: UUIDs) {
51 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
52 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
53 DOCTEST_CHECK(ctx.getPrimitiveArea(uuid) > 0.0f);
57 float total_area = 0.0f;
58 for (
uint uuid: UUIDs) {
59 total_area += ctx.getPrimitiveArea(uuid);
61 DOCTEST_CHECK(total_area == doctest::Approx(24.0f).epsilon(1e-3));
64 SUBCASE(
"Load complex large model") {
66 std::vector<uint> UUIDs;
69 auto start = std::chrono::high_resolution_clock::now();
70 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
71 auto end = std::chrono::high_resolution_clock::now();
74 DOCTEST_CHECK(UUIDs.size() > 5000);
75 DOCTEST_CHECK(ctx.getPrimitiveCount() == UUIDs.size());
78 for (
uint uuid: UUIDs) {
79 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
80 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
84 SUBCASE(
"Load with transformations") {
86 std::vector<uint> UUIDs;
88 vec3 origin =
make_vec3(5.0f, 10.0f, 2.0f);
91 RGBcolor color = RGB::red;
93 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_triangle_simple.obj", origin, height, rotation, color,
"ZUP",
true));
94 DOCTEST_CHECK(UUIDs.size() == 1);
97 std::vector<vec3> vertices = ctx.getPrimitiveVertices(UUIDs[0]);
98 DOCTEST_CHECK(vertices.size() == 3);
101 DOCTEST_CHECK(!(vertices[0].x == 0.0f && vertices[0].y == 0.0f && vertices[0].z == 0.0f));
104 RGBcolor prim_color = ctx.getPrimitiveColor(UUIDs[0]);
105 DOCTEST_CHECK(prim_color.r == doctest::Approx(color.r));
106 DOCTEST_CHECK(prim_color.g == doctest::Approx(color.g));
107 DOCTEST_CHECK(prim_color.b == doctest::Approx(color.b));
110 SUBCASE(
"Load existing test model") {
112 std::vector<uint> UUIDs;
115 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/obj_object_test.obj",
true));
116 DOCTEST_CHECK(UUIDs.size() > 0);
117 DOCTEST_CHECK(ctx.getPrimitiveCount() > 0);
120 for (
uint uuid: UUIDs) {
121 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
122 DOCTEST_CHECK(ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE);
123 DOCTEST_CHECK(ctx.getPrimitiveArea(uuid) > 0.0f);
128TEST_CASE(
"OBJ File Writing - Basic Functionality") {
129 SUBCASE(
"Write simple geometry and reload") {
136 std::vector<uint> original_uuids = {tri1, tri2};
139 std::string output_file =
"lib/models/test_output_simple.obj";
140 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), original_uuids,
true));
143 std::ifstream file_check(output_file);
144 DOCTEST_CHECK(file_check.good());
149 std::vector<uint> loaded_uuids;
150 DOCTEST_CHECK_NOTHROW(loaded_uuids = ctx2.loadOBJ(output_file.c_str(),
true));
152 DOCTEST_CHECK(loaded_uuids.size() == 2);
153 DOCTEST_CHECK(ctx2.getPrimitiveCount() == 2);
156 for (
size_t i = 0; i < loaded_uuids.size(); i++) {
157 std::vector<vec3> orig_verts = ctx.getPrimitiveVertices(original_uuids[i]);
158 std::vector<vec3> loaded_verts = ctx2.getPrimitiveVertices(loaded_uuids[i]);
160 DOCTEST_CHECK(orig_verts.size() == loaded_verts.size());
161 for (
size_t j = 0; j < orig_verts.size(); j++) {
162 DOCTEST_CHECK(orig_verts[j].x == doctest::Approx(loaded_verts[j].x));
163 DOCTEST_CHECK(orig_verts[j].y == doctest::Approx(loaded_verts[j].y));
164 DOCTEST_CHECK(orig_verts[j].z == doctest::Approx(loaded_verts[j].z));
169 std::remove(output_file.c_str());
170 std::remove(
"lib/models/test_output_simple.mtl");
173 SUBCASE(
"Write all primitives") {
181 std::string output_file =
"lib/models/test_output_all.obj";
182 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str()));
185 std::ifstream file_check(output_file);
186 DOCTEST_CHECK(file_check.good());
189 bool has_vertices =
false;
190 bool has_faces =
false;
191 while (std::getline(file_check, line)) {
192 if (line.substr(0, 2) ==
"v ")
194 if (line.substr(0, 2) ==
"f ")
199 DOCTEST_CHECK(has_vertices);
200 DOCTEST_CHECK(has_faces);
203 std::remove(output_file.c_str());
204 std::remove(
"lib/models/test_output_all.mtl");
208TEST_CASE(
"OBJ File I/O - Error Handling and Edge Cases") {
209 SUBCASE(
"Handle empty OBJ file") {
211 std::vector<uint> UUIDs;
213 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_empty.obj",
true));
214 DOCTEST_CHECK(UUIDs.empty());
215 DOCTEST_CHECK(ctx.getPrimitiveCount() == 0);
218 SUBCASE(
"Handle non-existent file") {
220 std::vector<uint> UUIDs;
222 DOCTEST_CHECK_THROWS(UUIDs = ctx.loadOBJ(
"lib/models/does_not_exist.obj",
true));
225 SUBCASE(
"Handle malformed OBJ file") {
229 DOCTEST_CHECK_THROWS(ctx.loadOBJ(
"lib/models/test_malformed.obj",
true));
232 SUBCASE(
"Handle invalid file extension") {
234 std::vector<uint> UUIDs;
236 DOCTEST_CHECK_THROWS(UUIDs = ctx.loadOBJ(
"lib/models/test_triangle_simple.txt",
true));
239 SUBCASE(
"Write to invalid path") {
244 std::vector<uint> test_uuids = {tri};
245 DOCTEST_CHECK_THROWS(ctx.writeOBJ(
"/invalid/path/test.obj", test_uuids,
true));
248 SUBCASE(
"Write with invalid UUIDs") {
250 std::vector<uint> invalid_uuids = {999, 1000};
253 DOCTEST_CHECK_THROWS(ctx.writeOBJ(
"lib/models/test_invalid_uuid.obj", invalid_uuids,
true));
256 std::remove(
"lib/models/test_invalid_uuid.obj");
257 std::remove(
"lib/models/test_invalid_uuid.mtl");
261TEST_CASE(
"OBJ File Loading - Texture and UV Coordinate Testing") {
262 SUBCASE(
"Load textured model with UV coordinates") {
264 std::vector<uint> UUIDs;
267 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
268 DOCTEST_CHECK(UUIDs.size() > 0);
271 DOCTEST_CHECK(ctx.getPrimitiveCount() > 0);
273 bool found_textured_primitive =
false;
274 int primitive_count = 0;
275 int textured_count = 0;
277 for (
uint uuid: UUIDs) {
279 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
283 DOCTEST_CHECK((type == PRIMITIVE_TYPE_TRIANGLE || type == PRIMITIVE_TYPE_PATCH));
286 std::vector<vec3> vertices = ctx.getPrimitiveVertices(uuid);
287 DOCTEST_CHECK(vertices.size() >= 3);
290 if (type == PRIMITIVE_TYPE_TRIANGLE) {
292 std::string texture_file;
293 DOCTEST_CHECK_NOTHROW(texture_file = ctx.getPrimitiveTextureFile(uuid));
296 if (!texture_file.empty()) {
301 if (!texture_file.empty()) {
302 found_textured_primitive =
true;
305 std::vector<vec2> uv_coords;
306 DOCTEST_CHECK_NOTHROW(uv_coords = ctx.getPrimitiveTextureUV(uuid));
307 DOCTEST_CHECK(uv_coords.size() == 3);
310 for (
const auto &uv: uv_coords) {
311 DOCTEST_CHECK(std::isfinite(uv.x));
312 DOCTEST_CHECK(std::isfinite(uv.y));
317 bool has_transparency, color_overridden;
318 DOCTEST_CHECK_NOTHROW(has_transparency = ctx.primitiveTextureHasTransparencyChannel(uuid));
319 DOCTEST_CHECK_NOTHROW(color_overridden = ctx.isPrimitiveTextureColorOverridden(uuid));
322 DOCTEST_CHECK((has_transparency ==
true || has_transparency ==
false));
323 DOCTEST_CHECK((color_overridden ==
true || color_overridden ==
false));
325 }
else if (type == PRIMITIVE_TYPE_PATCH) {
327 std::string texture_file;
328 DOCTEST_CHECK_NOTHROW(texture_file = ctx.getPrimitiveTextureFile(uuid));
330 if (!texture_file.empty()) {
334 if (!texture_file.empty()) {
335 found_textured_primitive =
true;
338 std::vector<vec2> uv_coords;
339 DOCTEST_CHECK_NOTHROW(uv_coords = ctx.getPrimitiveTextureUV(uuid));
340 DOCTEST_CHECK(uv_coords.size() == 4);
343 for (
const auto &uv: uv_coords) {
344 DOCTEST_CHECK(std::isfinite(uv.x));
345 DOCTEST_CHECK(std::isfinite(uv.y));
352 DOCTEST_CHECK_NOTHROW(color = ctx.getPrimitiveColor(uuid));
353 DOCTEST_CHECK(std::isfinite(color.r));
354 DOCTEST_CHECK(std::isfinite(color.g));
355 DOCTEST_CHECK(std::isfinite(color.b));
362 SUBCASE(
"Load model with material properties") {
364 std::vector<uint> UUIDs;
367 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
368 DOCTEST_CHECK(UUIDs.size() > 0);
371 std::vector<RGBcolor> colors_found;
373 for (
uint uuid: UUIDs) {
374 RGBcolor color = ctx.getPrimitiveColor(uuid);
375 colors_found.push_back(color);
378 DOCTEST_CHECK(color.r >= 0.0f);
379 DOCTEST_CHECK(color.r <= 1.0f);
380 DOCTEST_CHECK(color.g >= 0.0f);
381 DOCTEST_CHECK(color.g <= 1.0f);
382 DOCTEST_CHECK(color.b >= 0.0f);
383 DOCTEST_CHECK(color.b <= 1.0f);
387 DOCTEST_CHECK(colors_found.size() >= 2);
388 if (colors_found.size() >= 2) {
390 bool found_different =
false;
391 for (
size_t i = 1; i < colors_found.size(); i++) {
392 if (colors_found[0].r != colors_found[i].r || colors_found[0].g != colors_found[i].g || colors_found[0].b != colors_found[i].b) {
393 found_different =
true;
397 DOCTEST_CHECK(found_different);
401 SUBCASE(
"Texture coordinate consistency with transformations") {
403 std::vector<uint> UUIDs;
406 vec3 origin =
make_vec3(5.0f, 0.0f, 0.0f);
409 RGBcolor color = RGB::blue;
411 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/obj_object_test.obj", origin, height, rotation, color,
"ZUP",
true));
412 DOCTEST_CHECK(UUIDs.size() > 0);
414 for (
uint uuid: UUIDs) {
415 if (ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE) {
416 std::string texture_file = ctx.getPrimitiveTextureFile(uuid);
418 if (!texture_file.empty()) {
420 std::vector<vec2> uv_coords = ctx.getPrimitiveTextureUV(uuid);
421 DOCTEST_CHECK(uv_coords.size() == 3);
423 for (
const auto &uv: uv_coords) {
424 DOCTEST_CHECK(std::isfinite(uv.x));
425 DOCTEST_CHECK(std::isfinite(uv.y));
429 std::vector<vec3> vertices = ctx.getPrimitiveVertices(uuid);
430 DOCTEST_CHECK(vertices.size() == 3);
433 bool found_transformed =
false;
434 for (
const auto &vertex: vertices) {
435 if (vertex.magnitude() > 1.0f) {
436 found_transformed =
true;
440 DOCTEST_CHECK(found_transformed);
446 SUBCASE(
"Missing texture file handling") {
448 std::vector<uint> UUIDs;
451 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
452 DOCTEST_CHECK(UUIDs.size() > 0);
455 for (
uint uuid: UUIDs) {
456 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
459 std::vector<vec3> vertices = ctx.getPrimitiveVertices(uuid);
460 DOCTEST_CHECK(vertices.size() >= 3);
461 DOCTEST_CHECK(ctx.getPrimitiveArea(uuid) > 0.0f);
466TEST_CASE(
"OBJ File Loading - Error Handling and Edge Cases") {
467 SUBCASE(
"Invalid face vertex indices") {
471 std::string test_content =
"# Test OBJ with invalid face indices\n"
477 std::string test_file =
"lib/models/test_invalid_face.obj";
478 std::ofstream file(test_file);
479 file << test_content;
483 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
486 std::remove(test_file.c_str());
489 SUBCASE(
"Invalid texture coordinate indices") {
493 std::string test_content =
"# Test OBJ with invalid UV indices\n"
501 std::string test_file =
"lib/models/test_invalid_uv.obj";
502 std::ofstream file(test_file);
503 file << test_content;
507 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
510 std::remove(test_file.c_str());
513 SUBCASE(
"Missing texture file") {
517 std::string obj_content =
"# Test OBJ with missing texture\n"
518 "mtllib test_missing_texture.mtl\n"
522 "usemtl test_material\n"
525 std::string mtl_content =
"newmtl test_material\n"
528 "map_Kd nonexistent_texture.jpg\n";
530 std::string obj_file =
"lib/models/test_missing_texture.obj";
531 std::string mtl_file =
"lib/models/test_missing_texture.mtl";
533 std::ofstream obj_f(obj_file);
534 obj_f << obj_content;
537 std::ofstream mtl_f(mtl_file);
538 mtl_f << mtl_content;
542 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(obj_file.c_str(),
true), std::runtime_error);
545 std::remove(obj_file.c_str());
546 std::remove(mtl_file.c_str());
549 SUBCASE(
"Texture without UV coordinates") {
553 std::string texture_file =
"lib/models/test_dummy.jpg";
554 std::ofstream tex_f(texture_file);
559 std::string obj_content =
"# Test OBJ with texture but no UV coordinates\n"
560 "mtllib test_no_uv.mtl\n"
564 "usemtl textured_material\n"
567 std::string mtl_content =
"newmtl textured_material\n"
570 "map_Kd test_dummy.jpg\n";
572 std::string obj_file =
"lib/models/test_no_uv.obj";
573 std::string mtl_file =
"lib/models/test_no_uv.mtl";
575 std::ofstream obj_f(obj_file);
576 obj_f << obj_content;
579 std::ofstream mtl_f(mtl_file);
580 mtl_f << mtl_content;
584 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(obj_file.c_str(),
true), std::runtime_error);
587 std::remove(obj_file.c_str());
588 std::remove(mtl_file.c_str());
589 std::remove(texture_file.c_str());
592 SUBCASE(
"Zero vertex file") {
596 std::string test_content =
"# Test OBJ with no vertices\n"
599 std::string test_file =
"lib/models/test_zero_vertex.obj";
600 std::ofstream file(test_file);
601 file << test_content;
605 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
608 std::remove(test_file.c_str());
612TEST_CASE(
"OBJ File I/O - Performance Benchmarking") {
613 SUBCASE(
"Loading performance baseline") {
615 std::vector<uint> UUIDs;
618 auto start = std::chrono::high_resolution_clock::now();
619 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
620 auto end = std::chrono::high_resolution_clock::now();
622 auto load_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
624 DOCTEST_CHECK(UUIDs.size() > 0);
627 float triangles_per_second = UUIDs.size() * 1000000.0f / load_duration.count();
628 float load_time_ms = load_duration.count() / 1000.0f;
632 DOCTEST_CHECK(triangles_per_second > 1000.0f);
635 SUBCASE(
"Loading performance with transformations") {
637 std::vector<uint> UUIDs;
640 vec3 origin =
make_vec3(10.0f, 5.0f, 0.0f);
643 RGBcolor color = RGB::green;
645 auto start = std::chrono::high_resolution_clock::now();
646 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj", origin, height, rotation, color,
"ZUP",
true));
647 auto end = std::chrono::high_resolution_clock::now();
649 auto load_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
651 DOCTEST_CHECK(UUIDs.size() > 0);
653 float triangles_per_second = UUIDs.size() * 1000000.0f / load_duration.count();
654 float load_time_ms = load_duration.count() / 1000.0f;
657 DOCTEST_CHECK(triangles_per_second > 800.0f);
660 SUBCASE(
"Writing performance baseline") {
664 std::vector<uint> uuids;
665 for (
int i = 0; i < 1000; i++) {
666 float x =
static_cast<float>(i % 10);
667 float z =
static_cast<float>(i / 10);
669 uuids.push_back(tri);
673 std::string output_file =
"lib/models/test_perf_output.obj";
674 auto start = std::chrono::high_resolution_clock::now();
675 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
676 auto end = std::chrono::high_resolution_clock::now();
678 auto write_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
681 float triangles_per_second = uuids.size() * 1000000.0f / write_duration.count();
682 DOCTEST_CHECK(triangles_per_second > 500.0f);
685 std::remove(output_file.c_str());
686 std::remove(
"lib/models/test_perf_output.mtl");
690TEST_CASE(
"OBJ File I/O - Correctness Validation") {
691 SUBCASE(
"Round-trip geometry preservation") {
692 Context ctx1, ctx2, ctx3;
695 std::vector<uint> original_uuids;
696 DOCTEST_CHECK_NOTHROW(original_uuids = ctx1.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
699 std::string intermediate_file =
"lib/models/test_roundtrip.obj";
700 DOCTEST_CHECK_NOTHROW(ctx1.writeOBJ(intermediate_file.c_str(), original_uuids,
true));
703 std::vector<uint> roundtrip_uuids;
704 DOCTEST_CHECK_NOTHROW(roundtrip_uuids = ctx2.loadOBJ(intermediate_file.c_str(),
true));
707 std::string final_file =
"lib/models/test_roundtrip2.obj";
708 DOCTEST_CHECK_NOTHROW(ctx2.writeOBJ(final_file.c_str(), roundtrip_uuids,
true));
711 std::vector<uint> final_uuids;
712 DOCTEST_CHECK_NOTHROW(final_uuids = ctx3.loadOBJ(final_file.c_str(),
true));
715 DOCTEST_CHECK(original_uuids.size() == roundtrip_uuids.size());
716 DOCTEST_CHECK(roundtrip_uuids.size() == final_uuids.size());
719 float original_area = 0, roundtrip_area = 0, final_area = 0;
721 for (
uint uuid: original_uuids) {
722 original_area += ctx1.getPrimitiveArea(uuid);
724 for (
uint uuid: roundtrip_uuids) {
725 roundtrip_area += ctx2.getPrimitiveArea(uuid);
727 for (
uint uuid: final_uuids) {
728 final_area += ctx3.getPrimitiveArea(uuid);
731 DOCTEST_CHECK(original_area == doctest::Approx(roundtrip_area).epsilon(1e-4));
732 DOCTEST_CHECK(roundtrip_area == doctest::Approx(final_area).epsilon(1e-4));
735 std::remove(intermediate_file.c_str());
736 std::remove(
"lib/models/test_roundtrip.mtl");
737 std::remove(final_file.c_str());
738 std::remove(
"lib/models/test_roundtrip2.mtl");
741 SUBCASE(
"Material preservation") {
745 std::vector<uint> original_uuids;
746 DOCTEST_CHECK_NOTHROW(original_uuids = ctx1.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
749 std::string output_file =
"lib/models/test_material_preservation.obj";
750 DOCTEST_CHECK_NOTHROW(ctx1.writeOBJ(output_file.c_str(), original_uuids,
true));
752 std::vector<uint> reloaded_uuids;
753 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx2.loadOBJ(output_file.c_str(),
true));
755 DOCTEST_CHECK(original_uuids.size() == reloaded_uuids.size());
758 for (
size_t i = 0; i < std::min(original_uuids.size(), reloaded_uuids.size()); i++) {
759 RGBcolor orig_color = ctx1.getPrimitiveColor(original_uuids[i]);
760 RGBcolor new_color = ctx2.getPrimitiveColor(reloaded_uuids[i]);
763 float orig_brightness = orig_color.r + orig_color.g + orig_color.b;
764 float new_brightness = new_color.r + new_color.g + new_color.b;
765 DOCTEST_CHECK((orig_brightness > 0.01f || new_brightness > 0.01f));
769 std::remove(output_file.c_str());
770 std::remove(
"lib/models/test_material_preservation.mtl");
774TEST_CASE(
"OBJ WriteOBJ - Comprehensive Test Suite for Optimization") {
777 auto createTestDataset = [](Context &ctx,
const std::string &type,
int count) -> std::vector<uint> {
778 std::vector<uint> uuids;
780 if (type ==
"simple_triangles") {
781 for (
int i = 0; i < count; i++) {
782 float x =
static_cast<float>(i % 10);
783 float z =
static_cast<float>(i / 10);
784 uint tri = ctx.addTriangle(
make_vec3(x, 0, z),
make_vec3(x + 1, 0, z),
make_vec3(x + 0.5f, 1, z),
make_RGBcolor(0.5f + 0.5f * (i % 3 == 0), 0.5f + 0.5f * (i % 3 == 1), 0.5f + 0.5f * (i % 3 == 2)));
785 uuids.push_back(tri);
787 }
else if (type ==
"mixed_primitives") {
788 for (
int i = 0; i < count; i++) {
789 float x =
static_cast<float>(i % 10);
790 float z =
static_cast<float>(i / 10);
793 uuids.push_back(tri);
796 uuids.push_back(patch);
799 }
else if (type ==
"textured_primitives") {
802 for (
int i = 0; i < count; i++) {
803 float x =
static_cast<float>(i % 10);
804 float z =
static_cast<float>(i / 10);
806 RGBcolor color =
make_RGBcolor(0.3f + 0.4f * (i % 3 == 0), 0.3f + 0.4f * (i % 3 == 1), 0.3f + 0.4f * (i % 3 == 2));
808 uuids.push_back(tri);
810 }
else if (type ==
"multi_material") {
812 for (
int i = 0; i < count; i++) {
813 float x =
static_cast<float>(i % 10);
814 float z =
static_cast<float>(i / 10);
817 int material_type = i % 4;
820 switch (material_type) {
831 prim = ctx.addTriangle(
make_vec3(x, 0, z),
make_vec3(x + 1, 0, z),
make_vec3(x + 0.5f, 1, z),
make_RGBcolor(0.8f, 0.4f, 0.6f));
834 uuids.push_back(prim);
836 }
else if (type ==
"object_groups") {
837 for (
int i = 0; i < count; i++) {
838 float x =
static_cast<float>(i % 10);
839 float z =
static_cast<float>(i / 10);
843 std::string object_label =
"group_" + std::to_string(i / 25);
844 ctx.setPrimitiveData(tri,
"object_label", object_label);
845 uuids.push_back(tri);
852 SUBCASE(
"Small dataset validation") {
854 std::vector<uint> uuids = createTestDataset(ctx,
"simple_triangles", 50);
856 std::string output_file =
"test_writeobj_small.obj";
857 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
861 std::vector<uint> reloaded_uuids;
862 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
864 DOCTEST_CHECK(uuids.size() == reloaded_uuids.size());
867 std::remove(output_file.c_str());
868 std::remove(
"test_writeobj_small.mtl");
871 SUBCASE(
"Mixed primitives validation") {
873 std::vector<uint> uuids = createTestDataset(ctx,
"mixed_primitives", 100);
875 std::string output_file =
"test_writeobj_mixed.obj";
876 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
880 std::vector<uint> reloaded_uuids;
881 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
884 DOCTEST_CHECK(reloaded_uuids.size() >= uuids.size());
887 std::remove(output_file.c_str());
888 std::remove(
"test_writeobj_mixed.mtl");
891 SUBCASE(
"Textured primitives validation") {
893 std::vector<uint> uuids = createTestDataset(ctx,
"textured_primitives", 75);
895 std::string output_file =
"test_writeobj_textured.obj";
896 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
900 std::vector<uint> reloaded_uuids;
901 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
903 DOCTEST_CHECK(uuids.size() == reloaded_uuids.size());
906 std::remove(output_file.c_str());
907 std::remove(
"test_writeobj_textured.mtl");
911 SUBCASE(
"Multi-material validation") {
913 std::vector<uint> uuids = createTestDataset(ctx,
"multi_material", 200);
915 std::string output_file =
"test_writeobj_materials.obj";
916 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
true));
919 std::string mtl_file =
"test_writeobj_materials.mtl";
920 std::ifstream mtl_check(mtl_file);
921 DOCTEST_CHECK(mtl_check.good());
923 int material_count = 0;
925 while (std::getline(mtl_check, line)) {
926 if (line.substr(0, 6) ==
"newmtl") {
932 DOCTEST_CHECK(material_count >= 4);
935 std::remove(output_file.c_str());
936 std::remove(mtl_file.c_str());
939 SUBCASE(
"Object groups validation") {
941 std::vector<uint> uuids = createTestDataset(ctx,
"object_groups", 100);
943 std::string output_file =
"test_writeobj_groups.obj";
944 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
true));
947 std::ifstream obj_check(output_file);
948 DOCTEST_CHECK(obj_check.good());
950 int object_count = 0;
952 while (std::getline(obj_check, line)) {
953 if (line.substr(0, 2) ==
"o ") {
959 DOCTEST_CHECK(object_count >= 4);
962 std::remove(output_file.c_str());
963 std::remove(
"test_writeobj_groups.mtl");
967TEST_CASE(
"OBJ WriteOBJ - Performance Benchmarking Suite") {
970 auto benchmarkWriteOBJ = [](Context &ctx,
const std::vector<uint> &uuids,
const std::string &test_name) {
971 std::string output_file =
"bench_" + test_name +
".obj";
973 auto start = std::chrono::high_resolution_clock::now();
974 ctx.writeOBJ(output_file.c_str(), uuids);
975 auto end = std::chrono::high_resolution_clock::now();
977 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
978 float write_time_ms = duration.count() / 1000.0f;
979 float primitives_per_second = uuids.size() * 1000000.0f / duration.count();
982 std::remove(output_file.c_str());
983 std::string mtl_file =
"bench_" + test_name +
".mtl";
984 std::remove(mtl_file.c_str());
986 return std::make_pair(write_time_ms, primitives_per_second);
989 SUBCASE(
"Baseline performance - 1000 triangles") {
991 std::vector<uint> uuids;
993 for (
int i = 0; i < 1000; i++) {
994 float x =
static_cast<float>(i % 10);
995 float z =
static_cast<float>(i / 10);
997 uuids.push_back(tri);
1000 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"baseline_1k");
1003 DOCTEST_CHECK(prims_per_sec > 100.0f);
1006 SUBCASE(
"Multi-material performance - 2000 primitives") {
1008 std::vector<uint> uuids;
1010 for (
int i = 0; i < 2000; i++) {
1011 float x =
static_cast<float>(i % 20);
1012 float z =
static_cast<float>(i / 20);
1015 RGBcolor color =
make_RGBcolor((i % 10) / 10.0f, 0.5f, 0.7f);
1017 uuids.push_back(tri);
1020 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"multi_material_2k");
1023 DOCTEST_CHECK(prims_per_sec > 50.0f);
1026 SUBCASE(
"Large dataset performance - 5000 primitives") {
1028 std::vector<uint> uuids;
1030 for (
int i = 0; i < 5000; i++) {
1031 float x =
static_cast<float>(i % 50);
1032 float z =
static_cast<float>(i / 50);
1034 uuids.push_back(tri);
1037 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"large_5k");
1040 DOCTEST_CHECK(prims_per_sec > 25.0f);
1043 SUBCASE(
"Memory usage monitoring") {
1045 std::vector<uint> uuids;
1048 for (
int i = 0; i < 3000; i++) {
1049 float x =
static_cast<float>(i % 30);
1050 float z =
static_cast<float>(i / 30);
1052 uuids.push_back(tri);
1055 std::string output_file =
"bench_memory_test.obj";
1058 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
1061 std::ifstream file_check(output_file, std::ios::ate);
1062 auto file_size = file_check.tellg();
1065 DOCTEST_CHECK(file_size > 0);
1066 DOCTEST_CHECK(file_size < 50 * 1024 * 1024);
1069 std::remove(output_file.c_str());
1070 std::remove(
"bench_memory_test.mtl");
1074TEST_CASE(
"OBJ WriteOBJ - Stress Testing and Edge Cases") {
1076 SUBCASE(
"Very large primitive count") {
1078 std::vector<uint> uuids;
1081 for (
int i = 0; i < 10000; i++) {
1082 float x =
static_cast<float>(i % 100);
1083 float z =
static_cast<float>(i / 100);
1085 uuids.push_back(tri);
1088 std::string output_file =
"stress_large.obj";
1090 auto start = std::chrono::high_resolution_clock::now();
1091 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
1092 auto end = std::chrono::high_resolution_clock::now();
1094 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
1097 DOCTEST_CHECK(duration.count() < 10000);
1100 std::remove(output_file.c_str());
1101 std::remove(
"stress_large.mtl");
1104 SUBCASE(
"Many materials stress test") {
1106 std::vector<uint> uuids;
1109 for (
int i = 0; i < 500; i++) {
1110 float x =
static_cast<float>(i % 25);
1111 float z =
static_cast<float>(i / 25);
1114 float r =
static_cast<float>((i / 5) % 10) / 10.0f;
1115 float g =
static_cast<float>((i / 5) % 10) / 10.0f;
1116 float b =
static_cast<float>((i / 5) / 10) / 10.0f;
1120 uuids.push_back(tri);
1123 std::string output_file =
"stress_materials.obj";
1124 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
1127 std::string mtl_file =
"stress_materials.mtl";
1128 std::ifstream mtl_check(mtl_file);
1129 int material_count = 0;
1131 while (std::getline(mtl_check, line)) {
1132 if (line.substr(0, 6) ==
"newmtl") {
1138 DOCTEST_CHECK(material_count >= 50);
1141 std::remove(output_file.c_str());
1142 std::remove(mtl_file.c_str());
1145 SUBCASE(
"Degenerate geometry handling") {
1147 std::vector<uint> uuids;
1150 for (
int i = 0; i < 100; i++) {
1151 float x =
static_cast<float>(i % 10);
1152 float z =
static_cast<float>(i / 10);
1154 uuids.push_back(tri);
1158 for (
int i = 0; i < 10; i++) {
1159 float offset = i * 1e-8f;
1161 uuids.push_back(tri);
1164 std::string output_file =
"stress_degenerate.obj";
1165 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids));
1168 std::ifstream file_check(output_file);
1169 DOCTEST_CHECK(file_check.good());
1174 while (std::getline(file_check, line)) {
1179 DOCTEST_CHECK(line_count > 10);
1182 std::remove(output_file.c_str());
1183 std::remove(
"stress_degenerate.mtl");