354 lines
11 KiB
C++
354 lines
11 KiB
C++
|
|
#include <gtest/gtest.h>
|
|||
|
|
#include "vde/curves/class_a_surfacing.h"
|
|||
|
|
#include "vde/curves/nurbs_curve.h"
|
|||
|
|
#include "vde/curves/nurbs_surface.h"
|
|||
|
|
#include <cmath>
|
|||
|
|
|
|||
|
|
using namespace vde::curves;
|
|||
|
|
using namespace vde::core;
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Helpers
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
/// 创建平面 NURBS 曲面(XY 平面,[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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 创建二次曲面(抛物面样)
|
|||
|
|
static NurbsSurface quad_surface() {
|
|||
|
|
std::vector<std::vector<Point3D>> grid = {
|
|||
|
|
{Point3D(0,0,0), Point3D(0,1,0.5), Point3D(0,2,0)},
|
|||
|
|
{Point3D(1,0,0.5), Point3D(1,1,1), Point3D(1,2,0.5)},
|
|||
|
|
{Point3D(2,0,0), Point3D(2,1,0.5), Point3D(2,2,0)}
|
|||
|
|
};
|
|||
|
|
return NurbsSurface(grid,
|
|||
|
|
{0,0,0,1,1,1}, {0,0,0,1,1,1},
|
|||
|
|
{{1,1,1},{1,1,1},{1,1,1}}, 2, 2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 创建圆柱面
|
|||
|
|
static NurbsSurface cylinder_surface() {
|
|||
|
|
double R = 1.0;
|
|||
|
|
std::vector<std::vector<Point3D>> grid(5, std::vector<Point3D>(3));
|
|||
|
|
for (int i = 0; i < 5; ++i) {
|
|||
|
|
double angle = i * M_PI / 2.0;
|
|||
|
|
double x = R * std::cos(angle);
|
|||
|
|
double y = R * std::sin(angle);
|
|||
|
|
for (int j = 0; j < 3; ++j) {
|
|||
|
|
grid[i][j] = Point3D(x, y, j * 0.5);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
std::vector<std::vector<double>> w(5, std::vector<double>(3, 1.0));
|
|||
|
|
return NurbsSurface(grid,
|
|||
|
|
{0,0,0,0,0.5,1,1,1,1},
|
|||
|
|
{0,0,0,1,1,1}, w, 3, 2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 沿 Z 偏移的平面
|
|||
|
|
static NurbsSurface offset_plane(double z) {
|
|||
|
|
std::vector<std::vector<Point3D>> grid = {
|
|||
|
|
{Point3D(0,0,z), Point3D(0,1,z)},
|
|||
|
|
{Point3D(1,0,z), Point3D(1,1,z)}
|
|||
|
|
};
|
|||
|
|
return NurbsSurface(grid, {0,0,1,1}, {0,0,1,1}, {{1,1},{1,1}}, 1, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: G3 Blend — G3 连续性过渡
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, G3Blend_IdenticalPlanes) {
|
|||
|
|
auto a = plane_surface();
|
|||
|
|
auto b = offset_plane(0.0); // 重合平面
|
|||
|
|
|
|||
|
|
EdgeParams params;
|
|||
|
|
params.edge_a = 3; // vmax of a
|
|||
|
|
params.edge_b = 2; // vmin of b
|
|||
|
|
params.blend_width = 0.2;
|
|||
|
|
|
|||
|
|
auto result = g3_blend(a, b, params);
|
|||
|
|
|
|||
|
|
// 过渡面应该非退化
|
|||
|
|
auto [nu, nv] = result.blend_surface.num_control_points();
|
|||
|
|
EXPECT_GT(nu, 0);
|
|||
|
|
EXPECT_GT(nv, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, G3Blend_ContinuityChecks) {
|
|||
|
|
auto a = plane_surface();
|
|||
|
|
auto b = offset_plane(0.0);
|
|||
|
|
|
|||
|
|
EdgeParams params;
|
|||
|
|
params.blend_width = 0.15;
|
|||
|
|
|
|||
|
|
auto result = g3_blend(a, b, params);
|
|||
|
|
|
|||
|
|
// G0 位置连续
|
|||
|
|
EXPECT_TRUE(result.g0_ok);
|
|||
|
|
EXPECT_LT(result.max_position_error, 1e-3);
|
|||
|
|
|
|||
|
|
// G1 切平面连续
|
|||
|
|
EXPECT_TRUE(result.g1_ok);
|
|||
|
|
EXPECT_LT(result.max_tangent_error, 0.02);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, G3Blend_ResultHasBlendSurface) {
|
|||
|
|
auto a = plane_surface();
|
|||
|
|
auto b = offset_plane(1.0); // 间距 1
|
|||
|
|
|
|||
|
|
EdgeParams params;
|
|||
|
|
params.edge_a = 3;
|
|||
|
|
params.edge_b = 2;
|
|||
|
|
params.blend_width = 0.5;
|
|||
|
|
|
|||
|
|
auto result = g3_blend(a, b, params);
|
|||
|
|
|
|||
|
|
auto [nu, nv] = result.blend_surface.num_control_points();
|
|||
|
|
// 应生成有效的过渡曲面
|
|||
|
|
EXPECT_GE(nu, 2);
|
|||
|
|
EXPECT_EQ(nv, 4); // G3 需要 4 排控制点
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Curvature Matching — 曲率匹配
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, CurvatureMatching_FlatPlanes) {
|
|||
|
|
auto a = plane_surface();
|
|||
|
|
auto b = offset_plane(0.0);
|
|||
|
|
|
|||
|
|
CurvatureMatchOptions opts;
|
|||
|
|
opts.samples_per_edge = 10;
|
|||
|
|
opts.max_iterations = 20;
|
|||
|
|
|
|||
|
|
auto result = curvature_matching(a, b, opts);
|
|||
|
|
|
|||
|
|
// 平面曲率处处为0,匹配后应保持
|
|||
|
|
EXPECT_LT(result.final_rms_curvature_diff, 1.0);
|
|||
|
|
EXPECT_GT(result.iterations, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, CurvatureMatching_ReturnsMatchedSurface) {
|
|||
|
|
auto a = quad_surface();
|
|||
|
|
auto b = quad_surface();
|
|||
|
|
|
|||
|
|
CurvatureMatchOptions opts;
|
|||
|
|
opts.max_iterations = 5;
|
|||
|
|
opts.tolerance = 1e-4;
|
|||
|
|
|
|||
|
|
auto result = curvature_matching(a, b, opts);
|
|||
|
|
|
|||
|
|
auto [nu, nv] = result.matched_surface_b.num_control_points();
|
|||
|
|
EXPECT_EQ(nu, 3);
|
|||
|
|
EXPECT_EQ(nv, 3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, CurvatureMatching_DisplacementVector) {
|
|||
|
|
auto a = plane_surface();
|
|||
|
|
auto b = quad_surface();
|
|||
|
|
|
|||
|
|
CurvatureMatchOptions opts;
|
|||
|
|
opts.max_iterations = 3;
|
|||
|
|
|
|||
|
|
auto result = curvature_matching(a, b, opts);
|
|||
|
|
|
|||
|
|
// 应有位移向量
|
|||
|
|
EXPECT_FALSE(result.displacement.empty());
|
|||
|
|
EXPECT_GE(result.displacement.size(), 1u);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Highlight Lines — 高光线分析
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, HighlightLines_PlaneOutput) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
std::vector<Vector3D> lights = {Vector3D(0,0,1)};
|
|||
|
|
|
|||
|
|
auto result = highlight_lines(s, lights, 10, 10, 6);
|
|||
|
|
|
|||
|
|
EXPECT_EQ(result.res_u, 10);
|
|||
|
|
EXPECT_EQ(result.res_v, 10);
|
|||
|
|
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, HighlightLines_MultipleLights) {
|
|||
|
|
auto s = quad_surface();
|
|||
|
|
std::vector<Vector3D> lights = {
|
|||
|
|
Vector3D(1,0,0), Vector3D(0,1,0), Vector3D(0,0,1)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
auto result = highlight_lines(s, lights, 15, 15, 8);
|
|||
|
|
|
|||
|
|
EXPECT_EQ(static_cast<int>(result.band_mask.size()), 3);
|
|||
|
|
EXPECT_EQ(static_cast<int>(result.continuity_scores.size()), 3);
|
|||
|
|
EXPECT_GE(result.overall_score, 0.0);
|
|||
|
|
EXPECT_LE(result.overall_score, 1.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, HighlightLines_FlatPlanePerfectScore) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
std::vector<Vector3D> lights = {Vector3D(1,1,1)};
|
|||
|
|
|
|||
|
|
auto result = highlight_lines(s, lights, 20, 20, 10);
|
|||
|
|
|
|||
|
|
// 平面上高光线应连续
|
|||
|
|
EXPECT_GT(result.continuity_scores[0], 0.9);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Reflection Lines — 反射线分析
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, ReflectionLines_PlaneIsFair) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
auto result = reflection_lines(s,
|
|||
|
|
Point3D(5, 5, 5), Point3D(-5, -5, 10), 20, 20);
|
|||
|
|
|
|||
|
|
EXPECT_EQ(result.res_u, 20);
|
|||
|
|
EXPECT_EQ(result.res_v, 20);
|
|||
|
|
// 平面反射线应判定为光顺
|
|||
|
|
EXPECT_TRUE(result.is_fair);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, ReflectionLines_DistortionBounds) {
|
|||
|
|
auto s = quad_surface();
|
|||
|
|
auto result = reflection_lines(s,
|
|||
|
|
Point3D(3, 3, 5), Point3D(-3, -3, 8), 15, 15);
|
|||
|
|
|
|||
|
|
EXPECT_GE(result.max_distortion, 0.0);
|
|||
|
|
EXPECT_GE(result.mean_distortion, 0.0);
|
|||
|
|
EXPECT_LE(result.mean_distortion, result.max_distortion + 1e-9);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Iso-Photes — 等照度分析
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, IsoPhotes_OutputDimensions) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
auto result = iso_photes(s, 16);
|
|||
|
|
|
|||
|
|
EXPECT_EQ(result.res_u, 16);
|
|||
|
|
EXPECT_EQ(result.res_v, 16);
|
|||
|
|
EXPECT_EQ(static_cast<int>(result.illumination.size()), 17);
|
|||
|
|
EXPECT_EQ(static_cast<int>(result.illumination[0].size()), 17);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, IsoPhotes_IlluminationRange) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
auto result = iso_photes(s, 10);
|
|||
|
|
|
|||
|
|
for (size_t i = 0; i < result.illumination.size(); ++i) {
|
|||
|
|
for (size_t j = 0; j < result.illumination[i].size(); ++j) {
|
|||
|
|
double val = result.illumination[i][j];
|
|||
|
|
EXPECT_GE(val, -1.1);
|
|||
|
|
EXPECT_LE(val, 1.1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, IsoPhotes_GradientAnalysis) {
|
|||
|
|
auto s = quad_surface();
|
|||
|
|
auto result = iso_photes(s, 20);
|
|||
|
|
|
|||
|
|
EXPECT_GE(result.grad_max, 0.0);
|
|||
|
|
EXPECT_GE(result.grad_mean, 0.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Surface Diagnosis — 综合曲面诊断
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, SurfaceDiagnosis_PlaneDiagnosis) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
auto report = surface_diagnosis(s);
|
|||
|
|
|
|||
|
|
// 平面应该达到 A 级
|
|||
|
|
EXPECT_EQ(report.grade, DiagnosisGrade::A_CLASS);
|
|||
|
|
EXPECT_GT(report.overall_score, 80.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, SurfaceDiagnosis_ReportHasAllFields) {
|
|||
|
|
auto s = quad_surface();
|
|||
|
|
auto report = surface_diagnosis(s);
|
|||
|
|
|
|||
|
|
EXPECT_FALSE(report.gaussian_range.name.empty());
|
|||
|
|
EXPECT_FALSE(report.mean_range.name.empty());
|
|||
|
|
EXPECT_FALSE(report.highlight_score.name.empty());
|
|||
|
|
EXPECT_FALSE(report.reflection_distortion.name.empty());
|
|||
|
|
EXPECT_FALSE(report.isophote_gradient.name.empty());
|
|||
|
|
EXPECT_FALSE(report.normal_jump.name.empty());
|
|||
|
|
EXPECT_FALSE(report.recommendation.empty());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
// Test: Shape Modification — 保形修改
|
|||
|
|
// ---------------------------------------------------------------------------
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, ShapeModification_SingleConstraint) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
|
|||
|
|
std::vector<ShapeConstraint> constraints;
|
|||
|
|
ShapeConstraint c;
|
|||
|
|
c.target_point = Point3D(0.5, 0.5, 0.5); // 提升0.5
|
|||
|
|
c.u = 0.5;
|
|||
|
|
c.v = 0.5;
|
|||
|
|
c.weight = 1.0;
|
|||
|
|
constraints.push_back(c);
|
|||
|
|
|
|||
|
|
auto result = shape_modification(s, constraints);
|
|||
|
|
|
|||
|
|
auto [nu, nv] = result.modified_surface.num_control_points();
|
|||
|
|
EXPECT_EQ(nu, 2);
|
|||
|
|
EXPECT_EQ(nv, 2);
|
|||
|
|
|
|||
|
|
// 控制点应有位移
|
|||
|
|
EXPECT_GT(result.max_displacement, 0.0);
|
|||
|
|
EXPECT_GE(result.energy_preserved_ratio, 0.0);
|
|||
|
|
EXPECT_LE(result.energy_preserved_ratio, 1.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, ShapeModification_ConstraintPointMoves) {
|
|||
|
|
auto s = plane_surface();
|
|||
|
|
|
|||
|
|
std::vector<ShapeConstraint> constraints;
|
|||
|
|
ShapeConstraint c;
|
|||
|
|
c.target_point = Point3D(0.3, 0.3, 2.0);
|
|||
|
|
c.u = 0.3;
|
|||
|
|
c.v = 0.3;
|
|||
|
|
c.weight = 2.0;
|
|||
|
|
constraints.push_back(c);
|
|||
|
|
|
|||
|
|
auto result = shape_modification(s, constraints);
|
|||
|
|
|
|||
|
|
// 修改后的曲面在约束点处应接近目标
|
|||
|
|
auto pt = result.modified_surface.evaluate(0.3, 0.3);
|
|||
|
|
EXPECT_GT(pt.z(), 0.1); // 应被提起
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
TEST(ClassASurfacingTest, ShapeModification_MultipleConstraints) {
|
|||
|
|
auto s = quad_surface();
|
|||
|
|
|
|||
|
|
std::vector<ShapeConstraint> constraints;
|
|||
|
|
for (int i = 0; i < 3; ++i) {
|
|||
|
|
ShapeConstraint c;
|
|||
|
|
c.u = 0.25 * (i + 1);
|
|||
|
|
c.v = 0.5;
|
|||
|
|
c.target_point = Point3D(c.u * 2, 1.0, 1.5 + i * 0.5);
|
|||
|
|
c.weight = 1.0;
|
|||
|
|
constraints.push_back(c);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
auto result = shape_modification(s, constraints);
|
|||
|
|
|
|||
|
|
EXPECT_GE(result.iterations, 1);
|
|||
|
|
EXPECT_GT(result.max_displacement, 0.0);
|
|||
|
|
}
|