Files
ViewDesignEngine/tests/curves/test_class_a_surfacing.cpp
T

550 lines
17 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include "vde/curves/class_a_surfacing.h"
#include "vde/curves/nurbs_curve.h"
#include "vde/curves/nurbs_surface.h"
#include <cmath>
using namespace vde::curves;
using namespace vde::core;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// 创建平面 NURBS 曲面(XY 平面,[0,1]×[0,1]
static NurbsSurface plane_surface() {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,0), Point3D(0,1,0)},
{Point3D(1,0,0), Point3D(1,1,0)}
};
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {{1,1},{1,1}}, 1, 1);
}
/// 创建二次曲面(抛物面样)
static NurbsSurface quad_surface() {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,0), Point3D(0,1,0.5), Point3D(0,2,0)},
{Point3D(1,0,0.5), Point3D(1,1,1), Point3D(1,2,0.5)},
{Point3D(2,0,0), Point3D(2,1,0.5), Point3D(2,2,0)}
};
return NurbsSurface(grid,
{0,0,0,1,1,1}, {0,0,0,1,1,1},
{{1,1,1},{1,1,1},{1,1,1}}, 2, 2);
}
/// 创建圆柱面
static NurbsSurface cylinder_surface() {
double R = 1.0;
std::vector<std::vector<Point3D>> grid(5, std::vector<Point3D>(3));
for (int i = 0; i < 5; ++i) {
double angle = i * M_PI / 2.0;
double x = R * std::cos(angle);
double y = R * std::sin(angle);
for (int j = 0; j < 3; ++j) {
grid[i][j] = Point3D(x, y, j * 0.5);
}
}
std::vector<std::vector<double>> w(5, std::vector<double>(3, 1.0));
return NurbsSurface(grid,
{0,0,0,0,0.5,1,1,1,1},
{0,0,0,1,1,1}, w, 3, 2);
}
/// 沿 Z 偏移的平面
static NurbsSurface offset_plane(double z) {
std::vector<std::vector<Point3D>> grid = {
{Point3D(0,0,z), Point3D(0,1,z)},
{Point3D(1,0,z), Point3D(1,1,z)}
};
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {{1,1},{1,1}}, 1, 1);
}
// ---------------------------------------------------------------------------
// Test: G3 Blend — G3 连续性过渡
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, G3Blend_IdenticalPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0); // 重合平面
EdgeParams params;
params.edge_a = 3; // vmax of a
params.edge_b = 2; // vmin of b
params.blend_width = 0.2;
auto result = g3_blend(a, b, params);
// 过渡面应该非退化
auto [nu, nv] = result.blend_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
}
TEST(ClassASurfacingTest, G3Blend_ContinuityChecks) {
auto a = plane_surface();
auto b = offset_plane(0.0);
EdgeParams params;
params.blend_width = 0.15;
auto result = g3_blend(a, b, params);
// G0 位置连续
EXPECT_TRUE(result.g0_ok);
EXPECT_LT(result.max_position_error, 1e-3);
// G1 切平面连续
EXPECT_TRUE(result.g1_ok);
EXPECT_LT(result.max_tangent_error, 0.02);
}
TEST(ClassASurfacingTest, G3Blend_ResultHasBlendSurface) {
auto a = plane_surface();
auto b = offset_plane(1.0); // 间距 1
EdgeParams params;
params.edge_a = 3;
params.edge_b = 2;
params.blend_width = 0.5;
auto result = g3_blend(a, b, params);
auto [nu, nv] = result.blend_surface.num_control_points();
// 应生成有效的过渡曲面
EXPECT_GE(nu, 2);
EXPECT_EQ(nv, 4); // G3 需要 4 排控制点
}
// ---------------------------------------------------------------------------
// Test: Curvature Matching — 曲率匹配
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, CurvatureMatching_FlatPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0);
CurvatureMatchOptions opts;
opts.samples_per_edge = 10;
opts.max_iterations = 20;
auto result = curvature_matching(a, b, opts);
// 平面曲率处处为0,匹配后应保持
EXPECT_LT(result.final_rms_curvature_diff, 1.0);
EXPECT_GT(result.iterations, 0);
}
TEST(ClassASurfacingTest, CurvatureMatching_ReturnsMatchedSurface) {
auto a = quad_surface();
auto b = quad_surface();
CurvatureMatchOptions opts;
opts.max_iterations = 5;
opts.tolerance = 1e-4;
auto result = curvature_matching(a, b, opts);
auto [nu, nv] = result.matched_surface_b.num_control_points();
EXPECT_EQ(nu, 3);
EXPECT_EQ(nv, 3);
}
TEST(ClassASurfacingTest, CurvatureMatching_DisplacementVector) {
auto a = plane_surface();
auto b = quad_surface();
CurvatureMatchOptions opts;
opts.max_iterations = 3;
auto result = curvature_matching(a, b, opts);
// 应有位移向量
EXPECT_FALSE(result.displacement.empty());
EXPECT_GE(result.displacement.size(), 1u);
}
// ---------------------------------------------------------------------------
// Test: Highlight Lines — 高光线分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, HighlightLines_PlaneOutput) {
auto s = plane_surface();
std::vector<Vector3D> lights = {Vector3D(0,0,1)};
auto result = highlight_lines(s, lights, 10, 10, 6);
EXPECT_EQ(result.res_u, 10);
EXPECT_EQ(result.res_v, 10);
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 1);
}
TEST(ClassASurfacingTest, HighlightLines_MultipleLights) {
auto s = quad_surface();
std::vector<Vector3D> lights = {
Vector3D(1,0,0), Vector3D(0,1,0), Vector3D(0,0,1)
};
auto result = highlight_lines(s, lights, 15, 15, 8);
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 3);
EXPECT_EQ(static_cast<int>(result.continuity_scores.size()), 3);
EXPECT_GE(result.overall_score, 0.0);
EXPECT_LE(result.overall_score, 1.0);
}
TEST(ClassASurfacingTest, HighlightLines_FlatPlanePerfectScore) {
auto s = plane_surface();
std::vector<Vector3D> lights = {Vector3D(1,1,1)};
auto result = highlight_lines(s, lights, 20, 20, 10);
// 平面上高光线应连续
EXPECT_GT(result.continuity_scores[0], 0.9);
}
// ---------------------------------------------------------------------------
// Test: Reflection Lines — 反射线分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ReflectionLines_PlaneIsFair) {
auto s = plane_surface();
auto result = reflection_lines(s,
Point3D(5, 5, 5), Point3D(-5, -5, 10), 20, 20);
EXPECT_EQ(result.res_u, 20);
EXPECT_EQ(result.res_v, 20);
// 平面反射线应判定为光顺
EXPECT_TRUE(result.is_fair);
}
TEST(ClassASurfacingTest, ReflectionLines_DistortionBounds) {
auto s = quad_surface();
auto result = reflection_lines(s,
Point3D(3, 3, 5), Point3D(-3, -3, 8), 15, 15);
EXPECT_GE(result.max_distortion, 0.0);
EXPECT_GE(result.mean_distortion, 0.0);
EXPECT_LE(result.mean_distortion, result.max_distortion + 1e-9);
}
// ---------------------------------------------------------------------------
// Test: Iso-Photes — 等照度分析
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, IsoPhotes_OutputDimensions) {
auto s = plane_surface();
auto result = iso_photes(s, 16);
EXPECT_EQ(result.res_u, 16);
EXPECT_EQ(result.res_v, 16);
EXPECT_EQ(static_cast<int>(result.illumination.size()), 17);
EXPECT_EQ(static_cast<int>(result.illumination[0].size()), 17);
}
TEST(ClassASurfacingTest, IsoPhotes_IlluminationRange) {
auto s = plane_surface();
auto result = iso_photes(s, 10);
for (size_t i = 0; i < result.illumination.size(); ++i) {
for (size_t j = 0; j < result.illumination[i].size(); ++j) {
double val = result.illumination[i][j];
EXPECT_GE(val, -1.1);
EXPECT_LE(val, 1.1);
}
}
}
TEST(ClassASurfacingTest, IsoPhotes_GradientAnalysis) {
auto s = quad_surface();
auto result = iso_photes(s, 20);
EXPECT_GE(result.grad_max, 0.0);
EXPECT_GE(result.grad_mean, 0.0);
}
// ---------------------------------------------------------------------------
// Test: Surface Diagnosis — 综合曲面诊断
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, SurfaceDiagnosis_PlaneDiagnosis) {
auto s = plane_surface();
auto report = surface_diagnosis(s);
// 平面应该达到 A 级
EXPECT_EQ(report.grade, DiagnosisGrade::A_CLASS);
EXPECT_GT(report.overall_score, 80.0);
}
TEST(ClassASurfacingTest, SurfaceDiagnosis_ReportHasAllFields) {
auto s = quad_surface();
auto report = surface_diagnosis(s);
EXPECT_FALSE(report.gaussian_range.name.empty());
EXPECT_FALSE(report.mean_range.name.empty());
EXPECT_FALSE(report.highlight_score.name.empty());
EXPECT_FALSE(report.reflection_distortion.name.empty());
EXPECT_FALSE(report.isophote_gradient.name.empty());
EXPECT_FALSE(report.normal_jump.name.empty());
EXPECT_FALSE(report.recommendation.empty());
}
// ---------------------------------------------------------------------------
// Test: Shape Modification — 保形修改
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ShapeModification_SingleConstraint) {
auto s = plane_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(0.5, 0.5, 0.5); // 提升0.5
c.u = 0.5;
c.v = 0.5;
c.weight = 1.0;
constraints.push_back(c);
auto result = shape_modification(s, constraints);
auto [nu, nv] = result.modified_surface.num_control_points();
EXPECT_EQ(nu, 2);
EXPECT_EQ(nv, 2);
// 控制点应有位移
EXPECT_GT(result.max_displacement, 0.0);
EXPECT_GE(result.energy_preserved_ratio, 0.0);
EXPECT_LE(result.energy_preserved_ratio, 1.1);
}
TEST(ClassASurfacingTest, ShapeModification_ConstraintPointMoves) {
auto s = plane_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(0.3, 0.3, 2.0);
c.u = 0.3;
c.v = 0.3;
c.weight = 2.0;
constraints.push_back(c);
auto result = shape_modification(s, constraints);
// 修改后的曲面在约束点处应接近目标
auto pt = result.modified_surface.evaluate(0.3, 0.3);
EXPECT_GT(pt.z(), 0.1); // 应被提起
}
TEST(ClassASurfacingTest, ShapeModification_MultipleConstraints) {
auto s = quad_surface();
std::vector<ShapeConstraint> constraints;
for (int i = 0; i < 3; ++i) {
ShapeConstraint c;
c.u = 0.25 * (i + 1);
c.v = 0.5;
c.target_point = Point3D(c.u * 2, 1.0, 1.5 + i * 0.5);
c.weight = 1.0;
constraints.push_back(c);
}
auto result = shape_modification(s, constraints);
EXPECT_GE(result.iterations, 1);
EXPECT_GT(result.max_displacement, 0.0);
}
// ---------------------------------------------------------------------------
// Test: G3 Blend with Constraints — 带约束 G3 过渡 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, G3BlendWithConstraints_IdenticalPlanes) {
auto a = plane_surface();
auto b = offset_plane(0.0);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.2;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
auto result = g3_blend_with_constraints(a, b, edges);
auto [nu, nv] = result.blend_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
}
TEST(ClassASurfacingTest, G3BlendWithConstraints_G0G1Check) {
auto a = plane_surface();
auto b = offset_plane(0.0);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.15;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
edges.enforce_g0 = true;
edges.enforce_g1 = true;
auto result = g3_blend_with_constraints(a, b, edges);
EXPECT_TRUE(result.g0_ok);
EXPECT_TRUE(result.g1_ok);
}
TEST(ClassASurfacingTest, G3BlendWithConstraints_OnlyG0Enforced) {
auto a = plane_surface();
auto b = offset_plane(0.5);
G3ConstraintEdges edges;
edges.g0_edge.edge_a = 3;
edges.g0_edge.edge_b = 2;
edges.g0_edge.blend_width = 0.3;
edges.g1_edge = edges.g0_edge;
edges.g2_edge = edges.g0_edge;
edges.g3_edge = edges.g0_edge;
edges.enforce_g0 = true;
edges.enforce_g1 = false;
edges.enforce_g2 = false;
edges.enforce_g3 = false;
auto result = g3_blend_with_constraints(a, b, edges);
EXPECT_TRUE(result.g0_ok);
EXPECT_FALSE(result.g1_ok); // G1未强制=检查默认失败
}
// ---------------------------------------------------------------------------
// Test: Surface Energy Minimization — 薄板能量最小化 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, EnergyMinimization_FlatSurface) {
auto s = plane_surface();
EnergyMinimizationOptions opts;
opts.max_iterations = 20;
opts.tolerance = 1e-4;
auto result = surface_energy_minimization(s, {}, opts);
auto [nu, nv] = result.optimized_surface.num_control_points();
EXPECT_EQ(nu, 2);
EXPECT_EQ(nv, 2);
EXPECT_GE(result.final_bending_energy, 0.0);
}
TEST(ClassASurfacingTest, EnergyMinimization_WithConstraint) {
auto s = quad_surface();
std::vector<ShapeConstraint> constraints;
ShapeConstraint c;
c.target_point = Point3D(1.0, 1.0, 2.0);
c.u = 0.5;
c.v = 0.5;
c.weight = 5.0;
constraints.push_back(c);
EnergyMinimizationOptions opts;
opts.max_iterations = 30;
opts.constraint_weight = 20.0;
auto result = surface_energy_minimization(s, constraints, opts);
EXPECT_GT(result.iterations, 0);
EXPECT_GT(result.max_displacement, 0.0);
}
TEST(ClassASurfacingTest, EnergyMinimization_BendingReduction) {
auto s = quad_surface();
EnergyMinimizationOptions opts;
opts.bending_weight = 5.0;
opts.membrane_weight = 0.01;
opts.max_iterations = 50;
opts.preserve_boundary = false;
auto result = surface_energy_minimization(s, {}, opts);
EXPECT_GE(result.final_bending_energy, 0.0);
EXPECT_GE(result.iterations, 1);
}
// ---------------------------------------------------------------------------
// Test: Curvature Continuity Optimization — 曲率连续性迭代精化 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, CurvatureContinuity_IdenticalSurfaces) {
auto a = plane_surface();
auto b = plane_surface();
CurvatureContinuityOptions opts;
opts.max_iterations = 10;
opts.boundary_samples = 15;
auto result = curvature_continuity_optimization(a, b, opts);
EXPECT_LT(result.final_g0_error, 1e-3);
EXPECT_LT(result.final_g1_error, 0.01);
}
TEST(ClassASurfacingTest, CurvatureContinuity_QuadSurfaces) {
auto a = quad_surface();
auto b = quad_surface();
CurvatureContinuityOptions opts;
opts.max_iterations = 20;
opts.boundary_samples = 20;
opts.step_size = 0.02;
auto result = curvature_continuity_optimization(a, b, opts);
auto [nu, nv] = result.optimized_b.num_control_points();
EXPECT_EQ(nu, 3);
EXPECT_EQ(nv, 3);
EXPECT_GE(result.iterations, 0);
}
// ---------------------------------------------------------------------------
// Test: Reflection Line Discontinuity — 反射线不连续检测+修复 (新增)
// ---------------------------------------------------------------------------
TEST(ClassASurfacingTest, ReflectionDiscontinuity_PlaneIsSmooth) {
auto s = plane_surface();
ReflectionDiscontinuityParams params;
params.light_direction = Vector3D(1, 0, 0);
params.res_u = 20;
params.res_v = 20;
params.discontinuity_threshold = 0.05;
params.auto_fix = false;
auto result = reflection_line_discontinuity(s, params);
EXPECT_EQ(result.res_u, 20);
EXPECT_EQ(result.res_v, 20);
EXPECT_TRUE(result.is_smooth);
}
TEST(ClassASurfacingTest, ReflectionDiscontinuity_AutoFix) {
auto s = quad_surface();
ReflectionDiscontinuityParams params;
params.light_direction = Vector3D(1, 1, 1);
params.res_u = 15;
params.res_v = 15;
params.discontinuity_threshold = 0.1;
params.max_fix_iterations = 5;
params.fix_strength = 0.05;
params.auto_fix = true;
auto result = reflection_line_discontinuity(s, params);
auto [nu, nv] = result.fixed_surface.num_control_points();
EXPECT_GT(nu, 0);
EXPECT_GT(nv, 0);
EXPECT_GE(result.total_discontinuities, 0);
}