272 lines
9.1 KiB
C++
272 lines
9.1 KiB
C++
|
|
#include <gtest/gtest.h>
|
|||
|
|
#include "vde/mesh/fea_mesh.h"
|
|||
|
|
#include "vde/brep/modeling.h"
|
|||
|
|
#include <cmath>
|
|||
|
|
#include <map>
|
|||
|
|
|
|||
|
|
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<size_t>(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<size_t>(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<BlockRegion> 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<BlockRegion> 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<BlockRegion> 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<size_t>(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<BlockRegion> 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());
|
|||
|
|
}
|