Files

274 lines
9.7 KiB
C++
Raw Permalink Normal View History

#include <gtest/gtest.h>
#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 <cmath>
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<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);
}
/// 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<int>(ckr.intensity.size()), 21); // res_u+1
EXPECT_EQ(static_cast<int>(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());
}