7b76689ea1
v11.1 — Test Infrastructure (14 files): - .github/workflows/ci.yml: Ubuntu 22.04 + GCC 11, auto ctest on push - tests/regression/: degenerate geometry, extreme coords, thin wall, large models - tests/fuzz/: SDF random CSG, random booleans, format round-trip, continuous mode - tests/benchmark/: boolean perf, MC perf, IO perf benchmarks - docs/benchmark-report.md: benchmark template v11.2 — Code Quality + Docs: - .clang-tidy: 15 check categories, 22 exclusions - .github/workflows/static-analysis.yml: clang-tidy CI scan - CODEOWNERS, SECURITY.md - docs: API overview, testing guide, contributing guide - README: module capability overview table Pending: binary formats + curve projection (retrying)
272 lines
9.0 KiB
C++
272 lines
9.0 KiB
C++
/// @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 <gtest/gtest.h>
|
||
#include "vde/sdf/sdf_primitives.h"
|
||
#include "vde/sdf/sdf_operations.h"
|
||
#include "vde/sdf/sdf_tree.h"
|
||
#include <cmath>
|
||
#include <random>
|
||
#include <vector>
|
||
#include <string>
|
||
#include <cstdlib>
|
||
|
||
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<double> 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<int>(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<int>(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<int>(0, 4)(s_rng)];
|
||
}
|
||
|
||
} // anonymous
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 随机 CSG 树构造
|
||
// ═══════════════════════════════════════════════
|
||
|
||
/// 创建随机图元节点
|
||
SdfNodePtr rand_primitive() {
|
||
SdfOp op = rand_primitive_op();
|
||
auto node = std::make_shared<SdfNode>(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<SdfNode>(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<SdfNode>(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<int>(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;
|
||
}
|
||
}
|
||
}
|