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,
false,
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(),
true,
true));
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,
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,
false,
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(
"OBJ materials registered in Context material system") {
404 std::vector<uint> UUIDs = ctx.loadOBJ(
"lib/models/test_cube_medium.obj",
true);
405 DOCTEST_CHECK(UUIDs.size() == 12);
408 std::vector<std::string> mat_labels = ctx.listMaterials();
409 DOCTEST_CHECK(mat_labels.size() == 3);
411 DOCTEST_CHECK(ctx.doesMaterialExist(
"red_material"));
412 DOCTEST_CHECK(ctx.doesMaterialExist(
"blue_material"));
413 DOCTEST_CHECK(ctx.doesMaterialExist(
"green_material"));
416 RGBAcolor red_color = ctx.getMaterialColor(
"red_material");
417 DOCTEST_CHECK(red_color.r == doctest::Approx(1.0f));
418 DOCTEST_CHECK(red_color.g == doctest::Approx(0.0f));
419 DOCTEST_CHECK(red_color.b == doctest::Approx(0.0f));
421 RGBAcolor blue_color = ctx.getMaterialColor(
"blue_material");
422 DOCTEST_CHECK(blue_color.r == doctest::Approx(0.0f));
423 DOCTEST_CHECK(blue_color.g == doctest::Approx(0.0f));
424 DOCTEST_CHECK(blue_color.b == doctest::Approx(1.0f));
426 RGBAcolor green_color = ctx.getMaterialColor(
"green_material");
427 DOCTEST_CHECK(green_color.r == doctest::Approx(0.0f));
428 DOCTEST_CHECK(green_color.g == doctest::Approx(1.0f));
429 DOCTEST_CHECK(green_color.b == doctest::Approx(0.0f));
432 for (
uint uuid : UUIDs) {
433 std::string label = ctx.getPrimitiveMaterialLabel(uuid);
434 DOCTEST_CHECK(!label.empty());
435 bool valid_material = (label ==
"red_material" || label ==
"blue_material" || label ==
"green_material");
436 DOCTEST_CHECK(valid_material);
440 int red_count = 0, blue_count = 0, green_count = 0;
441 for (
uint uuid : UUIDs) {
442 std::string label = ctx.getPrimitiveMaterialLabel(uuid);
443 if (label ==
"red_material") red_count++;
444 else if (label ==
"blue_material") blue_count++;
445 else if (label ==
"green_material") green_count++;
447 DOCTEST_CHECK(red_count == 4);
448 DOCTEST_CHECK(blue_count == 4);
449 DOCTEST_CHECK(green_count == 4);
452 SUBCASE(
"Texture coordinate consistency with transformations") {
454 std::vector<uint> UUIDs;
457 vec3 origin =
make_vec3(5.0f, 0.0f, 0.0f);
460 RGBcolor color = RGB::blue;
462 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/obj_object_test.obj", origin, height, rotation, color,
"ZUP",
true));
463 DOCTEST_CHECK(UUIDs.size() > 0);
465 for (
uint uuid: UUIDs) {
466 if (ctx.getPrimitiveType(uuid) == PRIMITIVE_TYPE_TRIANGLE) {
467 std::string texture_file = ctx.getPrimitiveTextureFile(uuid);
469 if (!texture_file.empty()) {
471 std::vector<vec2> uv_coords = ctx.getPrimitiveTextureUV(uuid);
472 DOCTEST_CHECK(uv_coords.size() == 3);
474 for (
const auto &uv: uv_coords) {
475 DOCTEST_CHECK(std::isfinite(uv.x));
476 DOCTEST_CHECK(std::isfinite(uv.y));
480 std::vector<vec3> vertices = ctx.getPrimitiveVertices(uuid);
481 DOCTEST_CHECK(vertices.size() == 3);
484 bool found_transformed =
false;
485 for (
const auto &vertex: vertices) {
486 if (vertex.magnitude() > 1.0f) {
487 found_transformed =
true;
491 DOCTEST_CHECK(found_transformed);
497 SUBCASE(
"Missing texture file handling") {
499 std::vector<uint> UUIDs;
502 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
503 DOCTEST_CHECK(UUIDs.size() > 0);
506 for (
uint uuid: UUIDs) {
507 DOCTEST_CHECK(ctx.doesPrimitiveExist(uuid));
510 std::vector<vec3> vertices = ctx.getPrimitiveVertices(uuid);
511 DOCTEST_CHECK(vertices.size() >= 3);
512 DOCTEST_CHECK(ctx.getPrimitiveArea(uuid) > 0.0f);
517TEST_CASE(
"OBJ File Loading - Error Handling and Edge Cases") {
518 SUBCASE(
"Invalid face vertex indices") {
522 std::string test_content =
"# Test OBJ with invalid face indices\n"
528 std::string test_file =
"lib/models/test_invalid_face.obj";
529 std::ofstream file(test_file);
530 file << test_content;
534 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
537 std::remove(test_file.c_str());
540 SUBCASE(
"Invalid texture coordinate indices") {
544 std::string test_content =
"# Test OBJ with invalid UV indices\n"
552 std::string test_file =
"lib/models/test_invalid_uv.obj";
553 std::ofstream file(test_file);
554 file << test_content;
558 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
561 std::remove(test_file.c_str());
564 SUBCASE(
"Missing texture file") {
568 std::string obj_content =
"# Test OBJ with missing texture\n"
569 "mtllib test_missing_texture.mtl\n"
573 "usemtl test_material\n"
576 std::string mtl_content =
"newmtl test_material\n"
579 "map_Kd nonexistent_texture.jpg\n";
581 std::string obj_file =
"lib/models/test_missing_texture.obj";
582 std::string mtl_file =
"lib/models/test_missing_texture.mtl";
584 std::ofstream obj_f(obj_file);
585 obj_f << obj_content;
588 std::ofstream mtl_f(mtl_file);
589 mtl_f << mtl_content;
593 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(obj_file.c_str(),
true), std::runtime_error);
596 std::remove(obj_file.c_str());
597 std::remove(mtl_file.c_str());
600 SUBCASE(
"Texture without UV coordinates") {
604 std::string texture_file =
"lib/models/test_dummy.jpg";
605 std::ofstream tex_f(texture_file);
610 std::string obj_content =
"# Test OBJ with texture but no UV coordinates\n"
611 "mtllib test_no_uv.mtl\n"
615 "usemtl textured_material\n"
618 std::string mtl_content =
"newmtl textured_material\n"
621 "map_Kd test_dummy.jpg\n";
623 std::string obj_file =
"lib/models/test_no_uv.obj";
624 std::string mtl_file =
"lib/models/test_no_uv.mtl";
626 std::ofstream obj_f(obj_file);
627 obj_f << obj_content;
630 std::ofstream mtl_f(mtl_file);
631 mtl_f << mtl_content;
635 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(obj_file.c_str(),
true), std::runtime_error);
638 std::remove(obj_file.c_str());
639 std::remove(mtl_file.c_str());
640 std::remove(texture_file.c_str());
643 SUBCASE(
"Zero vertex file") {
647 std::string test_content =
"# Test OBJ with no vertices\n"
650 std::string test_file =
"lib/models/test_zero_vertex.obj";
651 std::ofstream file(test_file);
652 file << test_content;
656 DOCTEST_CHECK_THROWS_AS(ctx.loadOBJ(test_file.c_str(),
true), std::runtime_error);
659 std::remove(test_file.c_str());
663TEST_CASE(
"OBJ File I/O - Performance Benchmarking") {
664 SUBCASE(
"Loading performance baseline") {
666 std::vector<uint> UUIDs;
669 auto start = std::chrono::high_resolution_clock::now();
670 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj",
true));
671 auto end = std::chrono::high_resolution_clock::now();
673 auto load_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
675 DOCTEST_CHECK(UUIDs.size() > 0);
678 float triangles_per_second = UUIDs.size() * 1000000.0f / load_duration.count();
679 float load_time_ms = load_duration.count() / 1000.0f;
683 DOCTEST_CHECK(triangles_per_second > 1000.0f);
686 SUBCASE(
"Loading performance with transformations") {
688 std::vector<uint> UUIDs;
691 vec3 origin =
make_vec3(10.0f, 5.0f, 0.0f);
694 RGBcolor color = RGB::green;
696 auto start = std::chrono::high_resolution_clock::now();
697 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_complex_large.obj", origin, height, rotation, color,
"ZUP",
true));
698 auto end = std::chrono::high_resolution_clock::now();
700 auto load_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
702 DOCTEST_CHECK(UUIDs.size() > 0);
704 float triangles_per_second = UUIDs.size() * 1000000.0f / load_duration.count();
705 float load_time_ms = load_duration.count() / 1000.0f;
708 DOCTEST_CHECK(triangles_per_second > 800.0f);
711 SUBCASE(
"Writing performance baseline") {
715 std::vector<uint> uuids;
716 for (
int i = 0; i < 1000; i++) {
717 float x =
static_cast<float>(i % 10);
718 float z =
static_cast<float>(i / 10);
720 uuids.push_back(tri);
724 std::string output_file =
"lib/models/test_perf_output.obj";
725 auto start = std::chrono::high_resolution_clock::now();
726 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
727 auto end = std::chrono::high_resolution_clock::now();
729 auto write_duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
732 float triangles_per_second = uuids.size() * 1000000.0f / write_duration.count();
733 DOCTEST_CHECK(triangles_per_second > 500.0f);
736 std::remove(output_file.c_str());
737 std::remove(
"lib/models/test_perf_output.mtl");
741TEST_CASE(
"OBJ File I/O - Correctness Validation") {
742 SUBCASE(
"Round-trip geometry preservation") {
743 Context ctx1, ctx2, ctx3;
746 std::vector<uint> original_uuids;
747 DOCTEST_CHECK_NOTHROW(original_uuids = ctx1.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
750 std::string intermediate_file =
"lib/models/test_roundtrip.obj";
751 DOCTEST_CHECK_NOTHROW(ctx1.writeOBJ(intermediate_file.c_str(), original_uuids,
false,
true));
754 std::vector<uint> roundtrip_uuids;
755 DOCTEST_CHECK_NOTHROW(roundtrip_uuids = ctx2.loadOBJ(intermediate_file.c_str(),
true));
758 std::string final_file =
"lib/models/test_roundtrip2.obj";
759 DOCTEST_CHECK_NOTHROW(ctx2.writeOBJ(final_file.c_str(), roundtrip_uuids,
false,
true));
762 std::vector<uint> final_uuids;
763 DOCTEST_CHECK_NOTHROW(final_uuids = ctx3.loadOBJ(final_file.c_str(),
true));
766 DOCTEST_CHECK(original_uuids.size() == roundtrip_uuids.size());
767 DOCTEST_CHECK(roundtrip_uuids.size() == final_uuids.size());
770 float original_area = 0, roundtrip_area = 0, final_area = 0;
772 for (
uint uuid: original_uuids) {
773 original_area += ctx1.getPrimitiveArea(uuid);
775 for (
uint uuid: roundtrip_uuids) {
776 roundtrip_area += ctx2.getPrimitiveArea(uuid);
778 for (
uint uuid: final_uuids) {
779 final_area += ctx3.getPrimitiveArea(uuid);
782 DOCTEST_CHECK(original_area == doctest::Approx(roundtrip_area).epsilon(1e-4));
783 DOCTEST_CHECK(roundtrip_area == doctest::Approx(final_area).epsilon(1e-4));
786 std::remove(intermediate_file.c_str());
787 std::remove(
"lib/models/test_roundtrip.mtl");
788 std::remove(final_file.c_str());
789 std::remove(
"lib/models/test_roundtrip2.mtl");
792 SUBCASE(
"Material preservation") {
796 std::vector<uint> original_uuids;
797 DOCTEST_CHECK_NOTHROW(original_uuids = ctx1.loadOBJ(
"lib/models/test_cube_medium.obj",
true));
800 std::string output_file =
"lib/models/test_material_preservation.obj";
801 DOCTEST_CHECK_NOTHROW(ctx1.writeOBJ(output_file.c_str(), original_uuids,
false,
true));
803 std::vector<uint> reloaded_uuids;
804 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx2.loadOBJ(output_file.c_str(),
true));
806 DOCTEST_CHECK(original_uuids.size() == reloaded_uuids.size());
809 for (
size_t i = 0; i < std::min(original_uuids.size(), reloaded_uuids.size()); i++) {
810 RGBcolor orig_color = ctx1.getPrimitiveColor(original_uuids[i]);
811 RGBcolor new_color = ctx2.getPrimitiveColor(reloaded_uuids[i]);
814 float orig_brightness = orig_color.r + orig_color.g + orig_color.b;
815 float new_brightness = new_color.r + new_color.g + new_color.b;
816 DOCTEST_CHECK((orig_brightness > 0.01f || new_brightness > 0.01f));
820 std::remove(output_file.c_str());
821 std::remove(
"lib/models/test_material_preservation.mtl");
825TEST_CASE(
"OBJ WriteOBJ - Comprehensive Test Suite for Optimization") {
828 auto createTestDataset = [](Context &ctx,
const std::string &type,
int count) -> std::vector<uint> {
829 std::vector<uint> uuids;
831 if (type ==
"simple_triangles") {
832 for (
int i = 0; i < count; i++) {
833 float x =
static_cast<float>(i % 10);
834 float z =
static_cast<float>(i / 10);
835 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)));
836 uuids.push_back(tri);
838 }
else if (type ==
"mixed_primitives") {
839 for (
int i = 0; i < count; i++) {
840 float x =
static_cast<float>(i % 10);
841 float z =
static_cast<float>(i / 10);
844 uuids.push_back(tri);
847 uuids.push_back(patch);
850 }
else if (type ==
"textured_primitives") {
853 for (
int i = 0; i < count; i++) {
854 float x =
static_cast<float>(i % 10);
855 float z =
static_cast<float>(i / 10);
857 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));
859 uuids.push_back(tri);
861 }
else if (type ==
"multi_material") {
863 for (
int i = 0; i < count; i++) {
864 float x =
static_cast<float>(i % 10);
865 float z =
static_cast<float>(i / 10);
868 int material_type = i % 4;
871 switch (material_type) {
882 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));
885 uuids.push_back(prim);
887 }
else if (type ==
"object_groups") {
888 for (
int i = 0; i < count; i++) {
889 float x =
static_cast<float>(i % 10);
890 float z =
static_cast<float>(i / 10);
894 std::string object_label =
"group_" + std::to_string(i / 25);
895 ctx.setPrimitiveData(tri,
"object_label", object_label);
896 uuids.push_back(tri);
903 SUBCASE(
"Small dataset validation") {
905 std::vector<uint> uuids = createTestDataset(ctx,
"simple_triangles", 50);
907 std::string output_file =
"test_writeobj_small.obj";
908 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
912 std::vector<uint> reloaded_uuids;
913 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
915 DOCTEST_CHECK(uuids.size() == reloaded_uuids.size());
918 std::remove(output_file.c_str());
919 std::remove(
"test_writeobj_small.mtl");
922 SUBCASE(
"Mixed primitives validation") {
924 std::vector<uint> uuids = createTestDataset(ctx,
"mixed_primitives", 100);
926 std::string output_file =
"test_writeobj_mixed.obj";
927 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
931 std::vector<uint> reloaded_uuids;
932 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
935 DOCTEST_CHECK(reloaded_uuids.size() >= uuids.size());
938 std::remove(output_file.c_str());
939 std::remove(
"test_writeobj_mixed.mtl");
942 SUBCASE(
"Textured primitives validation") {
944 std::vector<uint> uuids = createTestDataset(ctx,
"textured_primitives", 75);
946 std::string output_file =
"test_writeobj_textured.obj";
947 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
951 std::vector<uint> reloaded_uuids;
952 DOCTEST_CHECK_NOTHROW(reloaded_uuids = ctx_reload.loadOBJ(output_file.c_str(),
true));
954 DOCTEST_CHECK(uuids.size() == reloaded_uuids.size());
957 std::remove(output_file.c_str());
958 std::remove(
"test_writeobj_textured.mtl");
962 SUBCASE(
"Multi-material validation") {
964 std::vector<uint> uuids = createTestDataset(ctx,
"multi_material", 200);
966 std::string output_file =
"test_writeobj_materials.obj";
967 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
970 std::string mtl_file =
"test_writeobj_materials.mtl";
971 std::ifstream mtl_check(mtl_file);
972 DOCTEST_CHECK(mtl_check.good());
974 int material_count = 0;
976 while (std::getline(mtl_check, line)) {
977 if (line.substr(0, 6) ==
"newmtl") {
983 DOCTEST_CHECK(material_count >= 4);
986 std::remove(output_file.c_str());
987 std::remove(mtl_file.c_str());
990 SUBCASE(
"Object groups validation") {
992 std::vector<uint> uuids = createTestDataset(ctx,
"object_groups", 100);
994 std::string output_file =
"test_writeobj_groups.obj";
995 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
998 std::ifstream obj_check(output_file);
999 DOCTEST_CHECK(obj_check.good());
1001 int object_count = 0;
1003 while (std::getline(obj_check, line)) {
1004 if (line.substr(0, 2) ==
"o ") {
1010 DOCTEST_CHECK(object_count >= 4);
1013 std::remove(output_file.c_str());
1014 std::remove(
"test_writeobj_groups.mtl");
1018TEST_CASE(
"OBJ WriteOBJ - Performance Benchmarking Suite") {
1021 auto benchmarkWriteOBJ = [](Context &ctx,
const std::vector<uint> &uuids,
const std::string &test_name) {
1022 std::string output_file =
"bench_" + test_name +
".obj";
1024 auto start = std::chrono::high_resolution_clock::now();
1025 ctx.writeOBJ(output_file.c_str(), uuids,
false,
true);
1026 auto end = std::chrono::high_resolution_clock::now();
1028 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
1029 float write_time_ms = duration.count() / 1000.0f;
1030 float primitives_per_second = uuids.size() * 1000000.0f / duration.count();
1033 std::remove(output_file.c_str());
1034 std::string mtl_file =
"bench_" + test_name +
".mtl";
1035 std::remove(mtl_file.c_str());
1037 return std::make_pair(write_time_ms, primitives_per_second);
1040 SUBCASE(
"Baseline performance - 1000 triangles") {
1042 std::vector<uint> uuids;
1044 for (
int i = 0; i < 1000; i++) {
1045 float x =
static_cast<float>(i % 10);
1046 float z =
static_cast<float>(i / 10);
1048 uuids.push_back(tri);
1051 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"baseline_1k");
1054 DOCTEST_CHECK(prims_per_sec > 100.0f);
1057 SUBCASE(
"Multi-material performance - 2000 primitives") {
1059 std::vector<uint> uuids;
1061 for (
int i = 0; i < 2000; i++) {
1062 float x =
static_cast<float>(i % 20);
1063 float z =
static_cast<float>(i / 20);
1066 RGBcolor color =
make_RGBcolor((i % 10) / 10.0f, 0.5f, 0.7f);
1068 uuids.push_back(tri);
1071 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"multi_material_2k");
1074 DOCTEST_CHECK(prims_per_sec > 50.0f);
1077 SUBCASE(
"Large dataset performance - 5000 primitives") {
1079 std::vector<uint> uuids;
1081 for (
int i = 0; i < 5000; i++) {
1082 float x =
static_cast<float>(i % 50);
1083 float z =
static_cast<float>(i / 50);
1085 uuids.push_back(tri);
1088 auto [time_ms, prims_per_sec] = benchmarkWriteOBJ(ctx, uuids,
"large_5k");
1091 DOCTEST_CHECK(prims_per_sec > 25.0f);
1094 SUBCASE(
"Memory usage monitoring") {
1096 std::vector<uint> uuids;
1099 for (
int i = 0; i < 3000; i++) {
1100 float x =
static_cast<float>(i % 30);
1101 float z =
static_cast<float>(i / 30);
1103 uuids.push_back(tri);
1106 std::string output_file =
"bench_memory_test.obj";
1109 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
1112 std::ifstream file_check(output_file, std::ios::ate);
1113 auto file_size = file_check.tellg();
1116 DOCTEST_CHECK(file_size > 0);
1117 DOCTEST_CHECK(file_size < 50 * 1024 * 1024);
1120 std::remove(output_file.c_str());
1121 std::remove(
"bench_memory_test.mtl");
1125TEST_CASE(
"OBJ WriteOBJ - Stress Testing and Edge Cases") {
1127 SUBCASE(
"Very large primitive count") {
1129 std::vector<uint> uuids;
1132 for (
int i = 0; i < 10000; i++) {
1133 float x =
static_cast<float>(i % 100);
1134 float z =
static_cast<float>(i / 100);
1136 uuids.push_back(tri);
1139 std::string output_file =
"stress_large.obj";
1141 auto start = std::chrono::high_resolution_clock::now();
1142 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
1143 auto end = std::chrono::high_resolution_clock::now();
1145 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
1148 DOCTEST_CHECK(duration.count() < 10000);
1151 std::remove(output_file.c_str());
1152 std::remove(
"stress_large.mtl");
1155 SUBCASE(
"Many materials stress test") {
1157 std::vector<uint> uuids;
1160 for (
int i = 0; i < 500; i++) {
1161 float x =
static_cast<float>(i % 25);
1162 float z =
static_cast<float>(i / 25);
1165 float r =
static_cast<float>((i / 5) % 10) / 10.0f;
1166 float g =
static_cast<float>((i / 5) % 10) / 10.0f;
1167 float b =
static_cast<float>((i / 5) / 10) / 10.0f;
1171 uuids.push_back(tri);
1174 std::string output_file =
"stress_materials.obj";
1175 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
1178 std::string mtl_file =
"stress_materials.mtl";
1179 std::ifstream mtl_check(mtl_file);
1180 int material_count = 0;
1182 while (std::getline(mtl_check, line)) {
1183 if (line.substr(0, 6) ==
"newmtl") {
1189 DOCTEST_CHECK(material_count >= 50);
1192 std::remove(output_file.c_str());
1193 std::remove(mtl_file.c_str());
1196 SUBCASE(
"Degenerate geometry handling") {
1198 std::vector<uint> uuids;
1201 for (
int i = 0; i < 100; i++) {
1202 float x =
static_cast<float>(i % 10);
1203 float z =
static_cast<float>(i / 10);
1205 uuids.push_back(tri);
1209 for (
int i = 0; i < 10; i++) {
1210 float offset = i * 1e-8f;
1212 uuids.push_back(tri);
1215 std::string output_file =
"stress_degenerate.obj";
1216 DOCTEST_CHECK_NOTHROW(ctx.writeOBJ(output_file.c_str(), uuids,
false,
true));
1219 std::ifstream file_check(output_file);
1220 DOCTEST_CHECK(file_check.good());
1225 while (std::getline(file_check, line)) {
1230 DOCTEST_CHECK(line_count > 10);
1233 std::remove(output_file.c_str());
1234 std::remove(
"stress_degenerate.mtl");
1238TEST_CASE(
"OBJ Material Color Override Test") {
1239 SUBCASE(
"Material with Kd color and map_d transparency should use correct color") {
1243 std::string obj_content = R
"(# Test OBJ file for material color override bug
1244mtllib test_material_color.mtl
1257 std::string mtl_content = R
"(# Test MTL file for material color override bug
1260Ka 1.000000 1.000000 1.000000
1261Kd 0.000000 0.000000 0.800000
1262Ks 0.500000 0.500000 0.500000
1263Ke 0.000000 0.000000 0.000000
1266map_d lib/images/solid.jpg
1270 std::ofstream obj_file(
"lib/models/test_material_color.obj");
1271 obj_file << obj_content;
1274 std::ofstream mtl_file(
"lib/models/test_material_color.mtl");
1275 mtl_file << mtl_content;
1279 std::vector<uint> UUIDs;
1280 RGBcolor default_color = RGB::green;
1281 DOCTEST_CHECK_NOTHROW(UUIDs = ctx.loadOBJ(
"lib/models/test_material_color.obj",
make_vec3(0, 0, 0),
make_vec3(1, 1, 1), nullrotation, default_color,
"ZUP",
true));
1282 DOCTEST_CHECK(UUIDs.size() == 1);
1285 DOCTEST_CHECK(ctx.doesPrimitiveExist(UUIDs[0]));
1286 DOCTEST_CHECK(ctx.getPrimitiveType(UUIDs[0]) == PRIMITIVE_TYPE_TRIANGLE);
1289 std::string texture_file = ctx.getPrimitiveTextureFile(UUIDs[0]);
1290 DOCTEST_CHECK(!texture_file.empty());
1293 RGBcolor primitive_color = ctx.getPrimitiveColor(UUIDs[0]);
1296 DOCTEST_CHECK(primitive_color.r == doctest::Approx(0.0f).epsilon(1e-5));
1297 DOCTEST_CHECK(primitive_color.g == doctest::Approx(0.0f).epsilon(1e-5));
1298 DOCTEST_CHECK(primitive_color.b == doctest::Approx(0.8f).epsilon(1e-5));
1301 DOCTEST_CHECK(ctx.isPrimitiveTextureColorOverridden(UUIDs[0]));
1304 std::remove(
"lib/models/test_material_color.obj");
1305 std::remove(
"lib/models/test_material_color.mtl");