#include #include "vde/curves/curve_tools.h" #include "vde/curves/surface_analysis.h" #include "vde/curves/nurbs_curve.h" #include "vde/curves/nurbs_surface.h" #include using namespace vde::curves; using namespace vde::core; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /// Create a planar NURBS surface on XY plane over [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); } /// Create a NURBS line segment static NurbsCurve line(const Point3D& a, const Point3D& b) { return NurbsCurve({a, b}, {0, 0, 1, 1}, {1, 1}, 1); } /// Create a cubic NURBS curve static NurbsCurve cubic_curve() { return NurbsCurve( {Point3D(0,0,0), Point3D(1,2,0), Point3D(2,1,0), Point3D(3,0,0)}, {0,0,0,0,1,1,1,1}, {1,1,1,1}, 3); } // --------------------------------------------------------------------------- // Test 1: project_curve_on_surface (via curve_tools.h inclusion) // --------------------------------------------------------------------------- TEST(CurveToolsTest, ProjectCurveOnSurface_PlaneProjection) { auto surf = plane_surface(); auto curve = line(Point3D(0.2, 0.3, 1.0), Point3D(0.8, 0.7, 2.0)); // Project the 3D curve onto the XY plane surface auto pcurve = project_curve_on_surface(curve, surf, 50); // pcurve should be a valid NURBS (non-degenerate) EXPECT_GE(pcurve.degree(), 0); auto dom = pcurve.domain(); EXPECT_LE(dom.first, dom.second); } // --------------------------------------------------------------------------- // Test 2: parallel_curve — straight line offset // --------------------------------------------------------------------------- TEST(CurveToolsTest, ParallelCurve_StraightLineOffset) { auto c = line(Point3D(0, 0, 0), Point3D(10, 0, 0)); // Offset by 1 in Y direction (plane normal = +Z) auto offset = parallel_curve(c, 1.0, Vector3D(0, 0, 1), 200); EXPECT_GT(offset.degree(), 0); // Midpoint should be (5, 1, 0) — shifted by 1 in Y auto dom = offset.domain(); double t_mid = (dom.first + dom.second) * 0.5; Point3D pm = offset.evaluate(t_mid); EXPECT_NEAR(pm.x(), 5.0, 0.5); EXPECT_NEAR(pm.y(), 1.0, 0.3); EXPECT_NEAR(pm.z(), 0.0, 0.1); } // --------------------------------------------------------------------------- // Test 3: parallel_curve — negative offset // --------------------------------------------------------------------------- TEST(CurveToolsTest, ParallelCurve_NegativeOffset) { auto c = line(Point3D(2, 0, 0), Point3D(2, 8, 0)); // Offset by -1 in X direction (plane normal = +Z, tangent = +Y → offset_dir = n×tangent = -X) // distance=-1 offsets opposite to offset_dir, i.e., in +X auto offset = parallel_curve(c, -1.0, Vector3D(0, 0, 1), 200); EXPECT_GT(offset.degree(), 0); auto dom = offset.domain(); double t_mid = (dom.first + dom.second) * 0.5; Point3D pm = offset.evaluate(t_mid); // Midpoint should be shifted from x=2 in the offset direction EXPECT_NEAR(pm.x(), 3.0, 0.5); EXPECT_NEAR(pm.y(), 4.0, 1.0); } // --------------------------------------------------------------------------- // Test 4: connect_curve — G1 continuity // --------------------------------------------------------------------------- TEST(CurveToolsTest, ConnectCurve_G1_BasicConnect) { auto c1 = line(Point3D(0, 0, 0), Point3D(5, 0, 0)); auto c2 = line(Point3D(10, 0, 0), Point3D(15, 0, 0)); auto bridge = connect_curve(c1, c2, Continuity::G1); EXPECT_GT(bridge.degree(), 0); // Endpoints should match auto dom = bridge.domain(); Point3D p_start = bridge.evaluate(dom.first); Point3D p_end = bridge.evaluate(dom.second); EXPECT_NEAR(p_start.x(), 5.0, 0.5); EXPECT_NEAR(p_start.y(), 0.0, 0.5); EXPECT_NEAR(p_end.x(), 10.0, 0.5); EXPECT_NEAR(p_end.y(), 0.0, 0.5); } // --------------------------------------------------------------------------- // Test 5: connect_curve — G2 continuity // --------------------------------------------------------------------------- TEST(CurveToolsTest, ConnectCurve_G2_HigherContinuity) { auto c1 = cubic_curve(); // ends near (3,0,0) auto c2 = line(Point3D(6, 0, 0), Point3D(9, 0, 0)); // starts at (6,0,0) auto bridge = connect_curve(c1, c2, Continuity::G2); EXPECT_GT(bridge.degree(), 0); } // --------------------------------------------------------------------------- // Test 6: connect_curve — G3 continuity // --------------------------------------------------------------------------- TEST(CurveToolsTest, ConnectCurve_G3_HighestContinuity) { auto c1 = cubic_curve(); // ends near (3,0,0) auto c2 = line(Point3D(8, 2, 0), Point3D(12, 2, 0)); // starts at (8,2,0) auto bridge = connect_curve(c1, c2, Continuity::G3); EXPECT_GT(bridge.degree(), 0); } // --------------------------------------------------------------------------- // Test 7: helix_curve — basic helix // --------------------------------------------------------------------------- TEST(CurveToolsTest, HelixCurve_BasicHelix) { auto helix = helix_curve( Point3D(0, 0, 0), // axis origin Vector3D(0, 0, 1), // axis direction (Z-up) 5.0, // radius 2.0, // pitch 10.0, // height 100); // samples per turn EXPECT_GT(helix.degree(), 0); // Check start point — should be near the helix start (radius distance from axis) auto dom = helix.domain(); Point3D p0 = helix.evaluate(dom.first); double r0 = std::sqrt(p0.x() * p0.x() + p0.y() * p0.y()); EXPECT_NEAR(r0, 5.0, 0.5); EXPECT_NEAR(p0.z(), 0.0, 0.5); // Check end point — should be near the top Point3D p1 = helix.evaluate(dom.second); double r1 = std::sqrt(p1.x() * p1.x() + p1.y() * p1.y()); EXPECT_NEAR(r1, 5.0, 0.5); EXPECT_NEAR(p1.z(), 10.0, 0.5); } // --------------------------------------------------------------------------- // Test 8: helix_curve — zero pitch / height edge case // --------------------------------------------------------------------------- TEST(CurveToolsTest, HelixCurve_ZeroPitchReturnsEmpty) { auto helix = helix_curve( Point3D(0, 0, 0), Vector3D(0, 0, 1), 2.0, 0.0, 5.0); // pitch = 0 → invalid // Should return empty curve EXPECT_EQ(helix.degree(), 0); } // --------------------------------------------------------------------------- // Test 9: isoparametric_curves — basic extraction // --------------------------------------------------------------------------- TEST(CurveToolsTest, IsoparametricCurves_BasicExtraction) { auto surf = plane_surface(); auto iso = isoparametric_curves(surf, 3, 3); EXPECT_EQ(iso.u_curves.size(), 3u); EXPECT_EQ(iso.v_curves.size(), 3u); // Each extracted curve should be valid for (const auto& cv : iso.u_curves) { EXPECT_GT(cv.degree(), 0); } for (const auto& cv : iso.v_curves) { EXPECT_GT(cv.degree(), 0); } } // --------------------------------------------------------------------------- // Test 10: isoparametric_curves — single curve // --------------------------------------------------------------------------- TEST(CurveToolsTest, IsoparametricCurves_SingleCurve) { auto surf = plane_surface(); auto iso = isoparametric_curves(surf, 1, 1); EXPECT_EQ(iso.u_curves.size(), 1u); EXPECT_EQ(iso.v_curves.size(), 1u); } // --------------------------------------------------------------------------- // Test 11: surface_analysis — inflection_lines // --------------------------------------------------------------------------- TEST(CurveToolsTest, InflectionLines_PlaneNoInflections) { auto surf = plane_surface(); auto result = inflection_lines(surf, 30, 30); // Plane has Gaussian curvature ≡ 0 everywhere → no sign change → no inflections EXPECT_EQ(result.segments.size(), 0u); } // --------------------------------------------------------------------------- // Test 12: surface_analysis — checker_mapping // --------------------------------------------------------------------------- TEST(CurveToolsTest, CheckerMapping_OutputDimensions) { auto surf = plane_surface(); auto ckr = checker_mapping(surf, Vector3D(1, 0, 0), 20, 15); EXPECT_EQ(ckr.res_u, 20); EXPECT_EQ(ckr.res_v, 15); EXPECT_EQ(static_cast(ckr.intensity.size()), 21); // res_u+1 EXPECT_EQ(static_cast(ckr.intensity[0].size()), 16); // res_v+1 } TEST(CurveToolsTest, CheckerMapping_IntensityRange) { auto surf = plane_surface(); auto ckr = checker_mapping(surf, Vector3D(0.5, 0.3, 1.0), 15, 15); for (const auto& row : ckr.intensity) { for (double val : row) { EXPECT_GE(val, 0.0); EXPECT_LE(val, 1.0); } } } // --------------------------------------------------------------------------- // Test 13: surface_analysis — surface_checker_report // --------------------------------------------------------------------------- TEST(CurveToolsTest, SurfaceCheckerReport_PlaneIsSmooth) { auto surf = plane_surface(); auto report = surface_checker_report(surf, 30); // Plane is perfectly smooth — should get grade A EXPECT_GT(report.checker_uniformity, 0.5); EXPECT_GT(report.curvature_continuity, 0.5); EXPECT_GE(report.gaussian_range, 0.0); // Description should be non-empty EXPECT_FALSE(report.description.empty()); }