/** * @file test_surface_editing.cpp * @brief 曲面编辑三件套测试:match_surface / shape_fillet / control_point_edit */ #include #include "vde/curves/surface_editing.h" #include "vde/curves/control_point_edit.h" #include "vde/curves/nurbs_surface.h" #include "vde/core/point.h" #include namespace vde::curves { namespace test { using core::Point3D; using core::Vector3D; // ── Helper: create a simple biquadratic plane surface ── NurbsSurface make_test_plane() { // 3×3 control grid on z=0 plane std::vector> grid = { {{0, 0, 0}, {1, 0, 0}, {2, 0, 0}}, {{0, 1, 0}, {1, 1, 0}, {2, 1, 0}}, {{0, 2, 0}, {1, 2, 0}, {2, 2, 0}} }; return NurbsSurface(grid, {0, 0, 0, 1, 1, 1}, // u-knots, degree=2 {0, 0, 0, 1, 1, 1}, // v-knots, degree=2 {}, 2, 2); // weights (empty = all 1.0) } // ── Helper: create a slightly curved surface (degree 2×2) ── NurbsSurface make_test_curved() { // 3×3 grid, slightly curved in z std::vector> grid = { {{0, 0, 0}, {1, 0, 0.5}, {2, 0, 0}}, {{0, 1, 0.5}, {1, 1, 1.0}, {2, 1, 0.5}}, {{0, 2, 0}, {1, 2, 0.5}, {2, 2, 0}} }; return NurbsSurface(grid, {0, 0, 0, 1, 1, 1}, {0, 0, 0, 1, 1, 1}, {}, 2, 2); } // ═══════════════════════════════════════════════════════════ // match_surface 测试 // ═══════════════════════════════════════════════════════════ TEST(SurfaceEditingTest, MatchSurfaceG0Planar) { // Two identical planes on z=0 — matching should produce no error auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G0; opts.boundary_samples = 10; auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts); EXPECT_TRUE(result.g0_ok); EXPECT_LT(result.max_position_error, 1e-4); EXPECT_EQ(result.rows_adjusted, 1); EXPECT_EQ(result.control_displacements.size(), 9u); // 3×3 grid } TEST(SurfaceEditingTest, MatchSurfaceG0DifferentSurfaces) { // Two slightly different curved surfaces — match VMin edge auto surf_a = make_test_curved(); // Modify a few control points to make it different auto surf_b = make_test_curved(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G0; opts.boundary_samples = 10; auto result = match_surface(surf_b, surf_a, BoundaryEdge::VMin, opts); // Should produce a valid surface auto [nu, nv] = result.matched_surface.num_control_points(); EXPECT_GE(nu, 1); EXPECT_GE(nv, 1); // Control points should be adjusted EXPECT_GT(result.control_displacements.size(), 0u); } TEST(SurfaceEditingTest, MatchSurfaceG1Planar) { auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G1; opts.boundary_samples = 10; auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts); EXPECT_TRUE(result.g0_ok); EXPECT_TRUE(result.g1_ok); EXPECT_EQ(result.rows_adjusted, 1); } TEST(SurfaceEditingTest, MatchSurfaceG1DifferentSurfaces) { auto surf_a = make_test_curved(); auto surf_b = make_test_curved(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G1; opts.boundary_samples = 10; auto result = match_surface(surf_b, surf_a, BoundaryEdge::VMin, opts); // Check that errors are computed EXPECT_GE(result.max_position_error, 0.0); EXPECT_GE(result.max_tangent_error, 0.0); EXPECT_EQ(result.rows_adjusted, 1); } TEST(SurfaceEditingTest, MatchSurfaceG2Planar) { auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G2; opts.boundary_samples = 10; auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts); EXPECT_TRUE(result.g0_ok); EXPECT_EQ(result.rows_adjusted, 2); } TEST(SurfaceEditingTest, MatchSurfaceG3Planar) { auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G3; opts.boundary_samples = 10; auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts); EXPECT_TRUE(result.g0_ok); EXPECT_EQ(result.rows_adjusted, 3); // G3 adjusts 3 rows } TEST(SurfaceEditingTest, MatchSurfaceAllEdges) { auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G1; // Test all 4 boundary edges for (int e = 0; e < 4; ++e) { BoundaryEdge edge = static_cast(e); auto result = match_surface(plane_b, plane_a, edge, opts); EXPECT_TRUE(result.g0_ok) << "Failed on edge " << e; } } TEST(SurfaceEditingTest, MatchSurfaceExplicitRows) { auto plane_a = make_test_plane(); auto plane_b = make_test_plane(); MatchSurfaceOptions opts; opts.continuity = ContinuityLevel::G2; opts.rows_to_adjust = 1; // override, only 1 row opts.boundary_samples = 10; auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts); EXPECT_EQ(result.rows_adjusted, 1); } // ═══════════════════════════════════════════════════════════ // control_point_edit 测试 // ═══════════════════════════════════════════════════════════ TEST(SurfaceEditingTest, ControlPointEditSingle) { auto surf = make_test_plane(); std::vector> indices = {{1, 1}}; // center control point std::vector new_pos = {{1, 1, 5}}; // lift up in z auto result = control_point_edit(surf, indices, new_pos); EXPECT_EQ(result.points_modified, 1); EXPECT_GT(result.max_displacement, 4.0); // moved ~5 units in z EXPECT_TRUE(result.weights_preserved); // Verify the control point was moved auto modified_cp = result.modified_surface.control_points(); EXPECT_NEAR(modified_cp[1][1].z(), 5.0, 1e-6); // Other control points unchanged EXPECT_NEAR(modified_cp[0][0].z(), 0.0, 1e-6); } TEST(SurfaceEditingTest, ControlPointEditMultiple) { auto surf = make_test_plane(); std::vector> indices = {{0, 0}, {2, 2}}; std::vector new_pos = {{0, 0, 3}, {3, 3, 3}}; auto result = control_point_edit(surf, indices, new_pos); EXPECT_EQ(result.points_modified, 2); EXPECT_GT(result.max_displacement, 2.0); auto cp = result.modified_surface.control_points(); EXPECT_NEAR(cp[0][0].z(), 3.0, 1e-6); EXPECT_NEAR(cp[2][2].z(), 3.0, 1e-6); // Center unchanged EXPECT_NEAR(cp[1][1].z(), 0.0, 1e-6); } TEST(SurfaceEditingTest, ControlPointEditEmpty) { auto surf = make_test_plane(); std::vector> indices; std::vector new_pos; auto result = control_point_edit(surf, indices, new_pos); EXPECT_EQ(result.points_modified, 0); EXPECT_NEAR(result.max_displacement, 0.0, 1e-12); // Should return a valid copy of original auto cp = result.modified_surface.control_points(); EXPECT_EQ(cp.size(), 3u); EXPECT_NEAR(cp[0][0].z(), 0.0, 1e-6); } TEST(SurfaceEditingTest, ControlPointEditPreservesKnots) { auto surf = make_test_plane(); std::vector> indices = {{0, 0}}; std::vector new_pos = {{-1, -1, 0}}; auto result = control_point_edit(surf, indices, new_pos); // Knot vectors preserved const auto& ku = result.modified_surface.knots_u(); const auto& kv = result.modified_surface.knots_v(); EXPECT_EQ(ku.size(), 6u); EXPECT_EQ(kv.size(), 6u); EXPECT_EQ(result.modified_surface.degree_u(), 2); EXPECT_EQ(result.modified_surface.degree_v(), 2); } } // namespace test } // namespace vde::curves