Temporary repository used to save branch code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1171 lines
48 KiB

#define _USE_MATH_DEFINES
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cassert>
#include <vector>
#include <functional>
#include <chrono>
#include <mimalloc.h>
#include <data/data_center.hpp>
#include <base/primitive.hpp>
#include <base/subface.hpp>
#include <primitive_descriptor.h>
#include <subface/geometry/geometry_data.hpp>
#include <primitive/simple/extrude.hpp>
// ============================================================================
// 测试框架宏定义
// ============================================================================
#define TEST_SECTION(name) \
std::cout << "\n========================================\n" \
<< "TEST: " << name << "\n" \
<< "========================================\n"
#define TEST_ASSERT(condition, message) \
do { \
if (!(condition)) { \
std::cerr << "❌ FAILED: " << message << "\n"; \
std::cerr << " at " << __FILE__ << ":" << __LINE__ << "\n"; \
return false; \
} else { \
std::cout << "✓ PASSED: " << message << "\n"; \
} \
} while (0)
#define TEST_APPROX_EQUAL(a, b, eps, message) \
TEST_ASSERT(std::abs((a) - (b)) < (eps), \
message << " (expected: " << (b) << ", got: " << (a) << ", diff: " << std::abs((a) - (b)) << ")")
#define TEST_VECTOR_APPROX_EQUAL(v1, v2, eps, message) \
TEST_ASSERT((v1 - v2).norm() < (eps), message << " (distance: " << (v1 - v2).norm() << ")")
// ============================================================================
// 测试数据工厂
// ============================================================================
class TestDataFactory
{
public:
struct ProfileData {
std::vector<vector3d> points;
std::vector<double> bulges;
profile_descriptor_t descriptor;
void finalize()
{
descriptor.point_number = static_cast<uint32_t>(points.size());
descriptor.points = points.data();
descriptor.bulge_number = static_cast<uint32_t>(bulges.size());
descriptor.bulges = bulges.data();
descriptor.is_close = true;
descriptor.reference_normal = {0, 0, 1};
}
};
static ProfileData createSquareProfile(double size = 1.0)
{
ProfileData data;
double half = size / 2.0;
// CCW winding: matches unit_square_profile convention expected by helixline SDF.
// Order: bottom-left → bottom-right → top-right → top-left
// i.e. (-,-) → (+,-) → (+,+) → (-,+)
// Using CW winding here inverts the profile normal, which flips the SDF sign
// globally and causes all sign-dependent tests to fail.
data.points = {
{-half, -half, 0.0},
{half, -half, 0.0},
{half, half, 0.0},
{-half, half, 0.0}
};
data.bulges = {0.0, 0.0, 0.0, 0.0};
data.finalize();
return data;
}
static ProfileData createCircleProfile(double radius = 0.5)
{
ProfileData data;
data.points = {
{radius, 0.0, 0.0},
{-radius, 0.0, 0.0}
};
data.bulges = {1.0, 1.0};
data.finalize();
return data;
}
static ProfileData createTriangleProfile(double size = 1.0)
{
ProfileData data;
double half = size / 2.0;
double h = size * std::sqrt(3.0) / 2.0;
data.points = {
{0, 2 * h / 3, 0.0},
{half, -h / 3, 0.0},
{-half, -h / 3, 0.0}
};
data.bulges = {0.0, 0.0, 0.0};
data.finalize();
return data;
}
static ProfileData createRectangleProfile(double width, double height)
{
ProfileData data;
double half_w = width / 2.0;
double half_h = height / 2.0;
data.points = {
{-half_w, -half_h, 0.0},
{-half_w, half_h, 0.0},
{half_w, half_h, 0.0},
{half_w, -half_h, 0.0}
};
data.bulges = {0.0, 0.0, 0.0, 0.0};
data.finalize();
return data;
}
static axis_descriptor_t createHelixlineAxis(double radius = 1.0,
double height = 4.0,
double turns = 2.0,
bool righthanded = true)
{
axis_descriptor_t desc;
desc.type = AXIS_TYPE_HELIXLINE;
desc.data.helixline.axis_start = {0, 0, 0};
desc.data.helixline.axis_end = {0, 0, height};
desc.data.helixline.start_direction = {radius, 0, 0};
desc.data.helixline.radius = radius;
desc.data.helixline.advance_per_round = height / turns;
desc.data.helixline.is_righthanded = righthanded;
return desc;
}
static axis_descriptor_t createTightHelixAxis(double radius = 0.5, double height = 10.0)
{
return createHelixlineAxis(radius, height, 10.0, true);
}
static axis_descriptor_t createLooseHelixAxis(double radius = 2.0, double height = 2.0)
{
return createHelixlineAxis(radius, height, 0.5, true);
}
static axis_descriptor_t createSlantedHelixAxis(double radius,
const Eigen::Vector3d& start,
const Eigen::Vector3d& end,
double turns = 2.0)
{
axis_descriptor_t desc;
desc.type = AXIS_TYPE_HELIXLINE;
desc.data.helixline.axis_start = {start.x(), start.y(), start.z()};
desc.data.helixline.axis_end = {end.x(), end.y(), end.z()};
desc.data.helixline.start_direction = {radius, 0, 0};
desc.data.helixline.radius = radius;
double height = (end - start).norm();
desc.data.helixline.advance_per_round = height / turns;
desc.data.helixline.is_righthanded = true;
return desc;
}
};
// ============================================================================
// 测试辅助工具(新架构适配)
// ============================================================================
class TestHelper
{
public:
static void safeFree(primitive* ptr)
{
if (!ptr) return;
ptr->destroy();
mi_free(ptr);
}
static bool verifyAABB(const aabb_t& aabb,
const Eigen::Vector3d& expected_min,
const Eigen::Vector3d& expected_max,
double tolerance = 1e-5)
{
bool min_ok = (aabb.min() - expected_min).norm() < tolerance;
bool max_ok = (aabb.max() - expected_max).norm() < tolerance;
if (!min_ok || !max_ok) {
std::cout << " AABB mismatch:\n";
std::cout << " Expected min: " << expected_min.transpose() << "\n";
std::cout << " Actual min: " << aabb.min().transpose() << "\n";
std::cout << " Expected max: " << expected_max.transpose() << "\n";
std::cout << " Actual max: " << aabb.max().transpose() << "\n";
}
return min_ok && max_ok;
}
static void printStatistics(int passed, int total)
{
std::cout << "\n╔══════════════════════════════════════════╗\n";
std::cout << "║ TEST RESULTS SUMMARY ║\n";
std::cout << "╠══════════════════════════════════════════╣\n";
std::cout << "║ Total Tests: " << std::setw(4) << total << "\n";
std::cout << "║ Passed: " << std::setw(4) << passed << "\n";
std::cout << "║ Failed: " << std::setw(4) << (total - passed) << "\n";
std::cout << "║ Success Rate: " << std::fixed << std::setprecision(1) << std::setw(5) << (100.0 * passed / total)
<< "% ║\n";
std::cout << "╚══════════════════════════════════════════╝\n";
}
};
// ============================================================================
// GROUP 1: Helixline Geometry 基础测试
// ============================================================================
bool test_helixline_construction()
{
TEST_SECTION("Helixline Geometry - Construction");
auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 4.0, 2.0, true);
internal::helixline_geometry_data geom;
Eigen::Transform<double, 3, Eigen::AffineCompact, Eigen::DontAlign> axis_to_world;
aabb_t aabb;
geom.build_from_descriptor(axis_desc.data.helixline, axis_to_world, aabb);
TEST_APPROX_EQUAL(geom.radius, 1.0, 1e-6, "Helixline radius");
TEST_APPROX_EQUAL(geom.height, 4.0, 1e-6, "Helixline height");
TEST_APPROX_EQUAL(geom.total_theta, 2.0 * 2.0 * M_PI, 1e-6, "Helixline total theta (4π for 2 turns)");
TEST_APPROX_EQUAL(aabb.min().x(), -1.0, 1e-6, "AABB min X");
TEST_APPROX_EQUAL(aabb.max().x(), 1.0, 1e-6, "AABB max X");
TEST_APPROX_EQUAL(aabb.min().z(), 0.0, 1e-6, "AABB min Z");
TEST_APPROX_EQUAL(aabb.max().z(), 4.0, 1e-6, "AABB max Z");
return true;
}
bool test_helixline_tangent_normal()
{
TEST_SECTION("Helixline Geometry - Tangent & Normal");
auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 4.0, 2.0, true);
internal::helixline_geometry_data geom;
Eigen::Transform<double, 3, Eigen::AffineCompact, Eigen::DontAlign> axis_to_world;
aabb_t aabb;
geom.build_from_descriptor(axis_desc.data.helixline, axis_to_world, aabb);
// 测试起点(t=0)
Eigen::Vector3d tangent_0 = geom.calculate_tangent(0.0);
Eigen::Vector3d normal_0 = geom.calculate_normal(0.0);
TEST_APPROX_EQUAL(tangent_0.norm(), 1.0, 1e-6, "Tangent at t=0 is normalized");
TEST_APPROX_EQUAL(normal_0.norm(), 1.0, 1e-6, "Normal at t=0 is normalized");
double dot_0 = tangent_0.dot(normal_0);
TEST_APPROX_EQUAL(dot_0, 0.0, 1e-6, "Tangent and normal are perpendicular at t=0");
// 测试中点(t=0.5)
Eigen::Vector3d tangent_mid = geom.calculate_tangent(0.5);
Eigen::Vector3d normal_mid = geom.calculate_normal(0.5);
TEST_APPROX_EQUAL(tangent_mid.norm(), 1.0, 1e-6, "Tangent at t=0.5 is normalized");
TEST_APPROX_EQUAL(normal_mid.norm(), 1.0, 1e-6, "Normal at t=0.5 is normalized");
double dot_mid = tangent_mid.dot(normal_mid);
TEST_APPROX_EQUAL(dot_mid, 0.0, 1e-6, "Tangent and normal are perpendicular at t=0.5");
return true;
}
bool test_helixline_closest_point()
{
TEST_SECTION("Helixline Geometry - Closest Point");
auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
internal::helixline_geometry_data geom;
Eigen::Transform<double, 3, Eigen::AffineCompact, Eigen::DontAlign> axis_to_world;
aabb_t aabb;
geom.build_from_descriptor(axis_desc.data.helixline, axis_to_world, aabb);
// 测试点在螺旋线上
Eigen::Vector3d point_on_helix(1, 0, 0);
auto result_on = geom.calculate_closest_param(point_on_helix);
double distance = (result_on.point - point_on_helix).norm();
TEST_APPROX_EQUAL(result_on.t, 0.0, 1e-5, "Closest t for point on helix start");
TEST_ASSERT(distance < 1e-5, "Distance to helix is very small");
// 测试点在中心轴附近
Eigen::Vector3d point_center(0, 0, 1);
auto result_center = geom.calculate_closest_param(point_center);
TEST_ASSERT(result_center.point.head<2>().norm() > 0.9, "Closest point should be on helix surface");
return true;
}
bool test_helixline_left_vs_right()
{
TEST_SECTION("Helixline Left-Handed vs Right-Handed");
// 右手螺旋
auto right_axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
internal::helixline_geometry_data right_geom;
Eigen::Transform<double, 3, Eigen::AffineCompact, Eigen::DontAlign> right_transform;
aabb_t right_aabb;
right_geom.build_from_descriptor(right_axis.data.helixline, right_transform, right_aabb);
// 左手螺旋
auto left_axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, false);
internal::helixline_geometry_data left_geom;
Eigen::Transform<double, 3, Eigen::AffineCompact, Eigen::DontAlign> left_transform;
aabb_t left_aabb;
left_geom.build_from_descriptor(left_axis.data.helixline, left_transform, left_aabb);
// 在 t=0.25 处(1/4圈),法线方向应该相同
Eigen::Vector3d right_normal = right_geom.calculate_normal(0.25);
Eigen::Vector3d left_normal = left_geom.calculate_normal(0.25);
// 切线方向应该相反
Eigen::Vector3d right_tangent = right_geom.calculate_tangent(0.25);
Eigen::Vector3d left_tangent = left_geom.calculate_tangent(0.25);
Eigen::Vector3d right_world_tangent = right_transform.linear() * right_tangent;
Eigen::Vector3d left_world_tangent = left_transform.linear() * left_tangent;
TEST_ASSERT(right_normal.y() * left_normal.y() > 0, "Normals have same Y signs");
TEST_ASSERT(right_world_tangent.y() * left_world_tangent.y() < 0, "Tangents have opposite Y signs");
return true;
}
// ============================================================================
// GROUP 2: Helixline Extrude Subface 函数测试(新架构适配)
// ============================================================================
bool test_helixline_eval_sdf_basic()
{
TEST_SECTION("Helixline SDF Basic");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.5);
auto axis = TestDataFactory::createHelixlineAxis(2.0, 2.0, 1.0, true);
// ✅ 新架构:添加 &dc
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
// 测试点在螺旋线起点附近(应该在Profile内部)
Eigen::Vector3d inside_point(1.95, 0, 0.01);
double sdf_inside =
internal::get_eval_sdf_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face), inside_point);
TEST_ASSERT(sdf_inside < 0.0, "Point inside helix should have negative SDF");
// 测试点在螺旋线外部
Eigen::Vector3d outside_point(4.0, 0, 0.1);
double sdf_outside =
internal::get_eval_sdf_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face), outside_point);
TEST_ASSERT(sdf_outside > 0.0, "Point outside helix should have positive SDF");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_parametrization_roundtrip()
{
TEST_SECTION("Helixline Parametrization Round-Trip");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(2.0);
auto axis = TestDataFactory::createHelixlineAxis(5.0, 10.0, 2.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
auto map_param_to_point_with_weight_func =
internal::get_map_param_to_point_with_weight_ptr(surface_type::extrude_helixline_side);
auto map_to_param = internal::get_map_point_to_param_ptr(surface_type::extrude_helixline_side);
std::vector<Eigen::Vector2d> test_params = {
{2.500, 0.500},
{3.999, 0.999},
{0.001, 0.001},
{1.234, 0.567}
};
constexpr double point_tol = 1e-2;
for (const auto& uv : test_params) {
auto [point, surface_jacobi, volume_jacobi] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), uv);
Eigen::Vector2d uv_back = map_to_param(make_pointer_wrapper(side_face), point.head<3>());
auto [point_back, surface_jacobi_back, volume_jacobi_back] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), uv_back);
double point_error = (point.head<3>() - point_back.head<3>()).norm();
TEST_ASSERT(point_error < point_tol, "Point round-trip error for uv=" << uv.transpose());
// 允许参数在模 segment_count 意义下相等(闭合曲线)
double u_diff = std::abs(uv_back.x() - uv.x());
double v_diff = std::abs(uv_back.y() - uv.y());
// Profile 有 4 个段,参数可能在 [0, 4) 循环
if (u_diff > 2.0) { // 如果差距超过半圈
u_diff = std::abs(u_diff - 4.0);
}
TEST_ASSERT(u_diff < 1e-6, "U parameter mismatch for uv=" << uv.transpose() << " (diff=" << u_diff << ")");
TEST_ASSERT(v_diff < 1e-6, "V parameter mismatch for uv=" << uv.transpose() << " (diff=" << v_diff << ")");
}
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_sdf_gradient()
{
TEST_SECTION("Helixline SDF Gradient");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
Eigen::Vector3d test_point(1.5, 0.2, 0.5);
Eigen::Vector3d gradient =
internal::get_eval_sdf_grad_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face), test_point);
double grad_norm = gradient.norm();
TEST_APPROX_EQUAL(grad_norm, 1.0, 1e-3, "Gradient should be unit vector");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_constraint_curves()
{
TEST_SECTION("Helixline Constraint Curves");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
Eigen::Vector2d uv(0.5, 0.25);
auto du_func = internal::get_eval_du_constraint_ptr(surface_type::extrude_helixline_side);
auto dv_func = internal::get_eval_dv_constraint_ptr(surface_type::extrude_helixline_side);
auto du_result = du_func(make_pointer_wrapper(side_face), uv);
auto dv_result = dv_func(make_pointer_wrapper(side_face), uv);
// 验证切向量非零
TEST_ASSERT(du_result.grad_f.norm() > 1e-6, "U-direction tangent is non-zero");
TEST_ASSERT(dv_result.grad_f.norm() > 1e-6, "V-direction tangent is non-zero");
// 验证两个方向的切向量不平行(叉积非零)
Eigen::Vector3d cross_product = du_result.grad_f.head<3>().cross(dv_result.grad_f.head<3>());
TEST_ASSERT(cross_product.norm() > 1e-6, "U and V tangents are not parallel");
TestHelper::safeFree(prim);
return true;
}
// ============================================================================
// GROUP 3: Handedness 测试
// ============================================================================
bool test_helixline_righthanded_vs_lefthanded()
{
TEST_SECTION("Helixline Right-Handed vs Left-Handed - Detailed");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
// 右手螺旋
auto right_axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* right_prim =
static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t right_desc;
right_desc.profile_number = 1;
right_desc.profiles =
const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
right_desc.axis = right_axis.data.helixline;
right_prim->initialize_with_components(&dc, right_desc);
// 左手螺旋
auto left_axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, false);
auto* left_prim =
static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t left_desc;
left_desc.profile_number = 1;
left_desc.profiles =
const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
left_desc.axis = left_axis.data.helixline;
left_prim->initialize_with_components(&dc, left_desc);
auto map_param_to_point_with_weight_func =
internal::get_map_param_to_point_with_weight_ptr(surface_type::extrude_helixline_side);
// 测试 1/4 圈位置
Eigen::Vector2d param_quarter(0, 0.25);
auto [right_point, right_surface_jacobi, right_volume_jacobi] =
map_param_to_point_with_weight_func(make_pointer_wrapper(right_prim->subfaces[0].get_ptr()), param_quarter);
auto [left_point, left_surface_jacobi, left_volume_jacobi] =
map_param_to_point_with_weight_func(make_pointer_wrapper(left_prim->subfaces[0].get_ptr()), param_quarter);
// 右手系:逆时针旋转,Y 坐标应该 > 0
// 左手系:顺时针旋转,Y 坐标应该 < 0
TEST_ASSERT(right_point.y() > 0, "Right-handed helix rotates counter-clockwise");
TEST_ASSERT(left_point.y() < 0, "Left-handed helix rotates clockwise");
// 验证X坐标相同(径向距离)
TEST_ASSERT(std::abs(right_point.x() - left_point.x()) < 1e-6, "X coordinates should match");
// Z坐标应该相同(而不是关于中心对称)
// 原因:左右螺旋是XZ平面的镜像,镜像操作中Z坐标不变
TEST_ASSERT(std::abs(right_point.z() - left_point.z()) < 1e-6,
"Z coordinates should be the same\n"
" Right: "
<< right_point.z()
<< "\n"
" Left: "
<< left_point.z()
<< "\n"
" Z-coordinates must be equal for XZ-plane mirror symmetry");
TestHelper::safeFree(right_prim);
TestHelper::safeFree(left_prim);
return true;
}
// ============================================================================
// GROUP 4-6: Replace 和 Profile 测试
// ============================================================================
bool test_helixline_replace_profile()
{
TEST_SECTION("Helixline Replace Profile");
primitive_data_center_t dc;
auto profile1 = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile1.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto side_face_before = prim->subfaces[0].get_ptr();
Eigen::Vector3d test_point(1.2, 0, 1);
double sdf_before =
internal::get_eval_sdf_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face_before), test_point);
// 替换为更大的Profile
auto profile2 = TestDataFactory::createSquareProfile(0.6);
prim->replace_profile(profile2.descriptor);
auto side_face_after = prim->subfaces[0].get_ptr();
double sdf_after =
internal::get_eval_sdf_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face_after), test_point);
TEST_ASSERT(sdf_after < sdf_before, "SDF should decrease with larger profile");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_replace_axis()
{
TEST_SECTION("Helixline Replace Axis");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis1 = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis1.data.helixline;
prim->initialize_with_components(&dc, desc);
aabb_t aabb_before = prim->fetch_aabb();
// 替换为更大半径的Axis
auto axis2 = TestDataFactory::createHelixlineAxis(2.0, 2.0, 1.0, true);
prim->replace_axis(axis2);
aabb_t aabb_after = prim->fetch_aabb();
TEST_ASSERT(aabb_after.volume() > aabb_before.volume(), "AABB should grow with larger radius");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_with_circular_profile()
{
TEST_SECTION("Helixline with Circular Profile");
primitive_data_center_t dc;
auto profile = TestDataFactory::createCircleProfile(0.2);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Circular profile helix created successfully");
aabb_t aabb = prim->fetch_aabb();
TEST_ASSERT(aabb.volume() > 0, "AABB has positive volume");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_with_triangular_profile()
{
TEST_SECTION("Helixline with Triangular Profile");
primitive_data_center_t dc;
auto profile = TestDataFactory::createTriangleProfile(0.4);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Triangular profile helix created successfully");
TestHelper::safeFree(prim);
return true;
}
bool test_helixline_with_rectangular_profile()
{
TEST_SECTION("Helixline with Rectangular Profile");
primitive_data_center_t dc;
auto profile = TestDataFactory::createRectangleProfile(0.4, 0.2);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Helixline with rectangular profile creates valid extrude");
aabb_t aabb = prim->fetch_aabb();
TEST_ASSERT(aabb.sizes().x() > 2.0, "AABB X size includes radius + profile width");
TEST_ASSERT(aabb.sizes().y() > 2.0, "AABB Y size includes radius + profile height");
TestHelper::safeFree(prim);
return true;
}
// ============================================================================
// Main 测试运行器
// ============================================================================
/**
* @brief 测试紧密螺旋(多圈)
* @details 验证:
* 1. 10 圈的螺旋能够正确创建
* 2. AABB 高度正确
*/
bool test_helixline_tight_spiral()
{
TEST_SECTION("Helixline Tight Spiral (Many Turns)");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.2);
auto axis = TestDataFactory::createTightHelixAxis(0.5, 10.0); // 10圈
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Tight helix creates valid extrude");
aabb_t aabb = prim->fetch_aabb();
TEST_APPROX_EQUAL(aabb.max().z(), 10.0, 0.5, "AABB height matches helix height");
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试疏松螺旋(少圈)
* @details 验证:
* 1. 0.5 圈的螺旋能够正确创建
*/
bool test_helixline_loose_spiral()
{
TEST_SECTION("Helixline Loose Spiral (Few Turns)");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.5);
auto axis = TestDataFactory::createLooseHelixAxis(2.0, 2.0); // 0.5圈
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Loose helix creates valid extrude");
aabb_t aabb = prim->fetch_aabb();
TEST_APPROX_EQUAL(aabb.max().x(), 2.0 + 0.5, 0.5, "AABB includes helix radius + profile");
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试极小半径螺旋
* @details 验证:
* 1. 半径 0.1 的螺旋能够正确创建
*/
bool test_helixline_very_small_radius()
{
TEST_SECTION("Helixline Very Small Radius");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.05);
auto axis = TestDataFactory::createHelixlineAxis(0.1, 1.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Very small radius helix is valid");
aabb_t aabb = prim->fetch_aabb();
TEST_ASSERT(aabb.volume() > 0, "AABB has positive volume");
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试大半径螺旋
* @details 验证:
* 1. 大半径螺旋的 AABB 正确
*/
bool test_helixline_large_radius()
{
TEST_SECTION("Helixline Large Radius");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.5);
auto axis = TestDataFactory::createHelixlineAxis(10.0, 5.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Large radius helix is valid");
aabb_t aabb = prim->fetch_aabb();
TEST_ASSERT(aabb.sizes().x() > 20.0, "AABB X size > 2 * radius");
TEST_ASSERT(aabb.sizes().y() > 20.0, "AABB Y size > 2 * radius");
TestHelper::safeFree(prim);
return true;
}
// ============================================================================
// GROUP 7: 边界情况测试
// ============================================================================
/**
* @brief 测试单圈完整螺旋
* @details 验证:
* 1. 起点和终点在同一 XY 平面位置(但 Z 不同)
*/
bool test_helixline_single_turn()
{
TEST_SECTION("Helixline Single Complete Turn");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true); // 恰好1圈
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
// 起点和终点应该在同一个X-Y平面位置(但Z不同)
auto map_param_to_point_with_weight_func =
internal::get_map_param_to_point_with_weight_ptr(surface_type::extrude_polyline_side);
auto [start_point, start_surface_jacobi, start_volume_jacobi] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), Eigen::Vector2d(0, 0));
auto [end_point, end_surface_jacobi, end_volume_jacobi] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), Eigen::Vector2d(0, 1.0));
// X-Y位置应该接近(完整一圈)
TEST_APPROX_EQUAL(start_point.x(), end_point.x(), 0.1, "After one turn, X position returns");
TEST_APPROX_EQUAL(start_point.y(), end_point.y(), 0.1, "After one turn, Y position returns");
// Z位置应该不同
TEST_ASSERT(std::abs(end_point.z() - start_point.z()) > 1.5, "Z position increases significantly");
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试螺旋线的参数边界情况
* @details 验证:
* 1. v=0 时点在起始位置
* 2. v=1 时点在结束位置
* 3. Z方向的增量等于height
*/
bool test_helixline_parameter_boundary()
{
TEST_SECTION("Helixline Parameter Boundary Cases");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
auto map_param_to_point_with_weight_func =
internal::get_map_param_to_point_with_weight_ptr(surface_type::extrude_polyline_side);
// 测试起点 (v=0)
Eigen::Vector2d uv_start(0, 0);
auto [point_start, surface_jacobi_start, volume_jacobi_start] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), uv_start);
constexpr double radius = 1.0;
constexpr double height = 2.0;
TEST_ASSERT(std::isfinite(point_start.x()), "Start point is valid");
// 考虑Profile偏移的影响
// Profile顶点[0] = (-0.045, 0.15)
// 在TBN变换后:X = axis_point.x + B.x * profile_x + N.x * profile_y
// = 1.0 + 0*(-0.045) + (-1)*0.15 = 0.85
constexpr double profile_y_offset = 0.15; // Profile的Y偏移量
const double expected_x = radius - profile_y_offset; // = 0.85
TEST_ASSERT(std::abs(point_start.x() - expected_x) < 1e-6,
"Start X should account for profile offset\n"
" Expected: "
<< expected_x
<< "\n"
" Actual: "
<< point_start.x()
<< "\n"
" Diff: "
<< std::abs(point_start.x() - expected_x));
TEST_ASSERT(std::abs(point_start.y() - 0) < 0.02, "Start Y should be near 0");
// Z坐标不验证绝对值为0,因为Profile起点有偏移
// 测试终点 (v=1)
auto [point_end, surface_jacobi_end, volume_jacobi_end] =
map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast<subface*>(side_face)), Eigen::Vector2d(0, 1.0));
// 终点的X坐标也应该考虑profile偏移
TEST_ASSERT(std::abs(point_end.x() - expected_x) < 1e-6, "End X should account for profile offset");
TEST_ASSERT(std::abs(point_end.y() - 0) < 0.02, "End Y should be near 0");
// Z方向的增量应该等于height(这才是正确的测试)
double z_rise = point_end.z() - point_start.z();
TEST_ASSERT(std::abs(z_rise - height) < 1e-6,
"Z rise should equal height\n"
" Start Z: "
<< point_start.z()
<< "\n"
" End Z: "
<< point_end.z()
<< "\n"
" Rise: "
<< z_rise
<< "\n"
" Expected: "
<< height);
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试倾斜螺旋线(轴不沿 Z 方向)
* @details 验证:
* 1. 倾斜的螺旋线能够正确创建
*/
bool test_helixline_slanted_axis()
{
TEST_SECTION("Helixline with Slanted Axis");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
Eigen::Vector3d start(0, 0, 0);
Eigen::Vector3d end(2, 0, 2);
auto axis = TestDataFactory::createSlantedHelixAxis(0.5, start, end, 1.0);
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
TEST_ASSERT(prim != nullptr, "Slanted helix creates valid extrude");
aabb_t aabb = prim->fetch_aabb();
TEST_ASSERT(aabb.volume() > 0, "AABB has positive volume");
TestHelper::safeFree(prim);
return true;
}
/**
* @brief 测试零螺距螺旋(理论上退化为圆)
* @details 验证:
* 1. 能否处理这种边界情况
*/
bool test_helixline_zero_pitch()
{
TEST_SECTION("Helixline with Zero Pitch (Edge Case)");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
// 零螺距:advance_per_round 极大,使得实际上没有上升
axis_descriptor_t axis;
axis.type = AXIS_TYPE_HELIXLINE;
axis.data.helixline.axis_start = {0, 0, 0};
axis.data.helixline.axis_end = {0, 0, 0.001}; // 极小高度
axis.data.helixline.start_direction = {1, 0, 0};
axis.data.helixline.radius = 1.0;
axis.data.helixline.advance_per_round = 1e-6; // 极小螺距
axis.data.helixline.is_righthanded = true;
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
// 这可能会失败或产生退化几何,测试系统是否能处理
try {
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
std::cout << " Zero pitch helix created (may be degenerate)\n";
TestHelper::safeFree(prim);
} catch (...) {
std::cout << " Zero pitch helix failed to create (expected for edge case)\n";
}
return true;
}
/**
* @brief 测试多圈螺旋的 SDF 一致性
* @details 验证:
* 1. 螺旋每一圈的 SDF 计算一致
*/
bool test_helixline_multi_turn_consistency()
{
TEST_SECTION("Helixline Multi-Turn SDF Consistency");
primitive_data_center_t dc;
auto profile = TestDataFactory::createSquareProfile(0.3);
auto axis = TestDataFactory::createHelixlineAxis(1.0, 6.0, 3.0, true); // 3圈
auto* prim = static_cast<internal::extrude_helixline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc));
extrude_helixline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.data.helixline;
prim->initialize_with_components(&dc, desc);
auto subfaces = prim->get_subfaces();
auto side_face = subfaces[0].get_ptr();
// 在每一圈的相同相位位置测试 SDF
std::vector<Eigen::Vector3d> test_points = {
{1.2, 0, 0.5}, // 第1圈 1/4 位置
{1.2, 0, 2.5}, // 第2圈 1/4 位置
{1.2, 0, 4.5} // 第3圈 1/4 位置
};
std::vector<double> sdfs;
for (const auto& point : test_points) {
double sdf = internal::get_eval_sdf_ptr(surface_type::extrude_helixline_side)(make_pointer_wrapper(side_face), point);
sdfs.push_back(sdf);
}
// 每一圈相同位置的 SDF 应该相近
TEST_APPROX_EQUAL(sdfs[0], sdfs[1], 0.1, "SDF consistent between turn 1 and 2");
TEST_APPROX_EQUAL(sdfs[1], sdfs[2], 0.1, "SDF consistent between turn 2 and 3");
TestHelper::safeFree(prim);
return true;
}
// ============================================================================
// MAIN 函数
// ============================================================================
int main()
{
SetConsoleOutputCP(CP_UTF8);
struct TestCase {
std::string name;
std::function<bool(void)> func;
std::string group;
};
std::vector<TestCase> tests = {
// Group 1: Helixline Geometry (4个测试)
{"Helixline Construction", test_helixline_construction, "Geometry" },
{"Helixline Tangent & Normal", test_helixline_tangent_normal, "Geometry" },
{"Helixline Closest Point", test_helixline_closest_point, "Geometry" },
{"Helixline Left vs Right", test_helixline_left_vs_right, "Geometry" },
// Group 2: Subface Functions (4个测试)
{"Helixline SDF Basic", test_helixline_eval_sdf_basic, "Subface" },
{"Helixline Param Round-Trip", test_helixline_parametrization_roundtrip, "Subface" },
{"Helixline SDF Gradient", test_helixline_sdf_gradient, "Subface" },
{"Helixline Constraint Curves", test_helixline_constraint_curves, "Subface" },
// Group 3: Handedness (1个测试)
{"Right vs Left Handed", test_helixline_righthanded_vs_lefthanded, "Handedness"},
// Group 4: Replace (2个测试)
{"Replace Profile", test_helixline_replace_profile, "Replace" },
{"Replace Axis", test_helixline_replace_axis, "Replace" },
// Group 5: Various Profiles (3个测试)
{"Circular Profile", test_helixline_with_circular_profile, "Profiles" },
{"Triangular Profile", test_helixline_with_triangular_profile, "Profiles" },
{"Rectangular Profile", test_helixline_with_rectangular_profile, "Profiles" },
// Group 6: Edge Cases
{"Slant ed Axis", test_helixline_slanted_axis, "EdgeCase" },
{"Multi-Turn Consistency", test_helixline_multi_turn_consistency, "EdgeCase" },
};
int passed = 0;
int total = static_cast<int>(tests.size());
std::string current_group;
for (const auto& test : tests) {
if (test.group != current_group) {
current_group = test.group;
std::cout << "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
std::cout << " GROUP: " << current_group << "\n";
std::cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
}
try {
if (test.func()) { passed++; }
} catch (const std::exception& e) {
std::cerr << "❌ EXCEPTION in " << test.name << ": " << e.what() << "\n";
} catch (...) {
std::cerr << "❌ UNKNOWN EXCEPTION in " << test.name << "\n";
}
}
TestHelper::printStatistics(passed, total);
return (passed == total) ? 0 : 1;
}