152 lines
5.6 KiB
C++
152 lines
5.6 KiB
C++
|
|
#include <gtest/gtest.h>
|
||
|
|
#include "vde/mesh/mesh_lod.h"
|
||
|
|
#include "vde/mesh/halfedge_mesh.h"
|
||
|
|
#include <cmath>
|
||
|
|
|
||
|
|
using namespace vde::mesh;
|
||
|
|
|
||
|
|
/// Create a simple box mesh (12 triangles, 8 vertices)
|
||
|
|
static HalfedgeMesh make_box_mesh() {
|
||
|
|
// Unit cube vertices
|
||
|
|
std::vector<Point3D> verts = {
|
||
|
|
{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}, // bottom
|
||
|
|
{0, 0, 1}, {1, 0, 1}, {1, 1, 1}, {0, 1, 1}, // top
|
||
|
|
};
|
||
|
|
// 12 triangles (2 per face)
|
||
|
|
std::vector<std::array<int, 3>> tris = {
|
||
|
|
{0, 2, 1}, {0, 3, 2}, // front
|
||
|
|
{1, 2, 6}, {1, 6, 5}, // right
|
||
|
|
{5, 6, 7}, {5, 7, 4}, // back
|
||
|
|
{4, 7, 3}, {4, 3, 0}, // left
|
||
|
|
{3, 7, 6}, {3, 6, 2}, // top
|
||
|
|
{4, 0, 1}, {4, 1, 5}, // bottom
|
||
|
|
};
|
||
|
|
|
||
|
|
HalfedgeMesh mesh;
|
||
|
|
mesh.build_from_triangles(verts, tris);
|
||
|
|
return mesh;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
// LOD generation
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
TEST(LODMeshTest, GenerateLOD_DefaultLevels) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
// Default: 3 levels (base + 2 simplified)
|
||
|
|
EXPECT_EQ(lod.levels.size(), 3u);
|
||
|
|
EXPECT_EQ(lod.thresholds.size(), 3u);
|
||
|
|
|
||
|
|
// Level 0 is the original mesh (12 faces)
|
||
|
|
EXPECT_GE(lod.levels[0].num_faces(), 8u);
|
||
|
|
// Each level should have ≤ faces than previous
|
||
|
|
EXPECT_LE(lod.levels[1].num_faces(), lod.levels[0].num_faces());
|
||
|
|
EXPECT_LE(lod.levels[2].num_faces(), lod.levels[1].num_faces());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, GenerateLOD_SingleLevel) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 1, 0.5);
|
||
|
|
|
||
|
|
// Only 1 level = base (no simplification)
|
||
|
|
EXPECT_EQ(lod.levels.size(), 1u);
|
||
|
|
EXPECT_EQ(lod.levels[0].num_faces(), 12u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, GenerateLOD_AggressiveReduction) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 5, 0.1); // keep only 10% each level
|
||
|
|
|
||
|
|
// Should stop early when < 4 faces remain
|
||
|
|
EXPECT_GE(lod.levels.size(), 1u);
|
||
|
|
EXPECT_LE(lod.levels.size(), 5u);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, GenerateLOD_EmptyMesh) {
|
||
|
|
HalfedgeMesh empty;
|
||
|
|
auto lod = generate_lod(empty, 3, 0.5);
|
||
|
|
|
||
|
|
EXPECT_EQ(lod.levels.size(), 1u); // only base
|
||
|
|
EXPECT_EQ(lod.levels[0].num_faces(), 0u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
// level_for_error
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
TEST(LODMeshTest, LevelForError_ZeroError) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
// error 0 → base level (threshold 0.0)
|
||
|
|
EXPECT_EQ(lod.level_for_error(0.0), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, LevelForError_ThresholdBoundary) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
// thresholds: [0.0, 100.0, 200.0]
|
||
|
|
EXPECT_EQ(lod.level_for_error(50.0), 0);
|
||
|
|
EXPECT_EQ(lod.level_for_error(100.0), 1); // ≥ 100 → level 1
|
||
|
|
EXPECT_EQ(lod.level_for_error(150.0), 1);
|
||
|
|
EXPECT_EQ(lod.level_for_error(200.0), 2); // ≥ 200 → level 2
|
||
|
|
EXPECT_EQ(lod.level_for_error(999.0), 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, LevelForError_NegativeError) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
// Negative error → level 0 (base)
|
||
|
|
EXPECT_EQ(lod.level_for_error(-1.0), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
// mesh_at
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
TEST(LODMeshTest, MeshAt_ValidLevels) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
EXPECT_EQ(lod.mesh_at(0).num_faces(), 12u);
|
||
|
|
EXPECT_LE(lod.mesh_at(1).num_faces(), 12u);
|
||
|
|
EXPECT_LE(lod.mesh_at(2).num_faces(), lod.mesh_at(1).num_faces());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, MeshAt_ClampNegative) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
// Negative level → clamped to 0
|
||
|
|
EXPECT_EQ(lod.mesh_at(-1).num_faces(), lod.mesh_at(0).num_faces());
|
||
|
|
EXPECT_EQ(lod.mesh_at(-100).num_faces(), lod.mesh_at(0).num_faces());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, MeshAt_ClampTooHigh) {
|
||
|
|
auto box = make_box_mesh();
|
||
|
|
auto lod = generate_lod(box, 3, 0.5);
|
||
|
|
|
||
|
|
int last = static_cast<int>(lod.levels.size()) - 1;
|
||
|
|
EXPECT_EQ(lod.mesh_at(999).num_faces(), lod.mesh_at(last).num_faces());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(LODMeshTest, MeshAt_EmptyLOD) {
|
||
|
|
LODMesh empty_lod;
|
||
|
|
// mesh_at returns a static empty mesh for empty LOD
|
||
|
|
EXPECT_EQ(empty_lod.mesh_at(0).num_faces(), 0u);
|
||
|
|
EXPECT_EQ(empty_lod.mesh_at(5).num_faces(), 0u);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
// generate_brep_lod (integration)
|
||
|
|
// ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
// Forward-declare for linking only (no brep::make_box in this TU's link deps)
|
||
|
|
// This test is compile-time only; runtime needs brep linked.
|
||
|
|
// We test through the LOD API directly above; brep path is tested
|
||
|
|
// in the brep test suite.
|