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
304 lines
11 KiB
C++
304 lines
11 KiB
C++
#include <gtest/gtest.h>
|
|
#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 <cmath>
|
|
|
|
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<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);
|
|
}
|
|
|
|
static NurbsSurface make_degree2_surface() {
|
|
std::vector<std::vector<Point3D>> 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<NurbsCurve> 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<NurbsCurve> 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<NurbsCurve> boundaries;
|
|
std::vector<Point3D> 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<NurbsCurve> 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<NurbsCurve> 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<std::vector<Point3D>> 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<std::vector<Point3D>> 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<std::vector<Point3D>> 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<std::vector<Point3D>> 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));
|
|
}
|