#define _USE_MATH_DEFINES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ============================================================================ // 测试框架宏定义 // ============================================================================ #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 points; std::vector bulges; profile_descriptor_t descriptor; void finalize() { descriptor.point_number = static_cast(points.size()); descriptor.points = points.data(); descriptor.bulge_number = static_cast(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 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 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 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 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 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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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 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(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(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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t right_desc; right_desc.profile_number = 1; right_desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t left_desc; left_desc.profile_number = 1; left_desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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(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(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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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(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(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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); // 这可能会失败或产生退化几何,测试系统是否能处理 try { extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc)); extrude_helixline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&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 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 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 func; std::string group; }; std::vector 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(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; }