bb0029234f
M1 — Generative Surfaces: - sweep_surface: Explicit/Spine/TwoGuides modes - loft_surface: multi-section interpolation + guide + tangent constraints - net_surface: bidirectional curve grid → NURBS M3 — Curve Tools + Analysis: - curve_tools: project_curve, parallel_curve, connect_curve(G1-G3), helix, isoparametric - surface_analysis: inflection_lines, checker_mapping, surface_checker_report (A/B/C/D) - 14/14 tests passed Fixed: constraint_solver.h duplicate declaration, fea_mesh.h comment syntax Pending: M2 surface editing (retrying)
250 lines
8.2 KiB
C++
250 lines
8.2 KiB
C++
/**
|
||
* @file test_surface_editing.cpp
|
||
* @brief 曲面编辑三件套测试:match_surface / shape_fillet / control_point_edit
|
||
*/
|
||
|
||
#include <gtest/gtest.h>
|
||
#include "vde/curves/surface_editing.h"
|
||
#include "vde/curves/control_point_edit.h"
|
||
#include "vde/curves/nurbs_surface.h"
|
||
#include "vde/core/point.h"
|
||
#include <cmath>
|
||
|
||
namespace vde::curves {
|
||
namespace test {
|
||
|
||
using core::Point3D;
|
||
using core::Vector3D;
|
||
|
||
// ── Helper: create a simple biquadratic plane surface ──
|
||
NurbsSurface make_test_plane() {
|
||
// 3×3 control grid on z=0 plane
|
||
std::vector<std::vector<Point3D>> grid = {
|
||
{{0, 0, 0}, {1, 0, 0}, {2, 0, 0}},
|
||
{{0, 1, 0}, {1, 1, 0}, {2, 1, 0}},
|
||
{{0, 2, 0}, {1, 2, 0}, {2, 2, 0}}
|
||
};
|
||
return NurbsSurface(grid,
|
||
{0, 0, 0, 1, 1, 1}, // u-knots, degree=2
|
||
{0, 0, 0, 1, 1, 1}, // v-knots, degree=2
|
||
{}, 2, 2); // weights (empty = all 1.0)
|
||
}
|
||
|
||
// ── Helper: create a slightly curved surface (degree 2×2) ──
|
||
NurbsSurface make_test_curved() {
|
||
// 3×3 grid, slightly curved in z
|
||
std::vector<std::vector<Point3D>> grid = {
|
||
{{0, 0, 0}, {1, 0, 0.5}, {2, 0, 0}},
|
||
{{0, 1, 0.5}, {1, 1, 1.0}, {2, 1, 0.5}},
|
||
{{0, 2, 0}, {1, 2, 0.5}, {2, 2, 0}}
|
||
};
|
||
return NurbsSurface(grid,
|
||
{0, 0, 0, 1, 1, 1}, {0, 0, 0, 1, 1, 1}, {}, 2, 2);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// match_surface 测试
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG0Planar) {
|
||
// Two identical planes on z=0 — matching should produce no error
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G0;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts);
|
||
|
||
EXPECT_TRUE(result.g0_ok);
|
||
EXPECT_LT(result.max_position_error, 1e-4);
|
||
EXPECT_EQ(result.rows_adjusted, 1);
|
||
EXPECT_EQ(result.control_displacements.size(), 9u); // 3×3 grid
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG0DifferentSurfaces) {
|
||
// Two slightly different curved surfaces — match VMin edge
|
||
auto surf_a = make_test_curved();
|
||
// Modify a few control points to make it different
|
||
auto surf_b = make_test_curved();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G0;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(surf_b, surf_a, BoundaryEdge::VMin, opts);
|
||
|
||
// Should produce a valid surface
|
||
auto [nu, nv] = result.matched_surface.num_control_points();
|
||
EXPECT_GE(nu, 1);
|
||
EXPECT_GE(nv, 1);
|
||
// Control points should be adjusted
|
||
EXPECT_GT(result.control_displacements.size(), 0u);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG1Planar) {
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G1;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts);
|
||
|
||
EXPECT_TRUE(result.g0_ok);
|
||
EXPECT_TRUE(result.g1_ok);
|
||
EXPECT_EQ(result.rows_adjusted, 1);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG1DifferentSurfaces) {
|
||
auto surf_a = make_test_curved();
|
||
auto surf_b = make_test_curved();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G1;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(surf_b, surf_a, BoundaryEdge::VMin, opts);
|
||
|
||
// Check that errors are computed
|
||
EXPECT_GE(result.max_position_error, 0.0);
|
||
EXPECT_GE(result.max_tangent_error, 0.0);
|
||
EXPECT_EQ(result.rows_adjusted, 1);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG2Planar) {
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G2;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts);
|
||
|
||
EXPECT_TRUE(result.g0_ok);
|
||
EXPECT_EQ(result.rows_adjusted, 2);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceG3Planar) {
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G3;
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts);
|
||
|
||
EXPECT_TRUE(result.g0_ok);
|
||
EXPECT_EQ(result.rows_adjusted, 3); // G3 adjusts 3 rows
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceAllEdges) {
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G1;
|
||
|
||
// Test all 4 boundary edges
|
||
for (int e = 0; e < 4; ++e) {
|
||
BoundaryEdge edge = static_cast<BoundaryEdge>(e);
|
||
auto result = match_surface(plane_b, plane_a, edge, opts);
|
||
EXPECT_TRUE(result.g0_ok) << "Failed on edge " << e;
|
||
}
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, MatchSurfaceExplicitRows) {
|
||
auto plane_a = make_test_plane();
|
||
auto plane_b = make_test_plane();
|
||
|
||
MatchSurfaceOptions opts;
|
||
opts.continuity = ContinuityLevel::G2;
|
||
opts.rows_to_adjust = 1; // override, only 1 row
|
||
opts.boundary_samples = 10;
|
||
|
||
auto result = match_surface(plane_b, plane_a, BoundaryEdge::VMin, opts);
|
||
EXPECT_EQ(result.rows_adjusted, 1);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════════
|
||
// control_point_edit 测试
|
||
// ═══════════════════════════════════════════════════════════
|
||
|
||
TEST(SurfaceEditingTest, ControlPointEditSingle) {
|
||
auto surf = make_test_plane();
|
||
|
||
std::vector<std::pair<int,int>> indices = {{1, 1}}; // center control point
|
||
std::vector<Point3D> new_pos = {{1, 1, 5}}; // lift up in z
|
||
|
||
auto result = control_point_edit(surf, indices, new_pos);
|
||
|
||
EXPECT_EQ(result.points_modified, 1);
|
||
EXPECT_GT(result.max_displacement, 4.0); // moved ~5 units in z
|
||
EXPECT_TRUE(result.weights_preserved);
|
||
|
||
// Verify the control point was moved
|
||
auto modified_cp = result.modified_surface.control_points();
|
||
EXPECT_NEAR(modified_cp[1][1].z(), 5.0, 1e-6);
|
||
// Other control points unchanged
|
||
EXPECT_NEAR(modified_cp[0][0].z(), 0.0, 1e-6);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, ControlPointEditMultiple) {
|
||
auto surf = make_test_plane();
|
||
|
||
std::vector<std::pair<int,int>> indices = {{0, 0}, {2, 2}};
|
||
std::vector<Point3D> new_pos = {{0, 0, 3}, {3, 3, 3}};
|
||
|
||
auto result = control_point_edit(surf, indices, new_pos);
|
||
|
||
EXPECT_EQ(result.points_modified, 2);
|
||
EXPECT_GT(result.max_displacement, 2.0);
|
||
|
||
auto cp = result.modified_surface.control_points();
|
||
EXPECT_NEAR(cp[0][0].z(), 3.0, 1e-6);
|
||
EXPECT_NEAR(cp[2][2].z(), 3.0, 1e-6);
|
||
// Center unchanged
|
||
EXPECT_NEAR(cp[1][1].z(), 0.0, 1e-6);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, ControlPointEditEmpty) {
|
||
auto surf = make_test_plane();
|
||
|
||
std::vector<std::pair<int,int>> indices;
|
||
std::vector<Point3D> new_pos;
|
||
|
||
auto result = control_point_edit(surf, indices, new_pos);
|
||
|
||
EXPECT_EQ(result.points_modified, 0);
|
||
EXPECT_NEAR(result.max_displacement, 0.0, 1e-12);
|
||
|
||
// Should return a valid copy of original
|
||
auto cp = result.modified_surface.control_points();
|
||
EXPECT_EQ(cp.size(), 3u);
|
||
EXPECT_NEAR(cp[0][0].z(), 0.0, 1e-6);
|
||
}
|
||
|
||
TEST(SurfaceEditingTest, ControlPointEditPreservesKnots) {
|
||
auto surf = make_test_plane();
|
||
|
||
std::vector<std::pair<int,int>> indices = {{0, 0}};
|
||
std::vector<Point3D> new_pos = {{-1, -1, 0}};
|
||
|
||
auto result = control_point_edit(surf, indices, new_pos);
|
||
|
||
// Knot vectors preserved
|
||
const auto& ku = result.modified_surface.knots_u();
|
||
const auto& kv = result.modified_surface.knots_v();
|
||
EXPECT_EQ(ku.size(), 6u);
|
||
EXPECT_EQ(kv.size(), 6u);
|
||
EXPECT_EQ(result.modified_surface.degree_u(), 2);
|
||
EXPECT_EQ(result.modified_surface.degree_v(), 2);
|
||
}
|
||
|
||
} // namespace test
|
||
} // namespace vde::curves
|