#include #include "vde/mesh/fea_mesh.h" #include "vde/brep/modeling.h" #include #include using namespace vde::mesh; using namespace vde::brep; using namespace vde::core; // ═══════════════════════════════════════════════════════════ // 1. mapped_hex_mesh — 映射法六面体测试 // ═══════════════════════════════════════════════════════════ TEST(MappedHexTest, BoxMappedMesh_NonEmpty) { auto box = make_box(2.0, 2.0, 2.0); FaceRegion src; src.face_indices = {0}; src.name = "bottom"; FaceRegion tgt; tgt.face_indices = {1}; tgt.name = "top"; auto mesh = mapped_hex_mesh(box, src, tgt, 3); EXPECT_EQ(mesh.element_type, FEAElementType::Hex8); } TEST(MappedHexTest, BoxMappedMesh_ValidConnectivity) { auto box = make_box(1.0, 1.0, 1.0); FaceRegion src; src.face_indices = {0}; FaceRegion tgt; tgt.face_indices = {1}; auto mesh = mapped_hex_mesh(box, src, tgt, 2); for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 8u); for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } TEST(MappedHexTest, BoxMappedMesh_LayerCount) { auto box = make_box(1.0, 1.0, 1.0); FaceRegion src; src.face_indices = {0}; FaceRegion tgt; tgt.face_indices = {1}; int layers = 4; auto mesh = mapped_hex_mesh(box, src, tgt, layers); // 顶点数 = 源面顶点 × (layers + 1) EXPECT_GT(mesh.num_vertices(), 0u); } // ═══════════════════════════════════════════════════════════ // 2. submapped_hex_mesh — 子映射法六面体测试 // ═══════════════════════════════════════════════════════════ TEST(SubmappedHexTest, BoxSubmappedMesh_NonEmpty) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = submapped_hex_mesh(box); EXPECT_EQ(mesh.element_type, FEAElementType::Hex8); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); } TEST(SubmappedHexTest, BoxSubmappedMesh_ValidConnectivity) { auto box = make_box(2.0, 1.0, 1.0); auto mesh = submapped_hex_mesh(box); for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 8u); for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } TEST(SubmappedHexTest, SphereSubmappedMesh_NonTrivial) { auto sphere = make_sphere(1.0); auto mesh = submapped_hex_mesh(sphere); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); } // ═══════════════════════════════════════════════════════════ // 3. multi_block_hex_mesh — 多块结构化六面体测试 // ═══════════════════════════════════════════════════════════ TEST(MultiBlockHexTest, EmptyBlocks_FallbackGrid) { auto box = make_box(2.0, 2.0, 2.0); // blocks为空时回退到4×4×4均匀栅格 std::vector blocks; auto mesh = multi_block_hex_mesh(box, blocks); EXPECT_EQ(mesh.element_type, FEAElementType::Hex8); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); // 4×4×4 grid → 4^3=64 elements EXPECT_GE(mesh.num_elements(), 64u); } TEST(MultiBlockHexTest, SingleBlock_TFILinear) { auto box = make_box(1.0, 1.0, 1.0); BlockRegion block; block.corner_vertices = { Point3D(0,0,0), Point3D(1,0,0), Point3D(1,1,0), Point3D(0,1,0), Point3D(0,0,1), Point3D(1,0,1), Point3D(1,1,1), Point3D(0,1,1) }; block.n_i = 2; block.n_j = 2; block.n_k = 2; std::vector blocks = {block}; auto mesh = multi_block_hex_mesh(box, blocks); EXPECT_GT(mesh.num_vertices(), 0u); EXPECT_GT(mesh.num_elements(), 0u); } TEST(MultiBlockHexTest, MultiBlocks_ValidConnectivity) { auto box = make_box(2.0, 2.0, 2.0); BlockRegion b1; b1.corner_vertices = { Point3D(0,0,0), Point3D(1,0,0), Point3D(1,1,0), Point3D(0,1,0), Point3D(0,0,1), Point3D(1,0,1), Point3D(1,1,1), Point3D(0,1,1) }; b1.n_i = 2; b1.n_j = 2; b1.n_k = 2; BlockRegion b2; b2.corner_vertices = { Point3D(1,0,0), Point3D(2,0,0), Point3D(2,1,0), Point3D(1,1,0), Point3D(1,0,1), Point3D(2,0,1), Point3D(2,1,1), Point3D(1,1,1) }; b2.n_i = 2; b2.n_j = 2; b2.n_k = 2; std::vector blocks = {b1, b2}; auto mesh = multi_block_hex_mesh(box, blocks); for (size_t ei = 0; ei < mesh.num_elements(); ++ei) { auto& e = mesh.elements[ei]; EXPECT_EQ(e.size(), 8u); for (int vi : e) { EXPECT_GE(vi, 0); EXPECT_LT(static_cast(vi), mesh.num_vertices()); } } } // ═══════════════════════════════════════════════════════════ // 4. hex_quality_optimization — 六面体质量优化 + untangling // ═══════════════════════════════════════════════════════════ TEST(HexQualityOptTest, UnitCube_QualityOptimization) { // 创建单位立方体六面体网格 FEAMesh mesh; mesh.element_type = FEAElementType::Hex8; mesh.vertices = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, {0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} }; mesh.elements.push_back({0,1,2,3,4,5,6,7}); mesh.material_ids.push_back(0); HexOptimizationParams params; params.untangle = true; params.laplacian_smooth = true; params.max_iterations = 5; auto result = hex_quality_optimization(mesh, params); EXPECT_GT(result.initial_quality.total_elements, 0u); EXPECT_GE(result.final_quality.avg_jacobian, 0.0); } TEST(HexQualityOptTest, QualityReport_AfterOptimization) { auto box = make_box(2.0, 2.0, 2.0); // 生成结构化六面体网格 std::vector blocks; auto mesh = multi_block_hex_mesh(box, blocks); auto initial = fea_quality_report(mesh); EXPECT_GT(initial.total_elements, 0u); HexOptimizationParams params; params.laplacian_smooth = true; params.max_iterations = 10; auto result = hex_quality_optimization(mesh, params); EXPECT_EQ(result.final_quality.total_elements, initial.total_elements); EXPECT_GE(result.final_quality.avg_jacobian, 0.0); EXPECT_LE(result.final_quality.avg_jacobian, 1.0); } TEST(HexQualityOptTest, Untangle_Inverted) { // 创建一个轻微扭曲的六面体 FEAMesh mesh; mesh.element_type = FEAElementType::Hex8; mesh.vertices = { {0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, {0,0,1}, {1,0,1}, {0.3,0.3,1}, {0,1,1} // 顶点6向内扭曲 }; mesh.elements.push_back({0,1,2,3,4,5,6,7}); mesh.material_ids.push_back(0); HexOptimizationParams params; params.untangle = true; params.laplacian_smooth = true; params.optimization_based_smooth = false; params.max_iterations = 10; auto result = hex_quality_optimization(mesh, params); EXPECT_GT(result.final_quality.avg_jacobian, 0.0); } // ═══════════════════════════════════════════════════════════ // 5. 综合集成测试 // ═══════════════════════════════════════════════════════════ TEST(HexIntegrationTest, MappedThenOptimize) { auto box = make_box(2.0, 2.0, 2.0); FaceRegion src; src.face_indices = {0}; FaceRegion tgt; tgt.face_indices = {1}; auto mesh = mapped_hex_mesh(box, src, tgt, 3); if (mesh.num_elements() == 0) { GTEST_SKIP() << "mapped_hex_mesh returned empty (no quad faces available)"; } HexOptimizationParams params; params.max_iterations = 5; auto result = hex_quality_optimization(mesh, params); EXPECT_EQ(result.final_quality.total_elements, mesh.num_elements()); } TEST(HexIntegrationTest, SubmappedThenOptimize) { auto box = make_box(1.0, 1.0, 1.0); auto mesh = submapped_hex_mesh(box); HexOptimizationParams params; params.laplacian_smooth = true; params.max_iterations = 5; auto result = hex_quality_optimization(mesh, params); EXPECT_EQ(result.final_quality.total_elements, mesh.num_elements()); }