Files
ViewDesignEngine/tests/foundation/test_io_gltf.cpp
T

166 lines
5.1 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <fstream>
#include <cstdio>
#include "vde/foundation/io_gltf.h"
2026-07-24 09:08:06 +00:00
#include "vde/brep/modeling.h"
2026-07-24 09:10:01 +00:00
#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<char>(f)), std::istreambuf_iterator<char>());
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<char*>(&magic), 4);
f.read(reinterpret_cast<char*>(&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<char*>(&total_len), 4);
// Should contain "JSON" chunk marker
uint32_t chunk_len = 0, chunk_type = 0;
f.read(reinterpret_cast<char*>(&chunk_len), 4);
f.read(reinterpret_cast<char*>(&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());
2026-07-24 09:08:06 +00:00
// Read header
uint32_t magic = 0, version = 0, total_len = 0;
f.read(reinterpret_cast<char*>(&magic), 4);
f.read(reinterpret_cast<char*>(&version), 4);
f.read(reinterpret_cast<char*>(&total_len), 4);
EXPECT_EQ(magic, 0x46546C67u);
2026-07-24 09:08:06 +00:00
// Read JSON chunk header
uint32_t chunk_len = 0, chunk_type = 0;
f.read(reinterpret_cast<char*>(&chunk_len), 4);
f.read(reinterpret_cast<char*>(&chunk_type), 4);
2026-07-24 09:08:06 +00:00
EXPECT_EQ(chunk_type, 0x4E4F534Au);
// Read unpadded JSON data + skip padding
std::string json(chunk_len, '\0');
f.read(&json[0], chunk_len);
2026-07-24 09:08:06 +00:00
size_t padding = (4 - (chunk_len % 4)) % 4;
f.seekg(static_cast<std::streamoff>(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<char*>(&bin_len), 4)) {
f.read(reinterpret_cast<char*>(&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<char*>(&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";
}