#include #include "vde/curves/surface_extension.h" #include "vde/curves/nurbs_operations.h" #include "vde/curves/nurbs_curve.h" #include "vde/curves/nurbs_surface.h" #include using namespace vde::curves; using namespace vde::core; // =========================================================================== // Helpers // =========================================================================== static NurbsCurve make_line_curve(const Point3D& a, const Point3D& b) { return NurbsCurve({a, b}, {0, 0, 1, 1}, {1, 1}, 1); } static NurbsSurface make_planar_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); } static NurbsSurface make_degree2_surface() { std::vector> grid = { {Point3D(0, 0, 0), Point3D(0, 1, 0.3), Point3D(0, 2, 0)}, {Point3D(1, 0, 0.3), Point3D(1, 1, 0.8), Point3D(1, 2, 0.3)}, {Point3D(2, 0, 0), Point3D(2, 1, 0.3), Point3D(2, 2, 0)} }; return NurbsSurface(grid, {0, 0, 0, 1, 1, 1}, {0, 0, 0, 1, 1, 1}, {}, 2, 2); } // =========================================================================== // extend_surface — G1 延伸 // =========================================================================== TEST(SurfaceExtensionTest, ExtendG1_Umax_AddsRow) { auto plane = make_planar_surface(); auto extended = extend_surface(plane, 1, 0.5, Continuity::G1); // Should have one extra row of control points auto [nu, nv] = extended.num_control_points(); EXPECT_EQ(nu, 3); // original 2 + 1 new row EXPECT_EQ(nv, 2); // Extended surface should evaluate to finite values throughout for (double u = 0.0; u <= 1.0; u += 0.25) { for (double v = 0.0; v <= 1.0; v += 0.25) { Point3D p = extended.evaluate(u, v); EXPECT_TRUE(std::isfinite(p.x())); EXPECT_TRUE(std::isfinite(p.y())); EXPECT_TRUE(std::isfinite(p.z())); } } } TEST(SurfaceExtensionTest, ExtendG1_Vmin_AddsColumn) { auto plane = make_planar_surface(); auto extended = extend_surface(plane, 2, 1.0, Continuity::G1); auto [nu, nv] = extended.num_control_points(); EXPECT_EQ(nu, 2); EXPECT_EQ(nv, 3); // original 2 + 1 new column // All points should be finite Point3D p = extended.evaluate(0.5, 0.5); EXPECT_TRUE(std::isfinite(p.x())); EXPECT_TRUE(std::isfinite(p.y())); EXPECT_TRUE(std::isfinite(p.z())); } TEST(SurfaceExtensionTest, ExtendG1_ZeroDistance) { auto plane = make_planar_surface(); auto same = extend_surface(plane, 0, 0.0, Continuity::G1); // Should return the same surface Point3D p1 = plane.evaluate(0.5, 0.5); Point3D p2 = same.evaluate(0.5, 0.5); EXPECT_NEAR((p1 - p2).norm(), 0.0, 1e-10); } // =========================================================================== // extend_surface — G2 延伸 // =========================================================================== TEST(SurfaceExtensionTest, ExtendG2_Umax_AddsTwoRows) { auto plane = make_planar_surface(); auto extended = extend_surface(plane, 1, 0.5, Continuity::G2); auto [nu, nv] = extended.num_control_points(); EXPECT_EQ(nu, 4); // original 2 + 2 new rows EXPECT_EQ(nv, 2); } TEST(SurfaceExtensionTest, ExtendG2_PlanarStaysPlanar) { auto plane = make_planar_surface(); auto extended = extend_surface(plane, 1, 1.0, Continuity::G2); // All points should have z=0 (planar extension of XY plane) for (double u = 0.0; u <= 1.0; u += 0.25) { for (double v = 0.0; v <= 1.0; v += 0.25) { Point3D p = extended.evaluate(u, v); EXPECT_NEAR(p.z(), 0.0, 1e-6); } } } TEST(SurfaceExtensionTest, ExtendG2_VmaxDegree2_AddsTwoCols) { auto surf = make_degree2_surface(); auto extended = extend_surface(surf, 3, 0.5, Continuity::G2); // Should have original 3 + 2 new columns = 5 in v auto [nu, nv] = extended.num_control_points(); EXPECT_EQ(nu, 3); EXPECT_EQ(nv, 5); // Evaluate at valid parameters Point3D p = extended.evaluate(0.5, 0.5); EXPECT_TRUE(std::isfinite(p.x())); EXPECT_TRUE(std::isfinite(p.y())); EXPECT_TRUE(std::isfinite(p.z())); } TEST(SurfaceExtensionTest, ExtendG1_PlanarPreservesFlatness) { auto plane = make_planar_surface(); auto extended = extend_surface(plane, 1, 0.5, Continuity::G1); // Extended surface should remain planar for (double u = 0.0; u <= 1.0; u += 0.25) { for (double v = 0.0; v <= 1.0; v += 0.25) { Point3D p = extended.evaluate(u, v); EXPECT_NEAR(p.z(), 0.0, 1e-6); } } } // =========================================================================== // n_sided_fill — N 边填充 // =========================================================================== TEST(SurfaceExtensionTest, FillNSided_TriangleG2) { auto l1 = make_line_curve(Point3D(0, 0, 0), Point3D(2, 0, 0)); auto l2 = make_line_curve(Point3D(2, 0, 0), Point3D(1, 2, 0)); auto l3 = make_line_curve(Point3D(1, 2, 0), Point3D(0, 0, 0)); std::vector boundaries = {l1, l2, l3}; auto fill = n_sided_fill(boundaries, Continuity::G2); // Should produce a valid surface Point3D mid = fill.evaluate(0.5, 0.5); EXPECT_TRUE(std::isfinite(mid.x())); EXPECT_TRUE(std::isfinite(mid.y())); EXPECT_TRUE(std::isfinite(mid.z())); // Center should be roughly inside the triangle EXPECT_GT(mid.x(), 0.0); EXPECT_LT(mid.x(), 2.0); EXPECT_GT(mid.y(), 0.0); EXPECT_LT(mid.y(), 2.0); } TEST(SurfaceExtensionTest, FillNSided_Quadrilateral) { auto l1 = make_line_curve(Point3D(0, 0, 0), Point3D(3, 0, 0)); auto l2 = make_line_curve(Point3D(3, 0, 0), Point3D(3, 2, 0)); auto l3 = make_line_curve(Point3D(3, 2, 0), Point3D(0, 2, 0)); auto l4 = make_line_curve(Point3D(0, 2, 0), Point3D(0, 0, 0)); std::vector boundaries = {l1, l2, l3, l4}; auto fill = n_sided_fill(boundaries, Continuity::G1); // Center should be inside the rectangle Point3D mid = fill.evaluate(0.5, 0.5); EXPECT_GT(mid.x(), 0.5); EXPECT_LT(mid.x(), 2.5); EXPECT_GT(mid.y(), 0.0); EXPECT_LT(mid.y(), 2.0); EXPECT_NEAR(mid.z(), 0.0, 1e-6); } TEST(SurfaceExtensionTest, FillNSided_Pentagon) { // Regular pentagon in XY plane std::vector boundaries; std::vector pts; for (int i = 0; i < 5; ++i) { double angle = 2.0 * M_PI * i / 5.0 - M_PI / 2.0; pts.push_back(Point3D(std::cos(angle), std::sin(angle), 0)); } for (int i = 0; i < 5; ++i) { boundaries.push_back(make_line_curve(pts[i], pts[(i + 1) % 5])); } auto fill = n_sided_fill(boundaries, Continuity::G2); // Center should be near origin Point3D mid = fill.evaluate(0.5, 0.5); EXPECT_NEAR(mid.x(), 0.0, 0.5); EXPECT_NEAR(mid.y(), 0.0, 0.5); EXPECT_NEAR(mid.z(), 0.0, 1e-6); } TEST(SurfaceExtensionTest, FillNSided_TriangleG1) { auto l1 = make_line_curve(Point3D(0, 0, 0), Point3D(2, 0, 0)); auto l2 = make_line_curve(Point3D(2, 0, 0), Point3D(0, 2, 0)); auto l3 = make_line_curve(Point3D(0, 2, 0), Point3D(0, 0, 0)); std::vector boundaries = {l1, l2, l3}; auto fill = n_sided_fill(boundaries, Continuity::G1); // Fill surface should be finite and pass through boundary for (double u = 0.0; u <= 1.0; u += 0.25) { for (double v = 0.0; v <= 1.0; v += 0.25) { Point3D p = fill.evaluate(u, v); EXPECT_TRUE(std::isfinite(p.x())); EXPECT_TRUE(std::isfinite(p.y())); EXPECT_TRUE(std::isfinite(p.z())); } } } TEST(SurfaceExtensionTest, FillNSided_DegenerateEdge) { // Only 2 curves → should return degenerate auto l1 = make_line_curve(Point3D(0, 0, 0), Point3D(1, 0, 0)); auto l2 = make_line_curve(Point3D(1, 0, 0), Point3D(0, 1, 0)); std::vector boundaries = {l1, l2}; auto fill = n_sided_fill(boundaries, Continuity::G1); auto [nu, nv] = fill.num_control_points(); // With < 3 curves, returns empty surface EXPECT_TRUE(nu == 0 || nu == 1); } // =========================================================================== // blend_surfaces — 两面过渡(自动检测公共边) // =========================================================================== TEST(SurfaceExtensionTest, BlendSurfacesAutoDetect_Adjacent) { auto plane1 = make_planar_surface(); std::vector> grid2 = { {Point3D(1, 0, 0), Point3D(1, 1, 0)}, {Point3D(2, 0, 0), Point3D(2, 1, 0)} }; NurbsSurface plane2(grid2, {0, 0, 1, 1}, {0, 0, 1, 1}, {}, 1, 1); auto blend = blend_surfaces(plane1, plane2, 0.3); // Should produce a valid blend surface auto [nu, nv] = blend.num_control_points(); EXPECT_GE(nu, 2); EXPECT_GE(nv, 2); // Blend surface should evaluate to finite values Point3D p = blend.evaluate(0.5, 0.5); EXPECT_TRUE(std::isfinite(p.x())); EXPECT_TRUE(std::isfinite(p.y())); EXPECT_TRUE(std::isfinite(p.z())); } TEST(SurfaceExtensionTest, BlendSurfacesNoCommonBoundary) { auto plane1 = make_planar_surface(); std::vector> grid2 = { {Point3D(3, 0, 0), Point3D(3, 1, 0)}, {Point3D(4, 0, 0), Point3D(4, 1, 0)} }; NurbsSurface plane2(grid2, {0, 0, 1, 1}, {0, 0, 1, 1}, {}, 1, 1); auto blend = blend_surfaces(plane1, plane2, 0.3); auto [nu, nv] = blend.num_control_points(); // No common boundary → degenerate EXPECT_TRUE(nu == 0 || nv == 0); } // =========================================================================== // is_g1_continuous — G1 连续性检查 // =========================================================================== TEST(SurfaceExtensionTest, G1Continuous_AdjacentPlanes) { auto plane1 = make_planar_surface(); std::vector> grid2 = { {Point3D(1, 0, 0), Point3D(1, 1, 0)}, {Point3D(2, 0, 0), Point3D(2, 1, 0)} }; NurbsSurface plane2(grid2, {0, 0, 1, 1}, {0, 0, 1, 1}, {}, 1, 1); // They share x=1 boundary: plane1 umax (edge=1) matches plane2 umin (edge=0) EXPECT_TRUE(is_g1_continuous(plane1, plane2, 1)); } TEST(SurfaceExtensionTest, G1Continuous_NonMatching) { auto plane1 = make_planar_surface(); // plane2 is angled → normals won't match std::vector> grid2 = { {Point3D(1, 0, 0), Point3D(1, 1, 1)}, {Point3D(2, 0, 0), Point3D(2, 1, 1)} }; NurbsSurface plane2(grid2, {0, 0, 1, 1}, {0, 0, 1, 1}, {}, 1, 1); // Normals differ → not G1 EXPECT_FALSE(is_g1_continuous(plane1, plane2, 1)); }