Files
茂之钳 7b76689ea1
CI / Build & Test (push) Failing after 41s
CI / Release Build (push) Failing after 30s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v11): CI/CD + regression tests + fuzzing + benchmarks + code quality
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)
2026-07-27 01:10:58 +08:00

215 lines
7.4 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// @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. 布尔自交:AA, 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;
});
}