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
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;
|
|
}
|