73df04d5cb
M2.1 — G2/G3 连续性分析 (Agent #0): - surface_continuity.h/.cpp: G0/G1/G2/G3 curve/surface detection - Weingarten equation for curvature, Frénet frame, zebra stripe - surface_analysis.h/.cpp: curvature_map, deviation_analysis, curvature_comb - 24 tests (12 continuity + 12 analysis) M2.2 — 曲面延伸 + N边填充 (Agent #1): - surface_extension.h/.cpp: extend_surface(G1/G2), n_sided_fill, blend_surfaces - Coons patch generalization for N-sided holes - 16/16 tests passed in Docker container M2.3 — 高级过渡曲面 (Agent #2): - advanced_blend.h/.cpp: real implementations replacing stubs - variable_radius_blend, multi_face_blend, rolling_ball_blend, face_face_blend - Ball-rolling envelope + corner sphere filling - 15+ tests with validate() verification
196 lines
7.6 KiB
C++
196 lines
7.6 KiB
C++
#include <gtest/gtest.h>
|
||
#include "vde/curves/surface_continuity.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 degree-1 NURBS line from a to b
|
||
static NurbsCurve line(const Point3D& a, const Point3D& b) {
|
||
return NurbsCurve({a, b}, {0, 0, 1, 1}, {1, 1}, 1);
|
||
}
|
||
|
||
/// 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 simple NURBS surface from a 2×2 grid
|
||
static NurbsSurface simple_surface(const std::vector<std::vector<Point3D>>& grid,
|
||
int du=1, int dv=1) {
|
||
int rows = static_cast<int>(grid.size());
|
||
int cols = static_cast<int>(grid.empty() ? 0 : grid[0].size());
|
||
std::vector<double> ku(rows + du + 1, 0.0);
|
||
std::vector<double> kv(cols + dv + 1, 0.0);
|
||
for (int i = 0; i <= du; ++i) ku[i] = 0.0;
|
||
for (size_t i = du + 1; i < ku.size() - du - 1; ++i)
|
||
ku[i] = static_cast<double>(i - du) / (ku.size() - 2*du - 1);
|
||
for (size_t i = ku.size() - du - 1; i < ku.size(); ++i) ku[i] = 1.0;
|
||
for (int i = 0; i <= dv; ++i) kv[i] = 0.0;
|
||
for (size_t i = dv + 1; i < kv.size() - dv - 1; ++i)
|
||
kv[i] = static_cast<double>(i - dv) / (kv.size() - 2*dv - 1);
|
||
for (size_t i = kv.size() - dv - 1; i < kv.size(); ++i) kv[i] = 1.0;
|
||
|
||
std::vector<std::vector<double>> w(rows, std::vector<double>(cols, 1.0));
|
||
return NurbsSurface(grid, ku, kv, w, du, dv);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: to_string
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(ContinuityTest, ToString) {
|
||
EXPECT_STREQ("G0", to_string(ContinuityLevel::G0));
|
||
EXPECT_STREQ("G1", to_string(ContinuityLevel::G1));
|
||
EXPECT_STREQ("G2", to_string(ContinuityLevel::G2));
|
||
EXPECT_STREQ("G3", to_string(ContinuityLevel::G3));
|
||
EXPECT_STREQ("None", to_string(ContinuityLevel::None));
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: G0 — Position continuity on lines
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(ContinuityTest, G0_SameCurveContinuous) {
|
||
// Two identical line segments should be G1 (same tangent direction)
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(1, 0, 0), Point3D(2, 0, 0));
|
||
auto level = continuity_type(a, b);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G1));
|
||
}
|
||
|
||
TEST(ContinuityTest, G0_EndpointsMatch) {
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(1, 0, 0), Point3D(1, 1, 0));
|
||
auto level = continuity_type(a, b);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G0));
|
||
}
|
||
|
||
TEST(ContinuityTest, G0_NoConnection) {
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(5, 5, 5), Point3D(6, 6, 6));
|
||
auto level = continuity_type(a, b);
|
||
EXPECT_EQ(ContinuityLevel::None, level);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: G1 — Tangent continuity
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(ContinuityTest, G1_CollinearLines) {
|
||
// Two collinear lines → G0 position, G1 tangent
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(1, 0, 0), Point3D(2, 0, 0));
|
||
// Check at the connection point with explicit t values
|
||
auto level = continuity_type(a, 1.0, b, 0.0);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G1));
|
||
}
|
||
|
||
TEST(ContinuityTest, G1_SharpCornerG0Only) {
|
||
// Two lines meeting at a right angle → only G0
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(1, 0, 0), Point3D(1, 1, 0));
|
||
auto level = continuity_type(a, 1.0, b, 0.0);
|
||
EXPECT_EQ(ContinuityLevel::G0, level);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: G2 — Curvature continuity on curves
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(ContinuityTest, G2_StraightLinesG3) {
|
||
// Two collinear straight lines have zero curvature everywhere → G3
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(1, 0, 0), Point3D(3, 0, 0));
|
||
auto level = continuity_type(a, 1.0, b, 0.0);
|
||
// Straight lines have d2=0, curvature=0, so they trivially match → G3
|
||
EXPECT_EQ(ContinuityLevel::G3, level);
|
||
}
|
||
|
||
TEST(ContinuityTest, G0_DisconnectedReturnsNone) {
|
||
auto a = line(Point3D(0, 0, 0), Point3D(1, 0, 0));
|
||
auto b = line(Point3D(10, 10, 10), Point3D(11, 11, 11));
|
||
auto level = continuity_type(a, 0.5, b, 0.5);
|
||
EXPECT_EQ(ContinuityLevel::None, level);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: Surface Continuity
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(SurfaceContinuityTest, IdenticalPlanes) {
|
||
// Two identical flat planes → should be G3
|
||
auto s1 = plane_surface();
|
||
auto s2 = plane_surface();
|
||
auto level = surface_continuity(s1, s2, 10);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G3));
|
||
}
|
||
|
||
TEST(SurfaceContinuityTest, CoplanarSurfacesEdgeMatch) {
|
||
// Two adjacent coplanar surfaces sharing an edge
|
||
std::vector<std::vector<Point3D>> g1 = {
|
||
{Point3D(0,0,0), Point3D(0,1,0)},
|
||
{Point3D(1,0,0), Point3D(1,1,0)}
|
||
};
|
||
std::vector<std::vector<Point3D>> g2 = {
|
||
{Point3D(1,0,0), Point3D(1,1,0)},
|
||
{Point3D(2,0,0), Point3D(2,1,0)}
|
||
};
|
||
auto s1 = simple_surface(g1);
|
||
auto s2 = simple_surface(g2);
|
||
auto level = surface_continuity(s1, s2, 10);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G3));
|
||
}
|
||
|
||
TEST(SurfaceContinuityTest, DisconnectedSurfaces) {
|
||
std::vector<std::vector<Point3D>> g1 = {
|
||
{Point3D(0,0,0), Point3D(0,1,0)},
|
||
{Point3D(1,0,0), Point3D(1,1,0)}
|
||
};
|
||
std::vector<std::vector<Point3D>> g2 = {
|
||
{Point3D(10,0,0), Point3D(10,1,0)},
|
||
{Point3D(11,0,0), Point3D(11,1,0)}
|
||
};
|
||
auto s1 = simple_surface(g1);
|
||
auto s2 = simple_surface(g2);
|
||
auto level = surface_continuity(s1, s2, 10);
|
||
EXPECT_EQ(ContinuityLevel::None, level);
|
||
}
|
||
|
||
TEST(SurfaceContinuityTest, ContinuityReport) {
|
||
auto s1 = plane_surface();
|
||
auto s2 = plane_surface();
|
||
auto report = surface_continuity_report(s1, s2, 20);
|
||
EXPECT_GE(static_cast<int>(report.worst), static_cast<int>(ContinuityLevel::G3));
|
||
EXPECT_NEAR(report.g1_ratio, 1.0, 0.01);
|
||
EXPECT_NEAR(report.g2_ratio, 1.0, 0.01);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Test: Edge parameter overload
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST(SurfaceContinuityTest, ExplicitEdgeParametersPlane) {
|
||
auto s1 = plane_surface();
|
||
auto s2 = plane_surface();
|
||
|
||
// Same physical edge (u=1) on both identical surfaces
|
||
auto level = surface_continuity(
|
||
s1, s2,
|
||
{{1.0, 0.0}, {1.0, 1.0}}, // s1: u=1 edge, v 0→1
|
||
{{1.0, 0.0}, {1.0, 1.0}}, // s2: same u=1 edge
|
||
20);
|
||
EXPECT_GE(static_cast<int>(level), static_cast<int>(ContinuityLevel::G3));
|
||
}
|