#include #include #include #include "vde/foundation/io_gltf.h" #include "vde/brep/modeling.h" #include "vde/brep/modeling.h" using namespace vde::foundation; using namespace vde::mesh; using namespace vde::core; // ── Helper: build a simple tetrahedron mesh ── static HalfedgeMesh make_tetrahedron() { HalfedgeMesh mesh; mesh.build_from_triangles( { Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0.5, 0.866, 0), Point3D(0.5, 0.289, 0.816) }, { {0, 1, 2}, {0, 3, 1}, {1, 3, 2}, {0, 2, 3} } ); return mesh; } // ── Tests ────────────────────────────────────────────────────────── TEST(IoGltfTest, ExportTetToGltfJson) { auto mesh = make_tetrahedron(); GltfOptions opt; opt.binary = false; opt.include_normals = false; const char* path = "/tmp/test_tet.gltf"; std::remove(path); // clean up from previous run ASSERT_TRUE(write_gltf(path, mesh, opt)); std::ifstream f(path); ASSERT_TRUE(f.good()); std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); EXPECT_NE(content.find("\"asset\""), std::string::npos); EXPECT_NE(content.find("\"meshes\""), std::string::npos); EXPECT_NE(content.find("ViewDesignEngine"), std::string::npos); std::remove(path); std::remove("/tmp/test_tet.bin"); } TEST(IoGltfTest, ExportTetToGlbBinary) { auto mesh = make_tetrahedron(); GltfOptions opt; opt.binary = true; opt.include_normals = false; const char* path = "/tmp/test_tet.glb"; std::remove(path); ASSERT_TRUE(write_gltf(path, mesh, opt)); // Verify GLB magic bytes std::ifstream f(path, std::ios::binary); ASSERT_TRUE(f.good()); uint32_t magic = 0, version = 0; f.read(reinterpret_cast(&magic), 4); f.read(reinterpret_cast(&version), 4); EXPECT_EQ(magic, 0x46546C67u) << "GLB magic should be 'glTF'"; EXPECT_EQ(version, 2u) << "GLB version should be 2"; uint32_t total_len = 0; f.read(reinterpret_cast(&total_len), 4); // Should contain "JSON" chunk marker uint32_t chunk_len = 0, chunk_type = 0; f.read(reinterpret_cast(&chunk_len), 4); f.read(reinterpret_cast(&chunk_type), 4); EXPECT_EQ(chunk_type, 0x4E4F534Au) << "First chunk should be JSON"; std::remove(path); } TEST(IoGltfTest, ExportWithNormals) { auto mesh = make_tetrahedron(); GltfOptions opt; opt.binary = true; opt.include_normals = true; const char* path = "/tmp/test_tet_normals.glb"; std::remove(path); ASSERT_TRUE(write_gltf(path, mesh, opt)); // Read GLB and verify it contains JSON with NORMAL attribute std::ifstream f(path, std::ios::binary); ASSERT_TRUE(f.good()); // Read header uint32_t magic = 0, version = 0, total_len = 0; f.read(reinterpret_cast(&magic), 4); f.read(reinterpret_cast(&version), 4); f.read(reinterpret_cast(&total_len), 4); EXPECT_EQ(magic, 0x46546C67u); // Read JSON chunk header uint32_t chunk_len = 0, chunk_type = 0; f.read(reinterpret_cast(&chunk_len), 4); f.read(reinterpret_cast(&chunk_type), 4); EXPECT_EQ(chunk_type, 0x4E4F534Au); // Read unpadded JSON data + skip padding std::string json(chunk_len, '\0'); f.read(&json[0], chunk_len); size_t padding = (4 - (chunk_len % 4)) % 4; f.seekg(static_cast(padding), std::ios::cur); EXPECT_NE(json.find("\"NORMAL\""), std::string::npos) << "JSON should include NORMAL attribute when normals enabled"; // File should also have a BIN chunk (larger because of normals) uint32_t bin_len = 0, bin_type = 0; if (f.read(reinterpret_cast(&bin_len), 4)) { f.read(reinterpret_cast(&bin_type), 4); EXPECT_EQ(bin_type, 0x004E4942u) << "Second chunk should be BIN"; EXPECT_GT(bin_len, 0u) << "BIN chunk should have data"; } std::remove(path); } TEST(IoGltfTest, ExportBrepBoxToGlb) { auto box = vde::brep::make_box(2.0, 2.0, 2.0); const char* path = "/tmp/test_brep_box.glb"; std::remove(path); ASSERT_TRUE(write_brep_gltf(path, box, 32)); // Verify file exists and has magic std::ifstream f(path, std::ios::binary); ASSERT_TRUE(f.good()); uint32_t magic = 0; f.read(reinterpret_cast(&magic), 4); EXPECT_EQ(magic, 0x46546C67u); std::remove(path); } TEST(IoGltfTest, EmptyMeshFailsGracefully) { HalfedgeMesh empty; GltfOptions opt; opt.binary = true; const char* path = "/tmp/test_empty.glb"; std::remove(path); EXPECT_FALSE(write_gltf(path, empty, opt)) << "Exporting empty mesh should return false"; } TEST(IoGltfTest, EmptyBrepFailsGracefully) { vde::brep::BrepModel empty; const char* path = "/tmp/test_empty_brep.glb"; std::remove(path); EXPECT_FALSE(write_brep_gltf(path, empty, 32)) << "Exporting empty B-Rep should return false"; }