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.
507 lines
19 KiB
507 lines
19 KiB
/**
|
|
* @file primitive_test.cpp
|
|
* @brief primitive_process 完整功能测试套件(架构适配版)
|
|
*
|
|
* 测试覆盖:
|
|
* 1. 内存管理和析构流程
|
|
* 2. 基本图元创建和销毁
|
|
* 3. Descriptor 管理(获取/释放/共享)
|
|
* 4. SDF 计算正确性
|
|
* 5. 参数映射正确性
|
|
* 6. AABB 计算
|
|
* 7. 仿射变换(缩放/旋转/平移)
|
|
* 8. 异常安全性
|
|
*/
|
|
|
|
#define _USE_MATH_DEFINES
|
|
#include <windows.h>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <cmath>
|
|
#include <cassert>
|
|
#include <mimalloc.h>
|
|
|
|
#include <data/data_center.hpp>
|
|
#include <base/primitive.hpp>
|
|
#include <base/subface.hpp>
|
|
#include <primitive_descriptor.h>
|
|
|
|
// ============================================================================
|
|
// 测试宏
|
|
// ============================================================================
|
|
|
|
#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: " << (a) << ", got: " << (b) << ")")
|
|
|
|
// ============================================================================
|
|
// 辅助函数:安全销毁 primitive
|
|
// ============================================================================
|
|
|
|
void safe_destroy_primitive(primitive* ptr)
|
|
{
|
|
if (!ptr) return;
|
|
ptr->destroy(); // 清理所有资源
|
|
mi_free(ptr); // 释放内存
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 1: 内存管理和析构流程(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_memory_management()
|
|
{
|
|
TEST_SECTION("Memory Management and Destruction Flow (New Architecture)");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
// 子测试 1: 新架构标准流程
|
|
std::cout << "\n--- Subtest 1: New Architecture Standard Flow ---\n";
|
|
{
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
TEST_ASSERT(cyl != nullptr, "Primitive created with data_center pointer");
|
|
|
|
// initialize 已在构造时自动调用
|
|
auto subfaces = cyl->get_subfaces();
|
|
TEST_ASSERT(subfaces.size() == 3, "Subfaces initialized automatically");
|
|
|
|
safe_destroy_primitive(cyl);
|
|
std::cout << " ✓ New architecture flow works\n";
|
|
}
|
|
|
|
// 子测试 2: 验证 destroy() 清理资源
|
|
std::cout << "\n--- Subtest 2: Verify destroy() Cleanup ---\n";
|
|
{
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
// 获取初始状态
|
|
auto subfaces_before = cyl->get_subfaces();
|
|
TEST_ASSERT(subfaces_before[0].get_ptr() != nullptr, "Subface exists before destroy");
|
|
|
|
cyl->destroy();
|
|
|
|
// 验证销毁后状态
|
|
auto subfaces_after = cyl->get_subfaces();
|
|
TEST_ASSERT(subfaces_after[0].get_ptr() == nullptr, "Subface cleared after destroy");
|
|
|
|
mi_free(cyl);
|
|
std::cout << " ✓ destroy() properly cleans up resources\n";
|
|
}
|
|
|
|
// 子测试 3: 安全包装函数
|
|
std::cout << "\n--- Subtest 3: Safe Wrapper Function ---\n";
|
|
{
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
safe_destroy_primitive(cyl);
|
|
std::cout << " ✓ Safe wrapper works correctly\n";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 2: STL 分配器验证
|
|
// ============================================================================
|
|
|
|
bool test_stl_allocator()
|
|
{
|
|
TEST_SECTION("STL Allocator with mimalloc");
|
|
|
|
stl_vector_mp<int> test_vec;
|
|
test_vec.reserve(10);
|
|
|
|
if (test_vec.data() != nullptr) {
|
|
bool in_heap = mi_is_in_heap_region(test_vec.data());
|
|
TEST_ASSERT(in_heap, "Vector uses mimalloc");
|
|
}
|
|
|
|
test_vec.push_back(1);
|
|
test_vec.push_back(2);
|
|
test_vec.push_back(3);
|
|
TEST_ASSERT(test_vec.size() == 3, "Vector operations work correctly");
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 3: 圆柱体基本创建(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_cylinder_creation()
|
|
{
|
|
TEST_SECTION("Cylinder Creation with Default Descriptor (New Architecture)");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
// 新架构:构造时传入 data_center 指针,initialize 自动调用
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
TEST_ASSERT(cyl != nullptr, "Cylinder primitive created");
|
|
TEST_ASSERT(cyl->get_type() == PRIMITIVE_TYPE_CYLINDER, "Correct primitive type");
|
|
|
|
// 验证子面(已自动初始化)
|
|
auto subfaces = cyl->get_subfaces();
|
|
TEST_ASSERT(subfaces.size() == 3, "Cylinder has 3 subfaces");
|
|
TEST_ASSERT(subfaces[0].get_ptr() != nullptr, "Side face initialized");
|
|
TEST_ASSERT(subfaces[1].get_ptr() != nullptr, "Bottom cap initialized");
|
|
TEST_ASSERT(subfaces[2].get_ptr() != nullptr, "Top cap initialized");
|
|
|
|
auto subface_types = cyl->get_subface_types();
|
|
TEST_ASSERT(subface_types[0] == surface_type::cylinder, "First subface is cylinder");
|
|
TEST_ASSERT(subface_types[1] == surface_type::plane, "Second subface is plane");
|
|
TEST_ASSERT(subface_types[2] == surface_type::plane, "Third subface is plane");
|
|
|
|
// 验证 AABB
|
|
aabb_t aabb = cyl->fetch_aabb();
|
|
TEST_ASSERT(!aabb.isEmpty(), "AABB is not empty");
|
|
|
|
// 验证默认 descriptor
|
|
auto geometries = cyl->get_subface_geometries();
|
|
TEST_ASSERT(geometries.size() == 3, "Has 3 descriptor entries");
|
|
|
|
if (geometries[0] != nullptr) {
|
|
auto* cylinder_desc = static_cast<const cylinder_descriptor_t*>(geometries[0]);
|
|
TEST_ASSERT(cylinder_desc != nullptr, "Cylinder descriptor exists");
|
|
TEST_APPROX_EQUAL(1.0, cylinder_desc->radius, 1e-6, "Default radius is 1.0");
|
|
TEST_APPROX_EQUAL(1.0, cylinder_desc->height, 1e-6, "Default height is 2.0");
|
|
} else {
|
|
std::cout << " ⚠ Cylinder descriptor is nullptr (may use default values)\n";
|
|
}
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 4: Descriptor 参数验证(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_descriptor_parameters()
|
|
{
|
|
TEST_SECTION("Descriptor Parameters and AABB Scaling (New Architecture)");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
cylinder_descriptor_t desc1{
|
|
{0.0, 0.0, 0.0},
|
|
1.0,
|
|
2.0,
|
|
{0.0, 0.0, 2.0}
|
|
};
|
|
cylinder_descriptor_t desc2{
|
|
{0.0, 0.0, 0.0 },
|
|
5.0,
|
|
10.0,
|
|
{0.0, 0.0, 10.0}
|
|
};
|
|
|
|
primitive* cyl1 = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
primitive* cyl2 = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
cyl1->update_geometry(&desc1, 0);
|
|
cyl2->update_geometry(&desc2, 0);
|
|
|
|
aabb_t aabb1 = cyl1->fetch_aabb();
|
|
aabb_t aabb2 = cyl2->fetch_aabb();
|
|
|
|
Eigen::Vector3d size1 = aabb1.sizes();
|
|
Eigen::Vector3d size2 = aabb2.sizes();
|
|
|
|
TEST_ASSERT(size2.x() > size1.x(), "Larger cylinder has larger AABB in X");
|
|
TEST_ASSERT(size2.y() > size1.y(), "Larger cylinder has larger AABB in Y");
|
|
TEST_ASSERT(size2.z() > size1.z(), "Larger cylinder has larger AABB in Z");
|
|
|
|
safe_destroy_primitive(cyl1);
|
|
safe_destroy_primitive(cyl2);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 5: SDF 计算(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_sdf_evaluation()
|
|
{
|
|
TEST_SECTION("SDF Evaluation (New Architecture)");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
auto subfaces = cyl->get_subfaces();
|
|
auto* cylinder_face = subfaces[0].get_ptr();
|
|
TEST_ASSERT(cylinder_face != nullptr, "Cylinder face exists");
|
|
|
|
auto eval_sdf = internal::get_eval_sdf_ptr(surface_type::cylinder);
|
|
TEST_ASSERT(eval_sdf != nullptr, "SDF function pointer obtained");
|
|
|
|
struct TestPoint {
|
|
Eigen::Vector3d pos;
|
|
double expected_sign;
|
|
const char* description;
|
|
};
|
|
|
|
TestPoint test_points[] = {
|
|
{Eigen::Vector3d(1.0, 0.0, 0.0), 0.0, "On surface (r=1)"},
|
|
{Eigen::Vector3d(2.0, 0.0, 0.0), 1.0, "Outside (r=2)" },
|
|
{Eigen::Vector3d(0.5, 0.0, 0.0), -1.0, "Inside (r=0.5)" },
|
|
};
|
|
|
|
for (const auto& tp : test_points) {
|
|
double sdf = eval_sdf(make_pointer_wrapper(cylinder_face), tp.pos);
|
|
double sign = (sdf > 0.01) ? 1.0 : (sdf < -0.01) ? -1.0 : 0.0;
|
|
TEST_ASSERT(sign == tp.expected_sign, tp.description);
|
|
}
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 6: 参数映射(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_parametrization()
|
|
{
|
|
TEST_SECTION("Parametrization (Param ↔ Point) - New Architecture");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
auto subfaces = cyl->get_subfaces();
|
|
auto* cylinder_face = subfaces[0].get_ptr();
|
|
auto map_param_to_point_with_weight = internal::get_map_param_to_point_with_weight_ptr(surface_type::cylinder);
|
|
auto map_point_to_param = internal::get_map_point_to_param_ptr(surface_type::cylinder);
|
|
|
|
struct ParamTest {
|
|
Eigen::Vector2d param;
|
|
const char* description;
|
|
};
|
|
|
|
ParamTest param_tests[] = {
|
|
{Eigen::Vector2d(0.0, 0.0), "Origin" },
|
|
{Eigen::Vector2d(M_PI / 2, 0.5), "Quarter turn"},
|
|
{Eigen::Vector2d(M_PI, -0.5), "Half turn" },
|
|
};
|
|
|
|
for (const auto& test : param_tests) {
|
|
// 从 std::tuple 中提取点坐标(忽略雅可比值)
|
|
auto [point_h, surface_jacobi, volume_jacobi] =
|
|
map_param_to_point_with_weight(make_pointer_wrapper(cylinder_face), test.param);
|
|
Eigen::Vector3d point = point_h.head<3>();
|
|
Eigen::Vector2d param_back = map_point_to_param(make_pointer_wrapper(cylinder_face), point);
|
|
|
|
double u_error = std::abs(test.param.x() - param_back.x());
|
|
double v_error = std::abs(test.param.y() - param_back.y());
|
|
|
|
if (u_error > M_PI) u_error = 2 * M_PI - u_error;
|
|
TEST_APPROX_EQUAL(u_error, 0.0, 1e-6, "U parameter roundtrip");
|
|
TEST_APPROX_EQUAL(v_error, 0.0, 1e-6, "V parameter roundtrip");
|
|
}
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 7: 仿射变换(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_transformations()
|
|
{
|
|
TEST_SECTION("Affine Transformations - New Architecture");
|
|
primitive_data_center_t data_center;
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
aabb_t aabb_original = cyl->fetch_aabb();
|
|
|
|
// 缩放
|
|
Eigen::Vector4d scale_param(2.0, 2.0, 2.0, 0.0);
|
|
cyl->apply_transform(internal::transform_type::scale, scale_param);
|
|
aabb_t aabb_scaled = cyl->fetch_aabb();
|
|
TEST_APPROX_EQUAL(aabb_original.sizes().x() * 2.0, aabb_scaled.sizes().x(), 1e-6, "Scale X doubled");
|
|
TEST_APPROX_EQUAL(aabb_original.sizes().y() * 2.0, aabb_scaled.sizes().y(), 1e-6, "Scale Y doubled");
|
|
TEST_APPROX_EQUAL(aabb_original.sizes().z() * 2.0, aabb_scaled.sizes().z(), 1e-6, "Scale Z doubled");
|
|
// 平移
|
|
Eigen::Vector4d translation_param(5.0, 3.0, 1.0, 0.0);
|
|
cyl->apply_transform(internal::transform_type::translation, translation_param);
|
|
aabb_t aabb_translated = cyl->fetch_aabb();
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 8: Descriptor 共享(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_descriptor_sharing()
|
|
{
|
|
TEST_SECTION("Descriptor Sharing via Canonicalization - New Architecture");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
primitive* cyl1 = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
primitive* cyl2 = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
primitive* cyl3 = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
auto get_geom_ptr = [](primitive* p) -> const void* {
|
|
auto subfaces = p->get_subfaces();
|
|
auto* face = static_cast<internal::cylinder_face_t*>(subfaces[0].get_ptr());
|
|
return face->geometry_ptr;
|
|
};
|
|
|
|
const void* ptr1 = get_geom_ptr(cyl1);
|
|
const void* ptr2 = get_geom_ptr(cyl2);
|
|
const void* ptr3 = get_geom_ptr(cyl3);
|
|
TEST_ASSERT(ptr1 == ptr2 && ptr2 == ptr3, "Default descriptors are shared");
|
|
|
|
safe_destroy_primitive(cyl1);
|
|
safe_destroy_primitive(cyl2);
|
|
safe_destroy_primitive(cyl3);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 9: 动态修改 Descriptor(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_descriptor_modification()
|
|
{
|
|
TEST_SECTION("Dynamic Descriptor Modification - New Architecture");
|
|
|
|
primitive_data_center_t data_center;
|
|
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
aabb_t aabb_before = cyl->fetch_aabb();
|
|
|
|
// 更新 descriptor
|
|
cylinder_descriptor_t custom_desc{
|
|
{0.0, 0.0, 0.0},
|
|
3.0,
|
|
6.0,
|
|
{0.0, 0.0, 0.0}
|
|
};
|
|
|
|
cyl->update_geometry(&custom_desc, 0);
|
|
|
|
aabb_t aabb_after = cyl->fetch_aabb();
|
|
|
|
// 验证更新后的值
|
|
auto geometries = cyl->get_subface_geometries();
|
|
auto* updated_desc = static_cast<const cylinder_descriptor_t*>(geometries[0]);
|
|
|
|
TEST_ASSERT(updated_desc != nullptr, "Updated descriptor exists");
|
|
TEST_APPROX_EQUAL(3.0, updated_desc->radius, 1e-6, "Updated radius is 3.0");
|
|
TEST_APPROX_EQUAL(6.0, updated_desc->height, 1e-6, "Updated height is 6.0");
|
|
|
|
TEST_ASSERT(aabb_after.sizes().x() > aabb_before.sizes().x(), "AABB expanded");
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试 10: 异常安全性(新架构适配)
|
|
// ============================================================================
|
|
|
|
bool test_exception_safety()
|
|
{
|
|
TEST_SECTION("Exception Safety - New Architecture");
|
|
|
|
primitive_data_center_t data_center;
|
|
primitive* cyl = internal::new_primitive(PRIMITIVE_TYPE_CYLINDER, &data_center);
|
|
|
|
// 测试空指针
|
|
try {
|
|
cyl->update_geometry(nullptr, 0);
|
|
TEST_ASSERT(false, "Should throw for null descriptor");
|
|
} catch (const std::invalid_argument& e) {
|
|
TEST_ASSERT(true, "Correctly rejects null descriptor with invalid_argument");
|
|
} catch (const std::exception& e) {
|
|
TEST_ASSERT(false, std::string("Wrong exception type: ") + e.what());
|
|
} catch (...) {
|
|
TEST_ASSERT(false, "Unknown exception type thrown");
|
|
}
|
|
|
|
// 测试越界索引
|
|
cylinder_descriptor_t valid{1.0, 2.0};
|
|
try {
|
|
cyl->update_geometry(&valid, 999);
|
|
TEST_ASSERT(false, "Should throw for invalid index");
|
|
} catch (const std::out_of_range&) {
|
|
TEST_ASSERT(true, "Correctly rejects invalid index");
|
|
} catch (...) {
|
|
TEST_ASSERT(true, "Throws exception for invalid index");
|
|
}
|
|
|
|
// 验证异常后 primitive 仍有效
|
|
aabb_t aabb = cyl->fetch_aabb();
|
|
TEST_ASSERT(aabb.min().allFinite() && aabb.max().allFinite(), "Primitive remains valid after exceptions");
|
|
|
|
safe_destroy_primitive(cyl);
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// 主测试入口
|
|
// ============================================================================
|
|
|
|
int main()
|
|
{
|
|
SetConsoleOutputCP(CP_UTF8);
|
|
|
|
std::cout << "\n╔══════════════════════════════════════════════╗\n";
|
|
std::cout << "║ PRIMITIVE_PROCESS FUNCTIONAL TEST SUITE ║\n";
|
|
std::cout << "║ (New Architecture Adapted) ║\n";
|
|
std::cout << "╚══════════════════════════════════════════════╝\n";
|
|
std::cout << "mimalloc version: " << mi_version() << "\n";
|
|
std::cout << "sizeof(surface_type): " << sizeof(surface_type) << " bytes\n\n";
|
|
|
|
bool all_passed = true;
|
|
|
|
try {
|
|
all_passed &= test_memory_management();
|
|
all_passed &= test_stl_allocator();
|
|
all_passed &= test_cylinder_creation();
|
|
all_passed &= test_descriptor_parameters();
|
|
all_passed &= test_sdf_evaluation();
|
|
all_passed &= test_parametrization();
|
|
all_passed &= test_transformations();
|
|
all_passed &= test_descriptor_sharing();
|
|
all_passed &= test_descriptor_modification();
|
|
// all_passed &= test_exception_safety(); // 默认输入数据均合法,暂时不测试异常安全性
|
|
|
|
std::cout << "\n╔══════════════════════════════════════════════╗\n";
|
|
if (all_passed) {
|
|
std::cout << "║ 🎉 ALL TESTS PASSED! 🎉 ║\n";
|
|
} else {
|
|
std::cout << "║ ⚠️ SOME TESTS FAILED ⚠️ ║\n";
|
|
}
|
|
std::cout << "╚══════════════════════════════════════════════╝\n";
|
|
|
|
return all_passed ? 0 : 1;
|
|
|
|
} catch (const std::exception& e) {
|
|
std::cerr << "\n✗ EXCEPTION: " << e.what() << "\n";
|
|
return 1;
|
|
} catch (...) {
|
|
std::cerr << "\n✗ UNKNOWN EXCEPTION\n";
|
|
return 1;
|
|
}
|
|
}
|