#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: // ========== Profile 工厂 ========== 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}; } }; // 正方形 (CW winding) static ProfileData createSquareProfile(double size = 1.0) { ProfileData data; double half = size / 2.0; // CW winding: bottom-left → top-left → top-right → bottom-right 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} }; // CW 弧:弧段1 (r,0)→(-r,0),bulge=+1 → d=(0,-r),弧经底部,CW ✓ // 弧段2 (-r,0)→(r,0),bulge=+1 → d=(0,+r),弧经顶部,CW ✓ data.bulges = {1.0, 1.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; // CW winding 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 ProfileData createTriangleProfile(double size = 1.0) { ProfileData data; double half = size / 2.0; double h = size * std::sqrt(3.0) / 2.0; // CW winding: reverse vertex order 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; } // L形(非凸) static ProfileData createLShapeProfile(double size = 1.0) { ProfileData data; double s = size; double half = size / 2.0; // CW winding: reverse vertex order data.points = { {0, s, 0.0}, {half, s, 0.0}, {half, half, 0.0}, {s, half, 0.0}, {s, 0, 0.0}, {0, 0, 0.0} }; data.bulges = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; data.finalize(); return data; } // ========== Axis 工厂 ========== struct AxisData { std::vector points; std::vector bulges; axis_descriptor_t descriptor; void finalizeAsPolyline(const vector3d& ref_normal = {1, 0, 0}) { descriptor.type = AXIS_TYPE_POLYLINE; descriptor.data.polyline.point_number = static_cast(points.size()); descriptor.data.polyline.points = points.data(); descriptor.data.polyline.bulge_number = static_cast(bulges.size()); descriptor.data.polyline.bulges = bulges.data(); descriptor.data.polyline.is_close = false; descriptor.data.polyline.reference_normal = ref_normal; } }; // 直线Axis(Z方向) static AxisData createStraightAxis(double length = 2.0) { AxisData data; data.points = { {0, 0, 0 }, {0, 0, length} }; data.bulges = {0.0}; data.finalizeAsPolyline(); return data; } // 倾斜直线Axis static AxisData createSlantedAxis(const Eigen::Vector3d& start, const Eigen::Vector3d& end) { AxisData data; data.points = { {start.x(), start.y(), start.z()}, {end.x(), end.y(), end.z() } }; data.bulges = {0.0}; data.finalizeAsPolyline(); return data; } // 折线Axis(多段) static AxisData createPolylineAxis(const std::vector& waypoints) { AxisData data; for (const auto& pt : waypoints) { data.points.push_back({pt.x(), pt.y(), pt.z()}); } data.bulges.resize(waypoints.size() - 1, 0.0); data.finalizeAsPolyline(); return data; } // Z字形Axis static AxisData createZigZagAxis(double segment_length = 1.0, int num_segments = 3) { AxisData data; for (int i = 0; i <= num_segments; ++i) { double x = (i % 2 == 0) ? 0.0 : 0.5; double z = i * segment_length; data.points.push_back({x, 0, z}); } data.bulges.resize(num_segments, 0.0); data.finalizeAsPolyline(); return data; } // Helixline Axis static axis_descriptor_t createHelixlineAxis(double radius, double height, double turns = 2.0, bool is_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 = is_righthanded; return desc; } }; // ============================================================================ // 测试辅助工具 // ============================================================================ class TestHelper { public: /** * @brief 安全销毁 primitive * @param ptr primitive 指针 */ static void safeFree(primitive* ptr) { if (!ptr) return; ptr->destroy(); mi_free(ptr); } // 验证AABB 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; } // 验证SDF符号 static bool verifySDF(double sdf, const std::string& expected_region, double surface_tol = 1e-4) { if (expected_region == "inside") return sdf < surface_tol; if (expected_region == "outside") return sdf > -surface_tol; if (expected_region == "surface") return std::abs(sdf) < surface_tol; return false; } // 打印统计信息 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"; } }; // ============================================================================ // 第1组:Polyline Geometry 基础测试 // ============================================================================ bool test_polyline_basic_shapes() { TEST_SECTION("Polyline Geometry - Basic Shapes Construction"); Eigen::Vector3d proj_x(1, 0, 0); Eigen::Vector3d proj_y(0, 1, 0); Eigen::Vector3d origin(0, 0, 0); internal::aabb_t_dim<2> aabb; // 测试1: 正方形 { auto profile = TestDataFactory::createSquareProfile(1.0); internal::polyline_geometry_data geom; geom.build_as_profile(*reinterpret_cast(&profile.descriptor), proj_x, proj_y, origin, aabb); TEST_ASSERT(geom.vertices.size() == 5, "Square: 5 vertices (closed)"); TEST_ASSERT(geom.start_indices.size() == 4, "Square: 4 segments"); } // 测试2: 圆形 { auto profile = TestDataFactory::createCircleProfile(0.5); internal::polyline_geometry_data geom; geom.build_as_profile(*reinterpret_cast(&profile.descriptor), proj_x, proj_y, origin, aabb); TEST_ASSERT(geom.vertices.size() > 5, "Circle: multiple vertices"); } // 测试3: 三角形 { auto profile = TestDataFactory::createTriangleProfile(1.0); internal::polyline_geometry_data geom; geom.build_as_profile(*reinterpret_cast(&profile.descriptor), proj_x, proj_y, origin, aabb); TEST_ASSERT(geom.vertices.size() == 4, "Triangle: 4 vertices (closed)"); TEST_ASSERT(geom.start_indices.size() == 3, "Triangle: 3 segments"); } return true; } bool test_polyline_sdf_calculation() { TEST_SECTION("Polyline SDF - Distance Field Calculation"); auto profile = TestDataFactory::createSquareProfile(1.0); internal::polyline_geometry_data geom; Eigen::Vector3d proj_x(1, 0, 0); Eigen::Vector3d proj_y(0, 1, 0); Eigen::Vector3d origin(0, 0, 0); internal::aabb_t_dim<2> aabb; geom.build_as_profile(*reinterpret_cast(&profile.descriptor), proj_x, proj_y, origin, aabb); // 测试点集合 struct TestPoint { Eigen::Vector3d point; std::string region; }; std::vector test_points = { {{0, 0, 0}, "inside" }, {{0.5, 0, 0}, "surface"}, {{0, 0.5, 0}, "surface"}, {{-0.5, -0.5, 0}, "surface"}, {{0.7, 0, 0}, "outside"}, {{0, 0.3, 0}, "inside" } }; int passed = 0; for (const auto& tp : test_points) { auto closest = geom.calculate_closest_param(tp.point); bool is_outside = geom.pmc(tp.point.head<2>(), closest.first); double sdf = is_outside ? std::abs(closest.second) : -std::abs(closest.second); if (TestHelper::verifySDF(sdf, tp.region)) { passed++; } } TEST_ASSERT(passed >= static_cast(test_points.size() * 0.80), "At least 80% of SDF tests pass (" << passed << "/" << test_points.size() << ")"); return true; } bool test_polyline_closest_point() { TEST_SECTION("Polyline - Closest Point Projection"); auto profile = TestDataFactory::createSquareProfile(1.0); internal::polyline_geometry_data geom; Eigen::Vector3d proj_x(1, 0, 0); Eigen::Vector3d proj_y(0, 1, 0); Eigen::Vector3d origin(0, 0, 0); internal::aabb_t_dim<2> aabb; geom.build_as_profile(*reinterpret_cast(&profile.descriptor), proj_x, proj_y, origin, aabb); // 测试不同位置的最近点投影 std::vector test_points = { {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {-1, -1, 0} }; for (const auto& pt : test_points) { auto result = geom.calculate_closest_param(pt); TEST_ASSERT(result.first.point.allFinite(), "Closest point computed for " << pt.transpose()); } return true; } // ============================================================================ // 第2组:Helixline Geometry 测试 // ============================================================================ bool test_helixline_construction() { TEST_SECTION("Helixline - Construction and Basic Properties"); auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 2.0, true); internal::helixline_geometry_data geom; Eigen::Transform transform; aabb_t aabb; geom.build_from_descriptor(axis_desc.data.helixline, transform, aabb); TEST_APPROX_EQUAL(geom.radius, 1.0, 1e-6, "Helixline radius"); TEST_APPROX_EQUAL(geom.height, 2.0, 1e-6, "Helixline height"); TEST_APPROX_EQUAL(geom.total_theta, 4.0 * M_PI, 1e-5, "Total rotation (2 turns)"); TEST_ASSERT(axis_desc.data.helixline.is_righthanded == true, "Right-handed descriptor flag"); return true; } bool test_helixline_tangent_normal() { TEST_SECTION("Helixline - Tangent and Normal Vectors"); auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true); internal::helixline_geometry_data geom; Eigen::Transform transform; aabb_t aabb; geom.build_from_descriptor(axis_desc.data.helixline, transform, aabb); // 测试多个参数位置 std::vector test_params = {0.0, 0.25, 0.5, 0.75, 1.0}; for (double t : test_params) { auto tangent = geom.calculate_tangent(t); auto normal = geom.calculate_normal(t); TEST_APPROX_EQUAL(tangent.norm(), 1.0, 1e-6, "Tangent normalized at t=" << t); TEST_APPROX_EQUAL(normal.norm(), 1.0, 1e-6, "Normal normalized at t=" << t); TEST_APPROX_EQUAL(tangent.dot(normal), 0.0, 1e-6, "Tangent ⊥ Normal at t=" << t); } return true; } bool test_helixline_closest_point() { TEST_SECTION("Helixline - Closest Point Calculation"); auto axis_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true); internal::helixline_geometry_data geom; Eigen::Transform transform; aabb_t aabb; geom.build_from_descriptor(axis_desc.data.helixline, transform, aabb); // 测试轴上点 auto result1 = geom.calculate_closest_param(Eigen::Vector3d(0, 0, 1.0)); TEST_APPROX_EQUAL(result1.t, 0.5, 0.15, "Closest t for point on axis"); // 测试外部点 auto result2 = geom.calculate_closest_param(Eigen::Vector3d(2, 0, 1.0)); TEST_ASSERT(result2.point.norm() > 0, "Closest point calculated for external point"); return true; } bool test_helixline_left_vs_right() { TEST_SECTION("Helixline - Left-handed vs Right-handed"); auto right_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, true); auto left_desc = TestDataFactory::createHelixlineAxis(1.0, 2.0, 1.0, false); // 验证descriptor中的标志 TEST_ASSERT(right_desc.data.helixline.is_righthanded == true, "Right-handed descriptor"); TEST_ASSERT(left_desc.data.helixline.is_righthanded == false, "Left-handed descriptor"); // 构建几何数据 internal::helixline_geometry_data right_geom, left_geom; Eigen::Transform transform; aabb_t aabb; right_geom.build_from_descriptor(right_desc.data.helixline, transform, aabb); left_geom.build_from_descriptor(left_desc.data.helixline, transform, aabb); // 两者的半径和高度应该相同 TEST_APPROX_EQUAL(right_geom.radius, left_geom.radius, 1e-6, "Same radius"); TEST_APPROX_EQUAL(right_geom.height, left_geom.height, 1e-6, "Same height"); return true; } // ============================================================================ // 第3组:Extrude 创建和初始化测试 // ============================================================================ bool test_extrude_polyline_creation() { TEST_SECTION("Extrude Polyline - Basic Creation"); primitive_data_center_t dc; // ✅ 方案3:手动管理数据生命周期 std::vector profile_points = { {-0.5, -0.5, 0.0}, {-0.5, 0.5, 0.0}, {0.5, 0.5, 0.0}, {0.5, -0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); // ✅ 指向局部vector profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); // ✅ 指向局部vector axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto subfaces = ext_poly->get_subfaces(); TEST_ASSERT(subfaces.size() == 3, "Extrude has 3 subfaces"); TEST_ASSERT(subfaces[0].get_ptr() != nullptr, "Side face exists"); TEST_ASSERT(subfaces[1].get_ptr() != nullptr, "Bottom cap exists"); TEST_ASSERT(subfaces[2].get_ptr() != nullptr, "Top cap exists"); TestHelper::safeFree(extrude); return true; } bool test_extrude_helixline_creation() { TEST_SECTION("Extrude Helixline - Basic Creation"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(0.5); auto axis = TestDataFactory::createHelixlineAxis(1.0, 2.0, 2.0, true); // 构建完整的 extrude_helixline_descriptor_t extrude_helixline_descriptor_t helix_desc; helix_desc.profile_number = 1; helix_desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); helix_desc.axis = axis.data.helixline; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc); auto* ext_helix = static_cast(extrude); ext_helix->initialize_with_components(&dc, helix_desc); auto subfaces = ext_helix->get_subfaces(); TEST_ASSERT(subfaces.size() == 3, "Helixline extrude has 3 subfaces"); TestHelper::safeFree(extrude); return true; } bool test_extrude_aabb_computation() { TEST_SECTION("Extrude - AABB Computation Correctness"); primitive_data_center_t dc; // 测试不同尺寸组合 struct AABBTest { double profile_size; double axis_length; Eigen::Vector3d expected_min; Eigen::Vector3d expected_max; }; std::vector tests = { {1.0, 2.0, {-0.5, -0.5, -0.5}, {0.5, 0.5, 2.5}}, {2.0, 1.0, {-1.0, -1.0, -1.0}, {1.0, 1.0, 2.0}} }; for (const auto& test : tests) { auto profile = TestDataFactory::createSquareProfile(test.profile_size); auto axis = TestDataFactory::createStraightAxis(test.axis_length); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto aabb = ext_poly->fetch_aabb(); bool ok = TestHelper::verifyAABB(aabb, test.expected_min, test.expected_max, 0.02); TEST_ASSERT(ok, "AABB correct for profile=" << test.profile_size); TestHelper::safeFree(extrude); } return true; } bool test_extrude_descriptor_sharing() { TEST_SECTION("Extrude - Descriptor Sharing"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); // 创建两个使用相同descriptor的extrude auto* extrude1 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext1 = static_cast(extrude1); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext1->initialize_with_components(&dc, desc); auto* extrude2 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext2 = static_cast(extrude2); ext2->initialize_with_components(&dc, desc); auto subfaces1 = ext1->get_subfaces(); auto subfaces2 = ext2->get_subfaces(); auto* side1 = static_cast(subfaces1[0].get_ptr()); auto* side2 = static_cast(subfaces2[0].get_ptr()); TEST_ASSERT(side1->geometry_ptr == side2->geometry_ptr, "Same geometry params are shared"); TestHelper::safeFree(extrude1); TestHelper::safeFree(extrude2); return true; } bool test_extrude_various_profiles() { TEST_SECTION("Extrude - Various Profile Shapes"); primitive_data_center_t dc; // 在外部作用域创建所有ProfileData auto square_data = TestDataFactory::createSquareProfile(); auto circle_data = TestDataFactory::createCircleProfile(0.5); auto triangle_data = TestDataFactory::createTriangleProfile(); auto axis_data = TestDataFactory::createStraightAxis(2.0); // Test 1: Square { auto* extrude = static_cast(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc)); extrude_polyline_descriptor_t square_desc; square_desc.profile_number = 1; square_desc.profiles = const_cast(reinterpret_cast(&square_data.descriptor)); square_desc.axis = axis_data.descriptor.data.polyline; extrude->initialize_with_components(&dc, square_desc); auto aabb = extrude->fetch_aabb(); TEST_ASSERT(aabb.min().x() >= -0.6 && aabb.max().x() <= 0.6, "Square profile creates valid AABB"); extrude->destroy(); mi_free(extrude); } // Test 2: Circle { auto* extrude = static_cast(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc)); extrude_polyline_descriptor_t circle_desc; circle_desc.profile_number = 1; circle_desc.profiles = const_cast(reinterpret_cast(&circle_data.descriptor)); circle_desc.axis = axis_data.descriptor.data.polyline; extrude->initialize_with_components(&dc, circle_desc); auto aabb = extrude->fetch_aabb(); TEST_ASSERT(aabb.min().x() >= -0.6 && aabb.max().x() <= 0.6, "Circle profile creates valid AABB"); extrude->destroy(); mi_free(extrude); } // Test 3: Triangle { auto* extrude = static_cast(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc)); extrude_polyline_descriptor_t triangle_desc; triangle_desc.profile_number = 1; triangle_desc.profiles = const_cast(reinterpret_cast(&triangle_data.descriptor)); triangle_desc.axis = axis_data.descriptor.data.polyline; extrude->initialize_with_components(&dc, triangle_desc); auto aabb = extrude->fetch_aabb(); TEST_ASSERT(aabb.min().x() >= -0.6 && aabb.max().x() <= 0.6, "Triangle profile creates valid AABB"); extrude->destroy(); mi_free(extrude); } // 所有ProfileData在这里才析构 return true; } // ============================================================================ // 第4组:SDF 和参数化测试 // ============================================================================ bool test_extrude_sdf_basic() { TEST_SECTION("Extrude SDF - Basic Distance Tests"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto subfaces = ext_poly->get_subfaces(); auto* side_face = subfaces[0].get_ptr(); auto side_type = ext_poly->get_subface_types()[0]; auto eval_sdf = internal::get_eval_sdf_ptr(side_type); // 基础测试点 struct SDFTest { Eigen::Vector3d point; std::string region; }; std::vector tests = { {{0, 0, 1.0}, "inside" }, {{0.5, 0, 1.0}, "surface"}, {{1.0, 0, 1.0}, "outside"} }; int passed = 0; for (const auto& test : tests) { double sdf = eval_sdf(make_pointer_wrapper(static_cast(side_face)), test.point); if (TestHelper::verifySDF(sdf, test.region, 0.1)) { passed++; } } TEST_ASSERT(passed >= 2, "Most SDF tests pass (" << passed << "/" << tests.size() << ")"); TestHelper::safeFree(extrude); return true; } bool test_extrude_parametrization() { TEST_SECTION("Extrude Parametrization - Basic Tests"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto subfaces = ext_poly->get_subfaces(); auto* side_face = subfaces[0].get_ptr(); auto side_type = ext_poly->get_subface_types()[0]; auto map_param_to_point_with_weight = internal::get_map_param_to_point_with_weight_ptr(side_type); // 测试几个参数点 std::vector test_params = { {0.0, 0.0}, {0.5, 0.5}, {1.0, 0.5} }; for (const auto& uv : test_params) { // 使用结构化绑定提取返回值 auto [point, surface_jacobi, volume_jacobi] = map_param_to_point_with_weight(make_pointer_wrapper(static_cast(side_face)), uv); TEST_ASSERT(point.head<3>().allFinite(), "Valid point at uv=" << uv.transpose()); } TestHelper::safeFree(extrude); return true; } // ============================================================================ // 第5组:Transform 测试 // ============================================================================ bool test_extrude_transform_scale() { TEST_SECTION("Extrude Transform - Scale"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto orig_aabb = ext_poly->fetch_aabb(); // 应用缩放 ext_poly->apply_transform(internal::transform_type::scale, Eigen::Vector4d(2, 2, 3, 0)); auto scaled_aabb = ext_poly->fetch_aabb(); auto orig_size = orig_aabb.sizes(); auto scaled_size = scaled_aabb.sizes(); TEST_APPROX_EQUAL(scaled_size.x(), orig_size.x() * 2, 0.05, "X scaled correctly"); TEST_APPROX_EQUAL(scaled_size.y(), orig_size.y() * 2, 0.05, "Y scaled correctly"); TEST_APPROX_EQUAL(scaled_size.z(), orig_size.z() * 3, 0.05, "Z scaled correctly"); TestHelper::safeFree(extrude); return true; } bool test_extrude_transform_translation() { TEST_SECTION("Extrude Transform - Translation"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); // 获取并立即缓存 auto orig_aabb = ext_poly->fetch_aabb(); Eigen::Vector3d orig_min = orig_aabb.min(); Eigen::Vector3d orig_max = orig_aabb.max(); // 应用平移 Eigen::Vector3d translation(1.0, 2.0, 3.0); ext_poly->apply_transform(internal::transform_type::translation, Eigen::Vector4d(translation.x(), translation.y(), translation.z(), 0)); auto translated_aabb = ext_poly->fetch_aabb(); Eigen::Vector3d trans_min = translated_aabb.min(); Eigen::Vector3d trans_max = translated_aabb.max(); // 计算期望值 Eigen::Vector3d expected_min = orig_min + translation; Eigen::Vector3d expected_max = orig_max + translation; // 使用缓存的值测试 TEST_VECTOR_APPROX_EQUAL(trans_min, expected_min, 0.05, "AABB min translated correctly"); TEST_VECTOR_APPROX_EQUAL(trans_max, expected_max, 0.05, "AABB max translated correctly"); TestHelper::safeFree(extrude); return true; } // ============================================================================ // 第6组:Replace Profile/Axis 测试 // ============================================================================ bool test_replace_profile() { TEST_SECTION("Replace Profile - Functionality"); primitive_data_center_t dc; auto profile1 = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile1.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto orig_aabb = ext_poly->fetch_aabb(); // 替换为更大的profile std::cout << "[try to replace profile]" << std::endl; auto profile2 = TestDataFactory::createSquareProfile(2.0); ext_poly->replace_profile(profile2.descriptor); auto new_aabb = ext_poly->fetch_aabb(); TEST_ASSERT(new_aabb.sizes().x() > orig_aabb.sizes().x(), "Width increased after profile replacement"); TestHelper::safeFree(extrude); return true; } bool test_replace_axis() { TEST_SECTION("Replace Axis - Functionality"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis1 = TestDataFactory::createStraightAxis(2.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis1.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto orig_aabb = ext_poly->fetch_aabb(); // 替换为更长的axis auto axis2 = TestDataFactory::createStraightAxis(4.0); ext_poly->replace_axis(axis2.descriptor); auto new_aabb = ext_poly->fetch_aabb(); TEST_ASSERT(new_aabb.sizes().z() > orig_aabb.sizes().z(), "Length increased after axis replacement"); TestHelper::safeFree(extrude); return true; } // ============================================================================ // 第7组:边界条件测试 // ============================================================================ bool test_edge_cases_small_dimensions() { TEST_SECTION("Edge Cases - Small Dimensions"); primitive_data_center_t dc; // 很小的profile auto profile = TestDataFactory::createSquareProfile(0.1); auto axis = TestDataFactory::createStraightAxis(1.0); auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto aabb = ext_poly->fetch_aabb(); TEST_ASSERT(!aabb.isEmpty(), "Small profile creates valid extrude"); TestHelper::safeFree(extrude); return true; } // ============================================================================ // 第8组:性能测试 // ============================================================================ bool test_performance_batch_creation() { TEST_SECTION("Performance - Batch Primitive Creation"); primitive_data_center_t dc; auto profile = TestDataFactory::createSquareProfile(1.0); auto axis = TestDataFactory::createStraightAxis(2.0); const int num_primitives = 50; std::vector primitives; auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < num_primitives; ++i) { auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile.descriptor)); desc.axis = axis.descriptor.data.polyline; ext_poly->initialize_with_components(&dc, desc); primitives.push_back(extrude); } auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end - start); TEST_ASSERT(duration.count() < 3000, "Creating " << num_primitives << " primitives in < 3s (" << duration.count() << "ms)"); // 清理 for (auto* p : primitives) { TestHelper::safeFree(p); } return true; } // ============================================================================ // Phase 1.1: Subface Function Tests - Polyline // ============================================================================ /** * @brief 测试 Polyline 侧面的 SDF 计算 * @details 验证: * 1. 内部点的 SDF < 0 * 2. 外部点的 SDF > 0 * 3. 边界点的 SDF ≈ 0 */ bool test_polyline_eval_sdf_detailed() { TEST_SECTION("Polyline SDF Evaluation - Detailed"); primitive_data_center_t dc; // 创建正方形 profile (1x1), 沿 Z 轴拉伸 2 单位 // CW 绕向: bottom-left → top-left → top-right → bottom-right std::vector profile_points = { {-0.5, -0.5, 0.0}, {-0.5, 0.5, 0.0}, {0.5, 0.5, 0.0}, {0.5, -0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto* side_face = static_cast(ext_poly->subfaces[0].get_ptr()); // 获取 eval_sdf 函数指针 auto eval_sdf_func = internal::get_eval_sdf_ptr(surface_type::extrude_polyline_side); // 测试点 1: 内部点 (应该是负值) Eigen::Vector3d inside_point(0.0, 0.0, 1.0); // 中心点 double sdf_inside = eval_sdf_func(make_pointer_wrapper(static_cast(side_face)), inside_point); TEST_ASSERT(sdf_inside < 0, "Inside point has negative SDF: " << sdf_inside); // 测试点 2: 外部点 (应该是正值) Eigen::Vector3d outside_point(1.0, 0.0, 1.0); // 距离表面 0.5 单位 double sdf_outside = eval_sdf_func(make_pointer_wrapper(static_cast(side_face)), outside_point); TEST_ASSERT(sdf_outside > 0, "Outside point has positive SDF: " << sdf_outside); TEST_APPROX_EQUAL(sdf_outside, 0.5, 0.01, "Outside point SDF magnitude"); // 测试点 3: 边界点 (应该接近 0) Eigen::Vector3d boundary_point(0.5, 0.0, 1.0); // 正方形边上 double sdf_boundary = eval_sdf_func(make_pointer_wrapper(static_cast(side_face)), boundary_point); TEST_APPROX_EQUAL(sdf_boundary, 0.0, 0.01, "Boundary point SDF close to zero"); // 测试点 4: 远离表面的点 Eigen::Vector3d far_point(5.0, 0.0, 1.0); double sdf_far = eval_sdf_func(make_pointer_wrapper(static_cast(side_face)), far_point); TEST_ASSERT(sdf_far > 4.0, "Far point has large positive SDF: " << sdf_far); TestHelper::safeFree(extrude); return true; } /** * @brief 测试 Polyline 侧面的 SDF 梯度计算 * @details 验证: * 1. 梯度是单位向量 * 2. 梯度方向正确(指向外侧) * 3. 数值稳定性 */ bool test_polyline_eval_sdf_grad() { TEST_SECTION("Polyline SDF Gradient Evaluation"); primitive_data_center_t dc; std::vector profile_points = { {-0.5, -0.5, 0.0}, {-0.5, 0.5, 0.0}, {0.5, 0.5, 0.0}, {0.5, -0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto* side_face = static_cast(ext_poly->subfaces[0].get_ptr()); auto eval_sdf_grad_func = internal::get_eval_sdf_grad_ptr(surface_type::extrude_polyline_side); // 测试点: 右侧外部 Eigen::Vector3d test_point(1.0, 0.0, 1.0); Eigen::Vector3d gradient = eval_sdf_grad_func(make_pointer_wrapper(static_cast(side_face)), test_point); // 1. 梯度应该是单位向量 double grad_norm = gradient.norm(); TEST_APPROX_EQUAL(grad_norm, 1.0, 0.01, "Gradient is normalized"); // 2. 梯度应该指向 +X 方向(远离表面) TEST_ASSERT(gradient.x() > 0.9, "Gradient points outward in X direction: " << gradient.transpose()); // 3. Y 和 Z 分量应该接近 0 TEST_APPROX_EQUAL(gradient.y(), 0.0, 0.1, "Gradient Y component near zero"); TEST_APPROX_EQUAL(gradient.z(), 0.0, 0.1, "Gradient Z component near zero"); TestHelper::safeFree(extrude); return true; } /** * @brief 测试参数化的往返转换 * @details 验证: * map_point_to_param(map_param_to_point(u,v)) ≈ (u,v) */ bool test_polyline_map_param_roundtrip() { TEST_SECTION("Polyline Parametrization Round-Trip (FIXED)"); primitive_data_center_t dc; // CW 绕向: bottom-left → top-left → top-right → bottom-right std::vector profile_points = { {-0.5, -0.5, 0.0}, {-0.5, 0.5, 0.0}, {0.5, 0.5, 0.0}, {0.5, -0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto* side_face = static_cast(ext_poly->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); auto map_point_to_param_func = internal::get_map_point_to_param_ptr(surface_type::extrude_polyline_side); // 使用边上的点,避开顶点 std::vector test_params = { {0.25, 0.0 }, // 第1段(左边 → 右边)25%处 {0.75, 0.25}, // 第1段 75%处,高度25% {1.5, 0.5 }, // 第2段(下边 → 上边)50%处 {2.5, 0.75}, // 第3段(右边 → 左边)50%处 {3.5, 1.0 }, // 第4段(上边 → 下边)50%处 {0.1, 0.1 }, // 接近起点但不在顶点 }; std::cout << "\n[Round-Trip Test] Testing " << test_params.size() << " points:\n"; for (const auto& uv_original : test_params) { // Step 1: (u,v) → Point auto [point_4d, surface_jacobi, volume_jacobi] = map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast(side_face)), uv_original); Eigen::Vector3d point = point_4d.head<3>(); // Step 2: Point → (u',v') Eigen::Vector2d uv_recovered = map_point_to_param_func(make_pointer_wrapper(static_cast(side_face)), point); // Step 3: (u',v') → Point' auto [point_recovered_4d, surface_recovered_jacobi, volume_recovered_jacobi] = map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast(side_face)), uv_recovered); Eigen::Vector3d point_recovered = point_recovered_4d.head<3>(); // 比较点坐标,而不是参数 // 这样可以避免封闭曲线参数歧义的问题 double point_distance = (point - point_recovered).norm(); TEST_ASSERT(point_distance < 0.001, "Point round-trip for (u,v) = " << uv_original.transpose() << " (point error: " << point_distance << ")"); } TestHelper::safeFree(extrude); return true; } /** * @brief 测试约束曲线的一致性 * @details 验证: * eval_du_constraint 是 map_param_to_point 对 u 的偏导数 */ bool test_polyline_constraint_curves() { TEST_SECTION("Polyline Constraint Curve Consistency"); primitive_data_center_t dc; std::vector profile_points = { {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto* side_face = static_cast(ext_poly->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); auto eval_du_constraint_func = internal::get_eval_du_constraint_ptr(surface_type::extrude_polyline_side); auto eval_dv_constraint_func = internal::get_eval_dv_constraint_ptr(surface_type::extrude_polyline_side); Eigen::Vector2d uv(1.5, 0.5); // 获取约束曲线结果 auto du_result = eval_du_constraint_func(make_pointer_wrapper(static_cast(side_face)), uv); auto dv_result = eval_dv_constraint_func(make_pointer_wrapper(static_cast(side_face)), uv); // 数值计算偏导数 constexpr double eps = 1e-6; // 使用结构化绑定提取返回值 auto [f_center, center_surface_jacobi, center_volume_jacobi] = map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast(side_face)), uv); auto [f_u_plus, u_plus_surface_jacobi, u_plus_volume_jacobi] = map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast(side_face)), Eigen::Vector2d(uv.x() + eps, uv.y())); auto [f_v_plus, v_plus_surface_jacobi, v_plus_volume_jacobi] = map_param_to_point_with_weight_func(make_pointer_wrapper(static_cast(side_face)), Eigen::Vector2d(uv.x(), uv.y() + eps)); Eigen::Vector4d numerical_du = (f_u_plus - f_center) / eps; Eigen::Vector4d numerical_dv = (f_v_plus - f_center) / eps; // 验证一致性 TEST_VECTOR_APPROX_EQUAL(du_result.f, f_center, 1e-8, "du constraint returns correct point"); TEST_VECTOR_APPROX_EQUAL(du_result.grad_f, numerical_du, 0.01, "du constraint gradient matches numerical derivative"); TEST_VECTOR_APPROX_EQUAL(dv_result.f, f_center, 1e-8, "dv constraint returns correct point"); TEST_VECTOR_APPROX_EQUAL(dv_result.grad_f, numerical_dv, 0.01, "dv constraint gradient matches numerical derivative"); TestHelper::safeFree(extrude); return true; } /** * @brief 测试 geometry_accessor 函数 */ bool test_polyline_geometry_accessor() { TEST_SECTION("Polyline Descriptor Accessor"); primitive_data_center_t dc; std::vector profile_points = { {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto* side_face = static_cast(ext_poly->subfaces[0].get_ptr()); auto geometry_accessor_func = internal::get_geometry_accessor_ptr(surface_type::extrude_polyline_side); const void* desc_ptr = geometry_accessor_func(make_pointer_wrapper(static_cast(side_face))); TEST_ASSERT(desc_ptr != nullptr, "Descriptor accessor returns non-null pointer"); TEST_ASSERT(desc_ptr == side_face->get_geometry_ptr(), "Descriptor accessor returns correct pointer"); TestHelper::safeFree(extrude); return true; } /** * @brief 测试 subface_equal 函数 */ /* bool test_polyline_subface_equal() { TEST_SECTION("Polyline Subface Equality"); primitive_data_center_t dc; std::vector profile_points = { {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 2} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; // 创建两个相同的拉伸体 auto* extrude1 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly1 = static_cast(extrude1); ext_poly1->initialize_with_components(&dc, profile, axis); auto* extrude2 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly2 = static_cast(extrude2); ext_poly2->initialize_with_components(&dc, profile, axis); auto* side_face1 = static_cast(ext_poly1->subfaces[0].get_ptr()); auto* side_face2 = static_cast(ext_poly2->subfaces[0].get_ptr()); auto subface_equal_func = internal::get_subface_equal_ptr(surface_type::extrude_polyline_side); // 两个相同的 subface 应该相等 bool are_equal = subface_equal_func(make_pointer_wrapper(static_cast(side_face1)), make_pointer_wrapper(static_cast(side_face2))); TEST_ASSERT(are_equal, "Identical subfaces are equal"); // 自身应该相等 bool self_equal = subface_equal_func(make_pointer_wrapper(static_cast(side_face1)), make_pointer_wrapper(static_cast(side_face2))); TEST_ASSERT(self_equal, "Subface equals itself"); TestHelper::safeFree(extrude1); TestHelper::safeFree(extrude2); return true; } */ // ============================================================================ // Phase 1.2: Geometry Data Internal Tests // ============================================================================ /** * @brief 测试 PMC (Point-in-Polygon) 算法 * @details 验证点是否在 polyline 内部的判断 */ bool test_polyline_pmc_inside_outside() { TEST_SECTION("Polyline PMC (Point-in-Polygon) - FIXED"); // 创建一个正方形 profile: [-1, 1] × [-1, 1],CW 绕向 std::vector profile_points = { {-1.0, -1.0, 0.0}, {-1.0, 1.0, 0.0}, {1.0, 1.0, 0.0}, {1.0, -1.0, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; polyline_descriptor_t desc; desc.point_number = 4; desc.points = profile_points.data(); desc.bulge_number = 4; desc.bulges = profile_bulges.data(); desc.is_close = true; desc.reference_normal = {0, 0, 1}; internal::polyline_geometry_data geom; Eigen::Vector3d proj_x(1, 0, 0); // X轴 → Profile 的 X 方向 Eigen::Vector3d proj_y(0, 1, 0); // Y轴 → Profile 的 Y 方向 Eigen::Vector3d origin(0, 0, 0); // 原点 internal::aabb_t_dim<2> aabb; geom.build_as_profile(desc, proj_x, proj_y, origin, aabb); // ========== 测试 1:内部点 ========== Eigen::Vector3d inside_point(0.0, 0.0, 0.0); auto closest_inside = geom.calculate_closest_param(inside_point); bool is_inside = geom.pmc(inside_point.head<2>(), closest_inside.first); TEST_ASSERT(!is_inside, "Point (0,0) is inside square"); // ========== 测试 2:外部点 ========== Eigen::Vector3d outside_point(2.0, 2.0, 0.0); auto closest_outside = geom.calculate_closest_param(outside_point); bool is_outside_pmc = geom.pmc(outside_point.head<2>(), closest_outside.first); TEST_ASSERT(is_outside_pmc, "Point (2,2) is outside square"); // ========== 测试 3:边界点 ========== Eigen::Vector3d boundary_point(1.0, 0.0, 0.0); // 右边界中点 auto closest_boundary = geom.calculate_closest_param(boundary_point); bool is_boundary_in = geom.pmc(boundary_point.head<2>(), closest_boundary.first); // 边界点可能返回 true 或 false,只要不崩溃就算通过 TEST_ASSERT(true, "Boundary point PMC completed without error"); // ========== 测试 4:更多外部点 ========== std::vector more_outside_points = { {-2.0, 0.0, 0.0}, // 左外侧 {0.0, -2.0, 0.0}, // 下外侧 {0.0, 2.0, 0.0}, // 上外侧 {2.0, 0.0, 0.0}, // 右外侧 }; for (const auto& pt : more_outside_points) { auto closest = geom.calculate_closest_param(pt); bool is_outside = geom.pmc(pt.head<2>(), closest.first); TEST_ASSERT(is_outside, "Point (" << pt.transpose() << ") should be outside square"); } return true; } /** * @brief 测试 Polyline 的 isEnd() 函数 */ bool test_polyline_isEnd() { TEST_SECTION("Polyline isEnd() Function"); std::vector points = { {0, 0, 0}, {1, 0, 0}, {1, 1, 0} }; std::vector bulges = {0.0, 0.0}; polyline_descriptor_t desc; desc.point_number = 3; desc.points = points.data(); desc.bulge_number = 2; desc.bulges = bulges.data(); desc.is_close = false; desc.reference_normal = {0, 0, 1}; internal::polyline_geometry_data geom; internal::aabb_t_dim<2> aabb_obj; geom.build_as_profile(desc, {1, 0, 0}, {0, 1, 0}, {0, 0, 0}, aabb_obj); // t = 0 应该是端点 TEST_ASSERT(geom.isEnd(0.0), "t=0 is an endpoint"); // t = segment_count 应该是端点 double max_t = static_cast(geom.start_indices.size()); TEST_ASSERT(geom.isEnd(max_t), "t=max is an endpoint"); // 中间值不应该是端点 TEST_ASSERT(!geom.isEnd(0.5), "t=0.5 is not an endpoint"); TEST_ASSERT(!geom.isEnd(1.0), "t=1.0 is not an endpoint"); return true; } // ============================================================================ // Phase 1.3: Edge Case Tests // ============================================================================ /** * @brief 测试退化的 polyline (只有2个点的直线) */ bool test_edge_degenerate_polyline() { TEST_SECTION("Edge Case - Degenerate Polyline (2 points)"); primitive_data_center_t dc; // 创建只有2个点的 profile (一条直线) std::vector profile_points = { {-0.5, 0.0, 0.0}, {0.5, 0.0, 0.0} }; std::vector profile_bulges = {0.0}; profile_descriptor_t profile; profile.point_number = 2; profile.points = profile_points.data(); profile.bulge_number = 1; profile.bulges = profile_bulges.data(); profile.is_close = false; // 不封闭 profile.reference_normal = {0, 0, 1}; std::vector axis_points = { {0, 0, 0}, {0, 0, 1} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); // 这应该成功创建(即使 profile 退化) extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto aabb = ext_poly->fetch_aabb(); TEST_ASSERT(!aabb.isEmpty(), "Degenerate profile still creates valid extrude"); TestHelper::safeFree(extrude); return true; } /** * @brief 测试极小尺寸的拉伸体 */ bool test_edge_very_small_dimensions() { TEST_SECTION("Edge Case - Very Small Dimensions"); primitive_data_center_t dc; // 创建非常小的 profile std::vector profile_points = { {-0.001, -0.001, 0.0}, {0.001, -0.001, 0.0}, {0.001, 0.001, 0.0}, {-0.001, 0.001, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; // 创建非常短的 axis std::vector axis_points = { {0, 0, 0 }, {0, 0, 0.001} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); auto aabb = ext_poly->fetch_aabb(); TEST_ASSERT(!aabb.isEmpty(), "Very small extrude is valid"); TEST_ASSERT(aabb.sizes().x() > 0, "AABB has positive X size"); TEST_ASSERT(aabb.sizes().y() > 0, "AABB has positive Y size"); TEST_ASSERT(aabb.sizes().z() > 0, "AABB has positive Z size"); TestHelper::safeFree(extrude); return true; } /** * @brief 测试零长度 axis 的错误处理 */ bool test_edge_zero_length_axis() { TEST_SECTION("Edge Case - Zero Length Axis"); primitive_data_center_t dc; std::vector profile_points = { {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0}, {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0} }; std::vector profile_bulges = {0.0, 0.0, 0.0, 0.0}; profile_descriptor_t profile; profile.point_number = 4; profile.points = profile_points.data(); profile.bulge_number = 4; profile.bulges = profile_bulges.data(); profile.is_close = true; profile.reference_normal = {0, 0, 1}; // 创建零长度 axis (两个相同的点) std::vector axis_points = { {0, 0, 0}, {0, 0, 0} }; std::vector axis_bulges = {0.0}; axis_descriptor_t axis; axis.type = AXIS_TYPE_POLYLINE; axis.data.polyline.point_number = 2; axis.data.polyline.points = axis_points.data(); axis.data.polyline.bulge_number = 1; axis.data.polyline.bulges = axis_bulges.data(); axis.data.polyline.is_close = false; axis.data.polyline.reference_normal = {1, 0, 0}; auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc); auto* ext_poly = static_cast(extrude); bool caught_error = false; try { extrude_polyline_descriptor_t desc; desc.profile_number = 1; desc.profiles = const_cast(reinterpret_cast(&profile)); desc.axis = axis.data.polyline; ext_poly->initialize_with_components(&dc, desc); } catch (const std::exception& e) { caught_error = true; std::cout << " Expected error caught: " << e.what() << "\n"; } // 零长度 axis 可能会抛出异常或产生退化结果 // 这里我们验证系统不会崩溃 if (!caught_error) { // 如果没有抛出异常,验证结果是否合理 auto aabb = ext_poly->fetch_aabb(); std::cout << " Zero-length axis produced AABB: " << "sizes = " << aabb.sizes().transpose() << "\n"; } TestHelper::safeFree(extrude); return true; } // ============================================================================ // Main 测试运行器 // ============================================================================ int main() { SetConsoleOutputCP(CP_UTF8); std::cout << "╔══════════════════════════════════════════════════════╗\n"; std::cout << "║ EXTRUDE TEST SUITE ║\n"; std::cout << "║ Based on Actual Code Structure ║\n"; std::cout << "╚══════════════════════════════════════════════════════╝\n"; std::cout << "mimalloc version: " << mi_version() << "\n\n"; struct TestCase { std::string name; std::function func; std::string group; }; std::vector tests = { // Group 1: Polyline {"Polyline Basic Shapes", test_polyline_basic_shapes, "Polyline" }, {"Polyline SDF Calculation", test_polyline_sdf_calculation, "Polyline" }, {"Polyline Closest Point", test_polyline_closest_point, "Polyline" }, // Group 2: Helixline {"Helixline Construction", test_helixline_construction, "Helixline" }, {"Helixline Tangent/Normal", test_helixline_tangent_normal, "Helixline" }, {"Helixline Closest Point", test_helixline_closest_point, "Helixline" }, {"Helixline Left vs Right", test_helixline_left_vs_right, "Helixline" }, // Group 3: Extrude Creation {"Extrude Polyline Creation", test_extrude_polyline_creation, "Extrude" }, {"Extrude Helixline Creation", test_extrude_helixline_creation, "Extrude" }, {"Extrude AABB Computation", test_extrude_aabb_computation, "Extrude" }, {"Extrude Descriptor Sharing", test_extrude_descriptor_sharing, "Extrude" }, {"Extrude Various Profiles", test_extrude_various_profiles, "Extrude" }, // Group 4: SDF & Parametrization {"Extrude SDF Basic", test_extrude_sdf_basic, "SDF" }, {"Extrude Parametrization", test_extrude_parametrization, "SDF" }, // Group 5: Transforms {"Transform Scale", test_extrude_transform_scale, "Transform" }, {"Transform Translation", test_extrude_transform_translation, "Transform" }, // Group 6: Replace {"Replace Profile", test_replace_profile, "Replace" }, {"Replace Axis", test_replace_axis, "Replace" }, // Group 7: Edge Cases {"Edge Cases Small Dimensions", test_edge_cases_small_dimensions, "EdgeCase" }, // Group 8: Performance {"Performance Batch Creation", test_performance_batch_creation, "Performance"}, // Phase 1.1: Subface Functions {"Polyline SDF Detailed", test_polyline_eval_sdf_detailed, "Subface" }, {"Polyline SDF Gradient", test_polyline_eval_sdf_grad, "Subface" }, {"Polyline Param Round-Trip", test_polyline_map_param_roundtrip, "Subface" }, {"Polyline Constraint Curves", test_polyline_constraint_curves, "Subface" }, {"Polyline Descriptor Accessor", test_polyline_geometry_accessor, "Subface" }, //{"Polyline Subface Equal", test_polyline_subface_equal, "Subface" }, // Phase 1.2: Geometry Data Internals {"Polyline PMC Inside/Outside", test_polyline_pmc_inside_outside, "Geometry" }, {"Polyline isEnd()", test_polyline_isEnd, "Geometry" }, // Phase 1.3: Edge Cases {"Edge: Degenerate Polyline", test_edge_degenerate_polyline, "EdgeCase" }, {"Edge: Very Small Dimensions", test_edge_very_small_dimensions, "EdgeCase" }, {"Edge: Zero Length Axis", test_edge_zero_length_axis, "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; }