/// @file fuzz_sdf.cpp /// @brief SDF 随机CSG树模糊测试 + 持续运行模式 /// /// 测试策略: /// 1. 随机生成深度 CSG 树(最大深度 6) /// 2. 随机选择操作符(union/intersection/difference/smooth variants) /// 3. 随机选择图元(sphere/box/cylinder/torus/capsule) /// 4. 随机应用域变形(translate/rotate/scale/twist/bend) /// 5. 随机采样点评估并验证结果 /// 6. 持续运行模式(命令行参数 -1 表示无限循环) #include #include "vde/sdf/sdf_primitives.h" #include "vde/sdf/sdf_operations.h" #include "vde/sdf/sdf_tree.h" #include #include #include #include #include using namespace vde::sdf; using namespace vde::core; // ═══════════════════════════════════════════════ // SDF 随机种子 // ═══════════════════════════════════════════════ static std::mt19937 s_rng(42); // ═══════════════════════════════════════════════ // 随机参数生成器 // ═══════════════════════════════════════════════ namespace { /// 随机双精度 [lo, hi] double rand_range(double lo, double hi) { std::uniform_real_distribution dist(lo, hi); return dist(s_rng); } /// 随机 Point3D,范围 [lo, hi] Point3D rand_point(double lo, double hi) { return Point3D(rand_range(lo, hi), rand_range(lo, hi), rand_range(lo, hi)); } /// 随机选择操作类型(CSG 布尔) SdfOp rand_csg_op() { static const SdfOp ops[] = { SdfOp::Union, SdfOp::Intersection, SdfOp::Difference, SdfOp::SmoothUnion, SdfOp::SmoothIntersection, SdfOp::SmoothDifference }; return ops[std::uniform_int_distribution(0, 5)(s_rng)]; } /// 随机选择图元类型 SdfOp rand_primitive_op() { static const SdfOp ops[] = { SdfOp::Sphere, SdfOp::Box, SdfOp::Cylinder, SdfOp::Torus, SdfOp::Capsule }; return ops[std::uniform_int_distribution(0, 4)(s_rng)]; } /// 随机选择域变形类型 SdfOp rand_transform_op() { static const SdfOp ops[] = { SdfOp::Translate, SdfOp::Rotate, SdfOp::Scale, SdfOp::Twist, SdfOp::Bend }; return ops[std::uniform_int_distribution(0, 4)(s_rng)]; } } // anonymous // ═══════════════════════════════════════════════ // 随机 CSG 树构造 // ═══════════════════════════════════════════════ /// 创建随机图元节点 SdfNodePtr rand_primitive() { SdfOp op = rand_primitive_op(); auto node = std::make_shared(op); switch (op) { case SdfOp::Sphere: node->params.radius = rand_range(0.1, 5.0); break; case SdfOp::Box: node->params.extents = rand_point(0.1, 3.0); break; case SdfOp::Cylinder: node->params.radius = rand_range(0.1, 3.0); node->params.height = rand_range(0.2, 6.0); break; case SdfOp::Torus: node->params.major_radius = rand_range(0.5, 4.0); node->params.minor_radius = rand_range(0.1, 1.5); break; case SdfOp::Capsule: node->params.pt_a = Point3D(0, -rand_range(0.5, 3.0), 0); node->params.pt_b = Point3D(0, rand_range(0.5, 3.0), 0); node->params.radius = rand_range(0.1, 1.0); break; default: break; } return node; } /// 创建随机 CSG 布尔节点(递归) SdfNodePtr rand_csg_tree(int depth, int max_depth) { if (depth >= max_depth) { return rand_primitive(); } SdfOp op = rand_csg_op(); auto left = rand_csg_tree(depth + 1, max_depth); auto right = rand_csg_tree(depth + 1, max_depth); auto node = std::make_shared(op); node->children.push_back(left); node->children.push_back(right); if (op == SdfOp::SmoothUnion || op == SdfOp::SmoothIntersection || op == SdfOp::SmoothDifference) { node->params.blend_k = rand_range(0.05, 0.5); } return node; } /// 随机应用域变形包装 SdfNodePtr wrap_transform(SdfNodePtr child) { if (rand_range(0.0, 1.0) < 0.3) { SdfOp op = rand_transform_op(); auto node = std::make_shared(op); node->children.push_back(child); switch (op) { case SdfOp::Translate: node->params.translate_offset = rand_point(-2.0, 2.0); break; case SdfOp::Rotate: node->params.amount = rand_range(-M_PI, M_PI); break; case SdfOp::Scale: node->params.scale_factors = rand_point(0.5, 2.0); break; case SdfOp::Twist: node->params.amount = rand_range(-0.5, 0.5); break; case SdfOp::Bend: node->params.amount = rand_range(-0.3, 0.3); break; default: break; } return node; } return child; } // ═══════════════════════════════════════════════ // 模糊测试用例 // ═══════════════════════════════════════════════ TEST(FuzzSdf, RandomCSGTree_Evaluate) { // 生成多个随机树并在多个点处求值 for (int i = 0; i < 50; ++i) { auto tree = rand_csg_tree(0, 4); // 可选变形包装 tree = wrap_transform(tree); for (int j = 0; j < 30; ++j) { Point3D p = rand_point(-5.0, 5.0); EXPECT_NO_THROW({ double v = evaluate(tree, p); // 求值结果必须是有限数(不应是 NaN 或 Inf) EXPECT_TRUE(std::isfinite(v)); }); } } } TEST(FuzzSdf, RandomCSGTree_DeepTree) { // 深度 CSG 树(depth 6 → 最多 2^6 = 64 个叶子节点) for (int i = 0; i < 20; ++i) { auto tree = rand_csg_tree(0, 6); EXPECT_NO_THROW({ auto bbox = estimate_bbox(tree, 0.5); EXPECT_TRUE(std::isfinite(bbox.min.x())); EXPECT_TRUE(std::isfinite(bbox.max.x())); Point3D p = rand_point(-10.0, 10.0); double v = evaluate(tree, p); EXPECT_TRUE(std::isfinite(v)); }); } } TEST(FuzzSdf, RandomCSGTree_Bounds) { // 验证 estimate_bounds 对所有随机树返回合理的值 for (int i = 0; i < 30; ++i) { auto tree = rand_csg_tree(0, 3); tree = wrap_transform(tree); EXPECT_NO_THROW({ auto half = estimate_bounds(tree, 1.0); EXPECT_GE(half.x(), 0.0); EXPECT_GE(half.y(), 0.0); EXPECT_GE(half.z(), 0.0); EXPECT_TRUE(std::isfinite(half.x())); EXPECT_TRUE(std::isfinite(half.y())); EXPECT_TRUE(std::isfinite(half.z())); }); } } TEST(FuzzSdf, RandomTree_Visit) { // 验证 visit() 对所有随机树都不崩溃 for (int i = 0; i < 30; ++i) { auto tree = rand_csg_tree(0, 4); tree = wrap_transform(tree); EXPECT_NO_THROW({ int count = 0; tree->visit([&](const SdfNode& /*n*/) { ++count; }); EXPECT_GT(count, 0); }); } } // ═══════════════════════════════════════════════ // 持续运行模式(-1 = 无限循环) // 用法: ./fuzz_sdf --fuzz_forever // ═══════════════════════════════════════════════ TEST(FuzzSdf, ContinuousFuzz) { // 检查环境变量 VDE_FUZZ_FOREVER=1 启用持续模式 const char* forever = std::getenv("VDE_FUZZ_FOREVER"); if (!forever || std::string(forever) != "1") { GTEST_SKIP() << "设置 VDE_FUZZ_FOREVER=1 启用持续模糊测试"; } // 持续运行直到手动停止 int iteration = 0; while (true) { ++iteration; auto tree = rand_csg_tree(0, std::uniform_int_distribution(2, 5)(s_rng)); tree = wrap_transform(tree); Point3D p = rand_point(-10.0, 10.0); double v = evaluate(tree, p); ASSERT_TRUE(std::isfinite(v)) << "iteration=" << iteration << " 非有限 SDF 值: " << v; // 每 10000 次打印一次进度 if (iteration % 10000 == 0) { auto bbox = estimate_bbox(tree, 1.0); std::cout << "[FuzzSdf] iteration=" << iteration << " value=" << v << " bbox=[" << bbox.min.x() << "," << bbox.max.x() << "]" << std::endl; } } }