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)
341 lines
10 KiB
C++
341 lines
10 KiB
C++
#include <gtest/gtest.h>
|
|
#include "vde/curves/generative_surfaces.h"
|
|
#include "vde/curves/nurbs_curve.h"
|
|
#include "vde/core/point.h"
|
|
#include <cmath>
|
|
|
|
using namespace vde::curves;
|
|
using namespace vde::core;
|
|
|
|
// ── 辅助:创建直线 NURBS ──
|
|
static NurbsCurve make_line(const Point3D& a, const Point3D& b) {
|
|
return NurbsCurve({a, b}, {0, 0, 1, 1}, {1, 1}, 1);
|
|
}
|
|
|
|
// ── 辅助:创建圆弧 profile ──
|
|
static NurbsCurve make_arc_profile() {
|
|
double r = 1.0;
|
|
double w = std::sqrt(2.0) / 2.0;
|
|
return NurbsCurve(
|
|
{Point3D(0, r, 0), Point3D(r, r, 0), Point3D(r, 0, 0)},
|
|
{0, 0, 0, 0.5, 1, 1, 1},
|
|
{1, w, 1, w, 1, w, 1},
|
|
2
|
|
);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Sweep 测试
|
|
// ============================================================================
|
|
|
|
TEST(GenerativeSurfacesTest, SweepExplicit_Basic) {
|
|
// 圆形 profile 沿直线扫描 → 圆柱面
|
|
NurbsCurve profile(
|
|
{Point3D(0, 1, 0), Point3D(1, 1, 0), Point3D(1, 0, 0)},
|
|
{0, 0, 0, 1, 1, 1},
|
|
{1, 1, 1},
|
|
2
|
|
);
|
|
NurbsCurve path = make_line({0, 0, 0}, {0, 0, 5});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::Explicit;
|
|
opt.samples = 8;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
EXPECT_EQ(surf.degree_u(), 2);
|
|
EXPECT_GE(surf.num_control_points()[0], 3);
|
|
EXPECT_GE(surf.num_control_points()[1], 3);
|
|
|
|
// 验证曲面在参数域中点可求值
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_GE(p.x(), 0.0);
|
|
EXPECT_LE(p.z(), 5.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, SweepExplicit_StraightPath) {
|
|
// 矩形 profile 沿 X 轴扫描
|
|
NurbsCurve profile = make_line({0, 0, 0}, {0, 0, 2});
|
|
NurbsCurve path = make_line({0, 0, 0}, {10, 0, 0});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::Explicit;
|
|
opt.samples = 4;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
// 验证曲面两端
|
|
auto p0 = surf.evaluate(0.0, 0.0);
|
|
auto p1 = surf.evaluate(1.0, 1.0);
|
|
|
|
EXPECT_NEAR(p0.x(), 0.0, 1e-6);
|
|
EXPECT_NEAR(p1.x(), 10.0, 1e-6);
|
|
EXPECT_GE(p1.z(), 0.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, SweepExplicit_VaryingSamples) {
|
|
NurbsCurve profile = make_line({0, 1, 0}, {0, -1, 0});
|
|
NurbsCurve path = make_line({0, 0, 0}, {0, 0, 3});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::Explicit;
|
|
opt.samples = 16;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
// 控制点数量应随采样增加
|
|
int n_u = surf.num_control_points()[0];
|
|
EXPECT_GT(n_u, 4);
|
|
|
|
// 曲面可正常求值
|
|
auto mid = surf.evaluate(0.5, 0.5);
|
|
EXPECT_NEAR(mid.x(), 0.0, 1e-6);
|
|
EXPECT_NEAR(mid.z(), 1.5, 1.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, SweepSpine_Basic) {
|
|
// 使用独立脊线的扫描
|
|
NurbsCurve profile = make_line({0, 1, 0}, {0, -1, 0});
|
|
NurbsCurve path = make_line({0, 0, 0}, {5, 0, 0});
|
|
|
|
// 脊线偏离路径,用于扭转控制
|
|
NurbsCurve spine = make_line({0, 0, 0}, {5, 0, 2});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::Spine;
|
|
opt.spine = spine;
|
|
opt.samples = 8;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 3);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 验证曲面可求值
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_FALSE(std::isnan(p.x()));
|
|
EXPECT_FALSE(std::isnan(p.y()));
|
|
EXPECT_FALSE(std::isnan(p.z()));
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, SweepTwoGuides_Basic) {
|
|
// 双引导线扫描:两条引导线间距变化控制缩放
|
|
NurbsCurve profile = make_line({0, 1, 0}, {0, -1, 0});
|
|
NurbsCurve path = make_line({0, 0, 0}, {5, 0, 0});
|
|
|
|
// 两条引导线逐渐分开
|
|
NurbsCurve guide1 = make_line({0, 0.5, 0}, {5, 2, 0});
|
|
NurbsCurve guide2 = make_line({0, -0.5, 0}, {5, -2, 0});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::TwoGuides;
|
|
opt.guide1 = guide1;
|
|
opt.guide2 = guide2;
|
|
opt.samples = 8;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 3);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 曲面起点和终点宽度应不同(缩放生效)
|
|
auto p_start = surf.evaluate(0.0, 0.5);
|
|
auto p_end = surf.evaluate(1.0, 0.5);
|
|
EXPECT_GE(std::abs(p_end.y()) - std::abs(p_start.y()), -0.5);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Sweep_DefaultOption) {
|
|
// 默认选项(Explicit 模式)
|
|
NurbsCurve profile = make_line({0, 1, 0}, {0, -1, 0});
|
|
NurbsCurve path = make_line({0, 0, 0}, {3, 0, 0});
|
|
|
|
auto surf = sweep_surface(profile, path);
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 3);
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_FALSE(std::isnan(p.x()));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Loft 测试
|
|
// ============================================================================
|
|
|
|
TEST(GenerativeSurfacesTest, Loft_Basic) {
|
|
// 两条平行截面放样
|
|
NurbsCurve s1 = make_line({0, 0, 0}, {2, 0, 0});
|
|
NurbsCurve s2 = make_line({0, 2, 5}, {2, 2, 5});
|
|
|
|
auto surf = loft_surface({s1, s2});
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 验证曲面插值首尾截面
|
|
auto p_bottom = surf.evaluate(0.5, 0.0);
|
|
EXPECT_NEAR(p_bottom.z(), 0.0, 1e-6);
|
|
|
|
auto p_top = surf.evaluate(0.5, 1.0);
|
|
EXPECT_NEAR(p_top.z(), 5.0, 1e-6);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Loft_ThreeSections) {
|
|
NurbsCurve s1 = make_line({0, 0, 0}, {2, 0, 0});
|
|
NurbsCurve s2 = make_line({0, 1, 3}, {2, 1, 3});
|
|
NurbsCurve s3 = make_line({0, 0, 6}, {2, 0, 6});
|
|
|
|
auto surf = loft_surface({s1, s2, s3});
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 中间截面应偏离直线
|
|
auto p_mid = surf.evaluate(0.5, 0.5);
|
|
EXPECT_NEAR(p_mid.z(), 3.0, 1.0);
|
|
EXPECT_GT(p_mid.y(), 0.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Loft_WithGuides) {
|
|
NurbsCurve s1 = make_line({0, 0, 0}, {2, 0, 0});
|
|
NurbsCurve s2 = make_line({0, 0, 5}, {2, 0, 5});
|
|
NurbsCurve s3 = make_line({0, 0, 10}, {2, 0, 10});
|
|
|
|
// 引导线:弯曲的脊线
|
|
NurbsCurve guide(
|
|
{Point3D(0, 1, 2.5), Point3D(1, 0, 5), Point3D(0, -1, 7.5)},
|
|
{0, 0, 0, 1, 1, 1},
|
|
{1, 1, 1},
|
|
2
|
|
);
|
|
|
|
auto surf = loft_surface({s1, s2, s3}, {guide});
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_FALSE(std::isnan(p.x()));
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Loft_WithTangents) {
|
|
NurbsCurve s1 = make_line({0, 0, 0}, {2, 0, 0});
|
|
NurbsCurve s2 = make_line({0, 0, 5}, {2, 0, 5});
|
|
|
|
LoftConstraint cons;
|
|
cons.start_tangent = Vector3D(0, 0, 1); // 垂直向上
|
|
cons.end_tangent = Vector3D(0, 0, 1); // 垂直向上
|
|
|
|
auto surf = loft_surface({s1, s2}, {}, cons);
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 曲面应能正常求值
|
|
auto p = surf.evaluate(0.5, 0.3);
|
|
EXPECT_FALSE(std::isnan(p.x()));
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Loft_InvalidInput) {
|
|
// 单个截面应抛出异常
|
|
NurbsCurve s1 = make_line({0, 0, 0}, {1, 0, 0});
|
|
EXPECT_THROW((void)loft_surface({s1}), std::invalid_argument);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Net 测试
|
|
// ============================================================================
|
|
|
|
TEST(GenerativeSurfacesTest, Net_Basic) {
|
|
// 双向曲线网格:u 向和 v 向各 2 条线
|
|
NurbsCurve u0 = make_line({0, 0, 0}, {5, 0, 0});
|
|
NurbsCurve u1 = make_line({0, 2, 0}, {5, 2, 0});
|
|
NurbsCurve v0 = make_line({0, 0, 0}, {0, 2, 0});
|
|
NurbsCurve v1 = make_line({5, 0, 0}, {5, 2, 0});
|
|
|
|
auto surf = net_surface({u0, u1}, {v0, v1});
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
// 曲面应在 XY 平面内
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_NEAR(p.z(), 0.0, 1e-6);
|
|
EXPECT_GE(p.x(), 0.0);
|
|
EXPECT_GE(p.y(), 0.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Net_UnequalCount) {
|
|
// u 向 3 条,v 向 2 条
|
|
NurbsCurve u0 = make_line({0, 0, 0}, {5, 0, 0});
|
|
NurbsCurve u1 = make_line({0, 1, 0}, {5, 1, 0});
|
|
NurbsCurve u2 = make_line({0, 2, 0}, {5, 2, 0});
|
|
NurbsCurve v0 = make_line({0, 0, 0}, {0, 2, 0});
|
|
NurbsCurve v1 = make_line({5, 0, 0}, {5, 2, 0});
|
|
|
|
auto surf = net_surface({u0, u1, u2}, {v0, v1});
|
|
|
|
EXPECT_EQ(surf.num_control_points()[0], 3);
|
|
EXPECT_EQ(surf.num_control_points()[1], 2);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Net_3DCurves) {
|
|
// 三维网格曲面
|
|
NurbsCurve u0(
|
|
{Point3D(0, 0, 0), Point3D(1, 0, 1), Point3D(2, 0, 0)},
|
|
{0, 0, 0, 1, 1, 1},
|
|
{1, 1, 1},
|
|
2
|
|
);
|
|
NurbsCurve u1(
|
|
{Point3D(0, 2, 0), Point3D(1, 2, 1), Point3D(2, 2, 0)},
|
|
{0, 0, 0, 1, 1, 1},
|
|
{1, 1, 1},
|
|
2
|
|
);
|
|
NurbsCurve v0 = make_line({0, 0, 0}, {0, 2, 0});
|
|
NurbsCurve v1 = make_line({2, 0, 0}, {2, 2, 0});
|
|
|
|
auto surf = net_surface({u0, u1}, {v0, v1});
|
|
|
|
EXPECT_GE(surf.num_control_points()[0], 2);
|
|
EXPECT_GE(surf.num_control_points()[1], 2);
|
|
|
|
auto p = surf.evaluate(0.5, 0.5);
|
|
EXPECT_FALSE(std::isnan(p.x()));
|
|
EXPECT_GE(p.z(), 0.0);
|
|
}
|
|
|
|
TEST(GenerativeSurfacesTest, Net_InvalidInput) {
|
|
NurbsCurve u0 = make_line({0, 0, 0}, {1, 0, 0});
|
|
EXPECT_THROW((void)net_surface({u0}, {u0}), std::invalid_argument);
|
|
|
|
NurbsCurve v0 = make_line({0, 0, 0}, {1, 0, 0});
|
|
EXPECT_THROW((void)net_surface({u0, u0}, {v0}), std::invalid_argument);
|
|
}
|
|
|
|
// ============================================================================
|
|
// 集成测试:组合使用
|
|
// ============================================================================
|
|
|
|
TEST(GenerativeSurfacesTest, Integration_SweepThenEvaluate) {
|
|
// 验证扫掠后曲面求值一致性
|
|
NurbsCurve profile = make_line({0, 1, 0}, {0, -1, 0});
|
|
NurbsCurve path = make_line({0, 0, 0}, {10, 0, 0});
|
|
|
|
SweepOption opt;
|
|
opt.type = SweepType::Explicit;
|
|
opt.samples = 10;
|
|
|
|
auto surf = sweep_surface(profile, path, opt);
|
|
|
|
// 曲面法向应在 XY 平面内(近似)
|
|
for (double u = 0.0; u <= 1.0; u += 0.25) {
|
|
for (double v = 0.0; v <= 1.0; v += 0.25) {
|
|
auto pt = surf.evaluate(u, v);
|
|
EXPECT_FALSE(std::isnan(pt.x()));
|
|
EXPECT_FALSE(std::isnan(pt.y()));
|
|
EXPECT_FALSE(std::isnan(pt.z()));
|
|
}
|
|
}
|
|
}
|