a5facc84f6
Tests (48 new test cases total):
- test_draft_analysis.cpp: 18 tests (draft angle, classification, report, distribution)
- test_incremental_mesh.cpp: 15 tests (rebuild, invalidate, cache, merge, memory)
- test_kinematic_chain.cpp: 27 tests (Grashof, Freudenstein, gear ratio, 5 cam motions)
Build system:
- src/CMakeLists.txt: +draft_analysis, +incremental_mesh, +kinematic_chain
- tests/brep/CMakeLists.txt: +3 new test targets
Docs:
- 剩余路线图: v4.5 → 🚧
184 lines
6.1 KiB
C++
184 lines
6.1 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/brep/brep.h"
|
|
#include "vde/brep/modeling.h"
|
|
#include "vde/brep/incremental_mesh.h"
|
|
#include <cmath>
|
|
|
|
using namespace vde::brep;
|
|
using namespace vde::core;
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Basic Operations
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(IncrementalMeshTest, EmptyMesher) {
|
|
IncrementalMesher mesher;
|
|
EXPECT_EQ(mesher.dirty_count(), 0);
|
|
EXPECT_EQ(mesher.total_faces(), 0);
|
|
EXPECT_EQ(mesher.hit_rate(), 0.0);
|
|
EXPECT_EQ(mesher.memory_estimate(), 0u);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, RebuildAll_UnitBox) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
|
|
int count = mesher.rebuild_all(box, 0.01);
|
|
EXPECT_GT(count, 0);
|
|
EXPECT_EQ(mesher.dirty_count(), 0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, RebuildAll_Cylinder) {
|
|
auto cyl = make_cylinder(2, 6, 32);
|
|
IncrementalMesher mesher;
|
|
|
|
int count = mesher.rebuild_all(cyl);
|
|
EXPECT_GT(count, 0);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Invalidate & Incremental Rebuild
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(IncrementalMeshTest, InvalidateFace_MarksDirty) {
|
|
IncrementalMesher mesher;
|
|
mesher.invalidate_face(0);
|
|
EXPECT_EQ(mesher.dirty_count(), 1);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, InvalidateFace_ThenRebuild) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
|
|
// Full rebuild first
|
|
mesher.rebuild_all(box);
|
|
|
|
// Invalidate one face
|
|
mesher.invalidate_face(0);
|
|
EXPECT_EQ(mesher.dirty_count(), 1);
|
|
|
|
// Rebuild only dirty
|
|
int count = mesher.rebuild_dirty(box);
|
|
EXPECT_GT(count, 0);
|
|
EXPECT_EQ(mesher.dirty_count(), 0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, RebuildDirty_Empty) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
|
|
// No faces marked dirty → rebuild_dirty should do nothing
|
|
int count = mesher.rebuild_dirty(box);
|
|
EXPECT_EQ(count, 0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, MultipleInvalidates) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
mesher.invalidate_face(0);
|
|
mesher.invalidate_face(1);
|
|
mesher.invalidate_face(0); // duplicate — should still be 2 dirty
|
|
EXPECT_EQ(mesher.dirty_count(), 2);
|
|
|
|
int count = mesher.rebuild_dirty(box);
|
|
EXPECT_GE(count, 2);
|
|
EXPECT_EQ(mesher.dirty_count(), 0);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Cache
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(IncrementalMeshTest, GetMesh_AfterRebuild) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
const auto* mesh = mesher.get_mesh(0);
|
|
EXPECT_TRUE(mesh != nullptr);
|
|
EXPECT_GT(mesher.cache_hits(), 0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, GetMesh_AfterInvalidate) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
mesher.invalidate_face(0);
|
|
const auto* mesh = mesher.get_mesh(0);
|
|
EXPECT_TRUE(mesh == nullptr); // dirty face → cache miss
|
|
EXPECT_GT(mesher.cache_misses(), 0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, GetMesh_InvalidFaceId) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
const auto* mesh = mesher.get_mesh(999);
|
|
EXPECT_TRUE(mesh == nullptr);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, HitRate_AfterRebuild) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
// Access a cached mesh → hit
|
|
mesher.get_mesh(0);
|
|
EXPECT_GT(mesher.hit_rate(), 0.0);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Merged Mesh
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(IncrementalMeshTest, MergedMesh_AfterFullRebuild) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
auto merged = mesher.merged_mesh();
|
|
EXPECT_GT(merged.num_vertices(), 0u);
|
|
EXPECT_GT(merged.num_faces(), 0u);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, MergedMesh_AfterPartialInvalidate) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
// Invalidate one face → merged mesh should miss it
|
|
mesher.invalidate_face(0);
|
|
auto merged = mesher.merged_mesh();
|
|
// Still should produce a valid (but partial) mesh
|
|
EXPECT_GE(merged.num_vertices(), 0u);
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// Clear & Memory
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
TEST(IncrementalMeshTest, ClearAll) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
EXPECT_GT(mesher.total_faces(), 0);
|
|
|
|
mesher.clear_all();
|
|
EXPECT_EQ(mesher.total_faces(), 0);
|
|
EXPECT_EQ(mesher.dirty_count(), 0);
|
|
EXPECT_EQ(mesher.hit_rate(), 0.0);
|
|
}
|
|
|
|
TEST(IncrementalMeshTest, MemoryEstimate) {
|
|
auto box = make_box(2, 2, 2);
|
|
IncrementalMesher mesher;
|
|
mesher.rebuild_all(box);
|
|
|
|
size_t mem = mesher.memory_estimate();
|
|
EXPECT_GT(mem, 0u);
|
|
}
|