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

1994 lines
75 KiB

#define _USE_MATH_DEFINES
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cassert>
#include <vector>
#include <functional>
#include <chrono>
#include <mimalloc.h>
#include <data/data_center.hpp>
#include <base/primitive.hpp>
#include <base/subface.hpp>
#include <primitive_descriptor.h>
#include <subface/geometry/geometry_data.hpp>
#include <primitive/simple/extrude.hpp>
// ============================================================================
// 测试框架宏
// ============================================================================
#define TEST_SECTION(name) \
std::cout << "\n========================================\n" \
<< "TEST: " << name << "\n" \
<< "========================================\n"
#define TEST_ASSERT(condition, message) \
do { \
if (!(condition)) { \
std::cerr << "❌ FAILED: " << message << "\n"; \
std::cerr << " at " << __FILE__ << ":" << __LINE__ << "\n"; \
return false; \
} else { \
std::cout << "✓ PASSED: " << message << "\n"; \
} \
} while (0)
#define TEST_APPROX_EQUAL(a, b, eps, message) \
TEST_ASSERT(std::abs((a) - (b)) < (eps), \
message << " (expected: " << (b) << ", got: " << (a) << ", diff: " << std::abs((a) - (b)) << ")")
#define TEST_VECTOR_APPROX_EQUAL(v1, v2, eps, message) \
TEST_ASSERT((v1 - v2).norm() < (eps), message << " (distance: " << (v1 - v2).norm() << ")")
// ============================================================================
// 测试数据工厂
// ============================================================================
class TestDataFactory
{
public:
// ========== Profile 工厂 ==========
struct ProfileData {
std::vector<vector3d> points;
std::vector<double> bulges;
profile_descriptor_t descriptor;
void finalize()
{
descriptor.point_number = static_cast<uint32_t>(points.size());
descriptor.points = points.data();
descriptor.bulge_number = static_cast<uint32_t>(bulges.size());
descriptor.bulges = bulges.data();
descriptor.is_close = true;
descriptor.reference_normal = {0, 0, 1};
}
};
// 正方形 (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<vector3d> points;
std::vector<double> 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<uint32_t>(points.size());
descriptor.data.polyline.points = points.data();
descriptor.data.polyline.bulge_number = static_cast<uint32_t>(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<Eigen::Vector3d>& 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<const polyline_descriptor_t*>(&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<const polyline_descriptor_t*>(&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<const polyline_descriptor_t*>(&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<const polyline_descriptor_t*>(&profile.descriptor), proj_x, proj_y, origin, aabb);
// 测试点集合
struct TestPoint {
Eigen::Vector3d point;
std::string region;
};
std::vector<TestPoint> 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<int>(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<const polyline_descriptor_t*>(&profile.descriptor), proj_x, proj_y, origin, aabb);
// 测试不同位置的最近点投影
std::vector<Eigen::Vector3d> 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<double, 3, Eigen::AffineCompact, Eigen::DontAlign> 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<double, 3, Eigen::AffineCompact, Eigen::DontAlign> transform;
aabb_t aabb;
geom.build_from_descriptor(axis_desc.data.helixline, transform, aabb);
// 测试多个参数位置
std::vector<double> 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<double, 3, Eigen::AffineCompact, Eigen::DontAlign> 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<double, 3, Eigen::AffineCompact, Eigen::DontAlign> 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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
helix_desc.axis = axis.data.helixline;
auto* extrude = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_HELIXLINE, &dc);
auto* ext_helix = static_cast<internal::extrude_helixline_t*>(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<AABBTest> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<internal::extrude_polyline_t*>(extrude1);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.descriptor.data.polyline;
ext1->initialize_with_components(&dc, desc);
auto* extrude2 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc);
auto* ext2 = static_cast<internal::extrude_polyline_t*>(extrude2);
ext2->initialize_with_components(&dc, desc);
auto subfaces1 = ext1->get_subfaces();
auto subfaces2 = ext2->get_subfaces();
auto* side1 = static_cast<internal::extrude_polyline_side_face_t*>(subfaces1[0].get_ptr());
auto* side2 = static_cast<internal::extrude_polyline_side_face_t*>(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::extrude_polyline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc));
extrude_polyline_descriptor_t square_desc;
square_desc.profile_number = 1;
square_desc.profiles =
const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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::extrude_polyline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc));
extrude_polyline_descriptor_t circle_desc;
circle_desc.profile_number = 1;
circle_desc.profiles =
const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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::extrude_polyline_t*>(internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc));
extrude_polyline_descriptor_t triangle_desc;
triangle_desc.profile_number = 1;
triangle_desc.profiles =
const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<SDFTest> 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<subface*>(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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<Eigen::Vector2d> 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<subface*>(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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile1.descriptor));
desc.axis = axis.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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis1.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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<primitive*> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile.descriptor));
desc.axis = axis.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<std::chrono::milliseconds>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile));
desc.axis = axis.data.polyline;
ext_poly->initialize_with_components(&dc, desc);
auto* side_face = static_cast<internal::extrude_polyline_side_face_t*>(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<subface*>(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<subface*>(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<subface*>(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<subface*>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile));
desc.axis = axis.data.polyline;
ext_poly->initialize_with_components(&dc, desc);
auto* side_face = static_cast<internal::extrude_polyline_side_face_t*>(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<subface*>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile));
desc.axis = axis.data.polyline;
ext_poly->initialize_with_components(&dc, desc);
auto* side_face = static_cast<internal::extrude_polyline_side_face_t*>(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<Eigen::Vector2d> 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<subface*>(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<subface*>(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<subface*>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile));
desc.axis = axis.data.polyline;
ext_poly->initialize_with_components(&dc, desc);
auto* side_face = static_cast<internal::extrude_polyline_side_face_t*>(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<subface*>(side_face)), uv);
auto dv_result = eval_dv_constraint_func(make_pointer_wrapper(static_cast<subface*>(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<subface*>(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<subface*>(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<subface*>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&profile));
desc.axis = axis.data.polyline;
ext_poly->initialize_with_components(&dc, desc);
auto* side_face = static_cast<internal::extrude_polyline_side_face_t*>(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<subface*>(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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 2}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude1);
ext_poly1->initialize_with_components(&dc, profile, axis);
auto* extrude2 = internal::new_primitive(PRIMITIVE_TYPE_EXTRUDE_POLYLINE, &dc);
auto* ext_poly2 = static_cast<internal::extrude_polyline_t*>(extrude2);
ext_poly2->initialize_with_components(&dc, profile, axis);
auto* side_face1 = static_cast<internal::extrude_polyline_side_face_t*>(ext_poly1->subfaces[0].get_ptr());
auto* side_face2 = static_cast<internal::extrude_polyline_side_face_t*>(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<subface*>(side_face1)),
make_pointer_wrapper(static_cast<subface*>(side_face2)));
TEST_ASSERT(are_equal, "Identical subfaces are equal");
// 自身应该相等
bool self_equal = subface_equal_func(make_pointer_wrapper(static_cast<subface*>(side_face1)),
make_pointer_wrapper(static_cast<subface*>(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<vector3d> 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<double> 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<Eigen::Vector3d> 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<vector3d> points = {
{0, 0, 0},
{1, 0, 0},
{1, 1, 0}
};
std::vector<double> 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<double>(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<vector3d> profile_points = {
{-0.5, 0.0, 0.0},
{0.5, 0.0, 0.0}
};
std::vector<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 1}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
// 这应该成功创建(即使 profile 退化)
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0 },
{0, 0, 0.001}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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<vector3d> 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<double> 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<vector3d> axis_points = {
{0, 0, 0},
{0, 0, 0}
};
std::vector<double> 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<internal::extrude_polyline_t*>(extrude);
bool caught_error = false;
try {
extrude_polyline_descriptor_t desc;
desc.profile_number = 1;
desc.profiles = const_cast<polyline_descriptor_t*>(reinterpret_cast<const polyline_descriptor_t*>(&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<bool()> func;
std::string group;
};
std::vector<TestCase> 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<int>(tests.size());
std::string current_group;
for (const auto& test : tests) {
if (test.group != current_group) {
current_group = test.group;
std::cout << "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
std::cout << " GROUP: " << current_group << "\n";
std::cout << "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
}
try {
if (test.func()) { passed++; }
} catch (const std::exception& e) {
std::cerr << "❌ EXCEPTION in " << test.name << ": " << e.what() << "\n";
} catch (...) {
std::cerr << "❌ UNKNOWN EXCEPTION in " << test.name << "\n";
}
}
TestHelper::printStatistics(passed, total);
return (passed == total) ? 0 : 1;
}