#include #include "vde/curves/class_a_surfacing.h" #include "vde/curves/nurbs_curve.h" #include "vde/curves/nurbs_surface.h" #include using namespace vde::curves; using namespace vde::core; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /// 创建平面 NURBS 曲面(XY 平面,[0,1]×[0,1]) static NurbsSurface plane_surface() { std::vector> 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> 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> grid(5, std::vector(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> w(5, std::vector(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> 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 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(result.band_mask.size()), 1); } TEST(ClassASurfacingTest, HighlightLines_MultipleLights) { auto s = quad_surface(); std::vector 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(result.band_mask.size()), 3); EXPECT_EQ(static_cast(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 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(result.illumination.size()), 17); EXPECT_EQ(static_cast(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 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 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 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 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); }