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)
215 lines
7.4 KiB
C++
215 lines
7.4 KiB
C++
/// @file regression_degenerate.cpp
|
||
/// @brief 退化几何回归测试集
|
||
///
|
||
/// 覆盖场景:
|
||
/// 1. 零长度边(zero-length edge)
|
||
/// 2. 零面积面(zero-area face)
|
||
/// 3. 重合顶点(coincident vertices)
|
||
/// 4. 自交体(self-intersecting body)
|
||
///
|
||
/// 每个测试:构造退化体 → 布尔/修复/验证 → 断言不崩溃
|
||
|
||
#include <gtest/gtest.h>
|
||
#include "vde/brep/brep.h"
|
||
#include "vde/brep/modeling.h"
|
||
#include "vde/brep/brep_boolean.h"
|
||
#include "vde/brep/brep_validate.h"
|
||
#include "vde/brep/brep_heal.h"
|
||
#include "vde/core/point.h"
|
||
#include <cmath>
|
||
#include <limits>
|
||
|
||
using namespace vde::brep;
|
||
using namespace vde::core;
|
||
|
||
// ── 辅助函数 ──
|
||
|
||
/// 断言 BrepModel 至少不会崩溃(尝试布尔、验证、修复都不会抛异常)
|
||
void assert_no_crash(const BrepModel& body) {
|
||
// 1. 基本验证:能调用 is_valid()
|
||
EXPECT_NO_THROW({ body.is_valid(); });
|
||
|
||
// 2. 验证:能调用 validate()
|
||
EXPECT_NO_THROW({ auto result = validate(body); (void)result; });
|
||
|
||
// 3. 布尔自交:A∪A, A∩A, A\\A
|
||
EXPECT_NO_THROW({ auto u = brep_union(body, body); (void)u; });
|
||
EXPECT_NO_THROW({ auto i = brep_intersection(body, body); (void)i; });
|
||
EXPECT_NO_THROW({ auto d = brep_difference(body, body); (void)d; });
|
||
|
||
// 4. 修复:heal_topology 不崩溃
|
||
{
|
||
BrepModel copy = body;
|
||
EXPECT_NO_THROW({ heal_topology(copy, 1e-6); });
|
||
}
|
||
|
||
// 5. 网格导出:to_mesh 不崩溃
|
||
EXPECT_NO_THROW({ auto mesh = body.to_mesh(); (void)mesh; });
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 零长度边测试
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, ZeroLengthEdge_Box) {
|
||
// 通过 make_box(0, 1, 1) 构造含退化边的体
|
||
// 宽度为 0 → 侧面退化为零面积面
|
||
EXPECT_NO_THROW({
|
||
auto body = make_box(0.0, 1.0, 1.0);
|
||
assert_no_crash(body);
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, ZeroLengthEdge_ExtremeSmall) {
|
||
// 极窄长方体(宽 ≤ 1e-12)→ 近乎零长度边
|
||
for (double w : {1e-12, 1e-10, 1e-8}) {
|
||
EXPECT_NO_THROW({
|
||
auto body = make_box(w, 1.0, 1.0);
|
||
assert_no_crash(body);
|
||
});
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 零面积面测试
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, ZeroAreaFace_FlatBox) {
|
||
// 一个维度为 0 → 退化成一个平面
|
||
EXPECT_NO_THROW({
|
||
auto box0 = make_box(0.0, 5.0, 5.0);
|
||
assert_no_crash(box0);
|
||
|
||
// 与正常 box 做布尔
|
||
auto box = make_box(3.0, 3.0, 3.0);
|
||
auto u = brep_union(box0, box);
|
||
(void)u.is_valid();
|
||
auto i = brep_intersection(box0, box);
|
||
(void)i.is_valid();
|
||
auto d = brep_difference(box, box0);
|
||
(void)d.is_valid();
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, ZeroAreaFace_Sliver) {
|
||
// 一个极其扁平的体(高度极其小)
|
||
for (double h : {1e-12, 1e-10, 1e-9}) {
|
||
EXPECT_NO_THROW({
|
||
auto thin = make_box(1.0, h, 1.0);
|
||
assert_no_crash(thin);
|
||
|
||
// 瘦短板与正常盒子的布尔
|
||
auto box = make_box(2.0, 2.0, 2.0);
|
||
auto u = brep_union(thin, box);
|
||
(void)u.is_valid();
|
||
});
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 重合顶点测试
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, CoincidentVertices_SelfUnion) {
|
||
// 两个完全相同的盒子做布尔 → 所有面/边/顶点重合
|
||
auto box = make_box(2.0, 2.0, 2.0);
|
||
EXPECT_NO_THROW({
|
||
auto u = brep_union(box, box);
|
||
EXPECT_TRUE(u.is_valid());
|
||
auto result = validate(u);
|
||
(void)result;
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, CoincidentVertices_SelfIntersection) {
|
||
auto box = make_box(2.0, 2.0, 2.0);
|
||
EXPECT_NO_THROW({
|
||
auto i = brep_intersection(box, box);
|
||
EXPECT_TRUE(i.is_valid());
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, CoincidentVertices_SelfDifference) {
|
||
auto box = make_box(2.0, 2.0, 2.0);
|
||
EXPECT_NO_THROW({
|
||
auto d = brep_difference(box, box);
|
||
EXPECT_TRUE(d.is_valid());
|
||
});
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 自交体测试(通过治疗修复验证)
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, SelfIntersecting_Heal) {
|
||
// 使用极小偏移的重复体模拟自交场景
|
||
auto box = make_box(2.0, 2.0, 2.0);
|
||
|
||
// 重复布尔运算产生复杂拓扑,至少不应该崩溃
|
||
for (int i = 0; i < 5; ++i) {
|
||
EXPECT_NO_THROW({
|
||
auto u = brep_union(box, box);
|
||
auto result = validate(u);
|
||
(void)result;
|
||
BrepModel copy = u;
|
||
heal_topology(copy, 1e-4);
|
||
});
|
||
}
|
||
|
||
// 使用不同尺寸的盒子布尔链式操作
|
||
auto small = make_box(0.5, 0.5, 0.5);
|
||
EXPECT_NO_THROW({
|
||
auto diff = brep_difference(box, small);
|
||
auto result = validate(diff);
|
||
(void)result;
|
||
heal_topology(diff, 1e-4);
|
||
});
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 退化圆柱测试
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, DegenerateCylinder_ZeroRadius) {
|
||
// 半径为 0 的圆柱(退化为线)
|
||
EXPECT_NO_THROW({
|
||
auto cyl = make_cylinder(0.0, 1.0, 8);
|
||
assert_no_crash(cyl);
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, DegenerateCylinder_ZeroHeight) {
|
||
// 高度为 0 的圆柱(退化为圆盘)
|
||
EXPECT_NO_THROW({
|
||
auto cyl = make_cylinder(1.0, 0.0, 16);
|
||
assert_no_crash(cyl);
|
||
});
|
||
}
|
||
|
||
TEST(RegressionDegenerate, DegenerateSphere_ZeroRadius) {
|
||
// 半径为 0 的球(退化为点)
|
||
EXPECT_NO_THROW({
|
||
auto sph = make_sphere(0.0, 8, 4);
|
||
assert_no_crash(sph);
|
||
});
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════
|
||
// 退化修复流水线测试
|
||
// ═══════════════════════════════════════════════
|
||
|
||
TEST(RegressionDegenerate, HealDegenerateModel) {
|
||
// 从退化体开始,通过修复流水线处理
|
||
auto thin = make_box(1.0, 1e-12, 1.0);
|
||
EXPECT_NO_THROW({
|
||
int n_gaps = heal_gaps(thin, 1e-6);
|
||
(void)n_gaps;
|
||
int n_slivers = heal_slivers(thin);
|
||
(void)n_slivers;
|
||
int n_oriented = heal_orientation(thin);
|
||
(void)n_oriented;
|
||
bool ok = heal_topology(thin, 1e-4);
|
||
(void)ok;
|
||
});
|
||
}
|