163 lines
5.0 KiB
C++
163 lines
5.0 KiB
C++
#include <gtest/gtest.h>
|
|
#include <fstream>
|
|
#include <cstdio>
|
|
#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<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";
|
|
|
|
// 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());
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
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<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";
|
|
}
|