docs: doxygen annotations for curves + mesh + sketch
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 38s

This commit is contained in:
茂之钳
2026-07-24 11:04:04 +00:00
parent b97b0415f5
commit 4c9ee4f760
53 changed files with 5307 additions and 506 deletions
+16
View File
@@ -3,14 +3,30 @@
namespace vde::foundation {
/**
* @brief 全局错误码枚举
*
* VDE 引擎中所有函数可能返回的错误状态码。
* 成功用 kSuccess(0) 表示,非零值表示各类错误。
*
* @ingroup foundation
*/
enum class ErrorCode : int32_t {
/// 操作成功完成,无错误
kSuccess = 0,
/// 传入参数无效(空指针、越界、非法值等)
kInvalidArgument,
/// 输入几何数据不合法(自交、非流形、退化等)
kInvalidGeometry,
/// 退化情况(共线、共面等导致无法确定唯一解)
kDegenerateCase,
/// 功能尚未实现
kNotImplemented,
/// 数值计算误差超出容差范围
kNumericalError,
/// 内存分配失败
kOutOfMemory,
/// 内部逻辑错误(不应出现的情况)
kInternalError,
};
+119 -4
View File
@@ -3,32 +3,147 @@
namespace vde::foundation::exact {
/**
* @brief 二维定向测试结果
*
* 判断三点构成的三角形的定向(顺时针 / 逆时针 / 共线)。
*
* @ingroup foundation
*/
enum class Orientation { Clockwise, CounterClockwise, Collinear };
/**
* @brief 圆测试结果
*
* 判断点 d 相对于三角形 abc 外接圆的位置。
*
* @ingroup foundation
*/
enum class CircleTest { Inside, On, Outside };
/// Adaptive-precision 2D orientation test (Shewchuk)
/**
* @brief 自适应精度二维定向测试(Shewchuk 算法)
*
* 判断点 a, b, c 的定向。使用自适应浮点精度:
* 先用双精度计算行列式,若结果不可靠则逐步提高精度,直至确定符号。
*
* @param a 第一个点
* @param b 第二个点
* @param c 第三个点
* @return Orientation::Clockwise 若 a→b→c 顺时针
* @return Orientation::CounterClockwise 若 a→b→c 逆时针
* @return Orientation::Collinear 若三点共线
*
* @note Delaunay 三角剖分、凸包计算等几何算法的核心原语
* @warning 输入点坐标必须为有限值,NaN/Inf 会导致未定义行为
*
* @code{.cpp}
* Point2D a(0,0), b(1,0), c(0,1);
* auto ori = exact::orient_2d(a, b, c);
* // ori == Orientation::CounterClockwise
* @endcode
*
* @see orient_3d, in_circle
* @ingroup foundation
*/
Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c);
/// Adaptive-precision 3D orientation test
/**
* @brief 自适应精度三维定向测试
*
* 判断点 a, b, c, d 的定向(即点 d 在平面 abc 的哪一侧)。
* 等价于计算四面体 abcd 的有向体积的符号。
*
* @param a 第一个点
* @param b 第二个点
* @param c 第三个点
* @param d 第四个点(测试点)
* @return Orientation::CounterClockwise 若 d 在平面 abc 上方(按右手法则)
* @return Orientation::Clockwise 若 d 在平面 abc 下方
* @return Orientation::Collinear 若四点共面
*
* @note 三维 Delaunay 剖分和凸包计算的核心原语
*
* @code{.cpp}
* Point3D a(0,0,0), b(1,0,0), c(0,1,0), d(0,0,1);
* auto ori = exact::orient_3d(a, b, c, d);
* // ori == Orientation::CounterClockwise
* @endcode
*
* @see orient_2d, in_circle
* @ingroup foundation
*/
Orientation orient_3d(const Point3D& a, const Point3D& b,
const Point3D& c, const Point3D& d);
/// Adaptive-precision in-circle test
/**
* @brief 自适应精度圆内测试(In-Circle Test
*
* 判断点 d 是否在三角形 abc 的外接圆内部、边界上或外部。
* 等价于计算 4×4 行列式的符号。
*
* @param a 三角形第一个顶点
* @param b 三角形第二个顶点
* @param c 三角形第三个顶点
* @param d 待测试点
* @return CircleTest::Inside 若 d 在三角形外接圆内部
* @return CircleTest::On 若 d 恰好在三角形外接圆上
* @return CircleTest::Outside 若 d 在三角形外接圆外部
*
* @note Delaunay 三角剖分的空圆性质检查核心原语
* @pre 三点 a、b、c 不共线
*
* @code{.cpp}
* Point2D a(0,0), b(2,0), c(0,2);
* Point2D d(0.5, 0.5); // 在外接圆内部
* auto result = exact::in_circle(a, b, c, d);
* // result == CircleTest::Inside
* @endcode
*
* @see orient_2d, orient_3d
* @ingroup foundation
*/
CircleTest in_circle(const Point2D& a, const Point2D& b,
const Point2D& c, const Point2D& d);
} // namespace vde::foundation::exact
/// Adaptive double-precision predicates (default backend)
/**
* @brief 自适应双精度谓词后端(默认)
*
* 封装 exact 命名空间下的自适应精度谓词函数,
* 作为 Predicates 的默认后端实现。
*
* @ingroup foundation
*/
namespace vde::foundation {
struct AdaptiveDouble {
/**
* @brief 二维定向测试
* @param a,b,c 三个二维点
* @return 定向结果
* @see exact::orient_2d
*/
static exact::Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) {
return exact::orient_2d(a, b, c);
}
/**
* @brief 三维定向测试
* @param a,b,c,d 四个三维点
* @return 定向结果
* @see exact::orient_3d
*/
static exact::Orientation orient_3d(const Point3D& a, const Point3D& b,
const Point3D& c, const Point3D& d) {
return exact::orient_3d(a, b, c, d);
}
/**
* @brief 圆内测试
* @param a,b,c 三角形三顶点
* @param d 待测试点
* @return 圆测试结果
* @see exact::in_circle
*/
static exact::CircleTest in_circle(const Point2D& a, const Point2D& b,
const Point2D& c, const Point2D& d) {
return exact::in_circle(a, b, c, d);
+52 -4
View File
@@ -6,11 +6,39 @@
namespace vde::foundation::exact {
/// GMP-based exact predicates — zero rounding error, arbitrary precision
/**
* @brief GMP 精确算术谓词后端
*
* 基于 GNU MP(多精度有理数)的精确几何谓词。
* 使用高精度有理数运算,零舍入误差,适用于对鲁棒性要求极高的场景。
*
* @note 仅在定义了 VDE_USE_GMP 宏且链接了 GMP 库时可用
* @warning GMP 版本比自适应双精度版本慢约 10-50 倍,仅在双精度不足以确定结果时使用
*
* @ingroup foundation
*/
struct GmpExact {
/// GMP 有理数类型
using Rational = mpq_class;
/// orient_2d with exact rational arithmetic
/**
* @brief 精确有理数二维定向测试
*
* 使用 GMP 有理数精确计算三点定向,零误差。
*
* @param a 第一个点
* @param b 第二个点
* @param c 第三个点
* @return 定向结果(保证正确,无舍入误差)
*
* @code{.cpp}
* // 对近共线点仍能给出确定结果
* Point2D a(0, 0), b(1, 1e-15), c(2, 2e-15);
* auto ori = GmpExact::orient_2d(a, b, c);
* @endcode
*
* @see GmpExact::orient_3d, GmpExact::in_circle
*/
static Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) {
Rational ax(a.x()), ay(a.y());
Rational bx(b.x()), by(b.y());
@@ -21,7 +49,17 @@ struct GmpExact {
s < 0 ? Orientation::Clockwise : Orientation::Collinear;
}
/// orient_3d with exact rational arithmetic
/**
* @brief 精确有理数三维定向测试
*
* 判断点 d 相对于平面 abc 的位置,使用有理数精确运算。
*
* @param a,b,c 定义平面的三点
* @param d 待测试点
* @return 定向结果(零误差)
*
* @see GmpExact::orient_2d
*/
static Orientation orient_3d(const Point3D& a, const Point3D& b,
const Point3D& c, const Point3D& d) {
Rational ax(a.x()), ay(a.y()), az(a.z());
@@ -42,7 +80,17 @@ struct GmpExact {
s < 0 ? Orientation::Clockwise : Orientation::Collinear;
}
/// in_circle with exact rational arithmetic
/**
* @brief 精确有理数圆内测试
*
* 判断点 d 是否在三角形 abc 的外接圆内,零误差。
*
* @param a,b,c 三角形三顶点
* @param d 待测试点
* @return 圆测试结果(零误差)
*
* @see GmpExact::orient_2d
*/
static CircleTest in_circle(const Point2D& a, const Point2D& b,
const Point2D& c, const Point2D& d) {
Rational ax(a.x()), ay(a.y());
+83 -5
View File
@@ -8,7 +8,26 @@
namespace vde::foundation {
/// Format double with adequate precision for CAD (12 significant digits)
/**
* @brief 以 CAD 级别精度格式化双精度浮点数
*
* 输出科学计数法字符串,保证 12 位有效数字,满足 CAD 数据交换精度要求。
* NaN 被格式化为 "0.0"Inf/ -Inf 被裁剪为安全边界值。
*
* @param v 要格式化的双精度数
* @param precision 有效数字位数(默认 12)
* @return 格式化后的字符串,如 "1.234567890123e+02"
*
* @note 总是确保指数部分带符号(如 "e+00" 而非 "e00"),便于解析器统一处理
*
* @code{.cpp}
* auto s = fmt_double(3.14159265358979);
* // s == "3.141592653590e+00"
* @endcode
*
* @see fmt_iges_real, fmt_point
* @ingroup foundation
*/
inline std::string fmt_double(double v, int precision = 12) {
if (std::isnan(v)) return "0.0";
if (std::isinf(v)) return v > 0 ? "1e30" : "-1e30";
@@ -25,7 +44,22 @@ inline std::string fmt_double(double v, int precision = 12) {
return s;
}
/// Format for IGES: uses D exponent
/**
* @brief IGES 格式实数输出
*
* 与 fmt_double() 相同,但使用 IGES 标准的 'D' 指数标记代替 'E'。
*
* @param v 要格式化的双精度数
* @return IGES 格式字符串,如 "3.141592653590D+00"
*
* @code{.cpp}
* auto s = fmt_iges_real(3.14159265358979);
* // s == "3.141592653590D+00"
* @endcode
*
* @see fmt_double
* @ingroup foundation
*/
inline std::string fmt_iges_real(double v, int /*precision*/ = 12) {
auto s = fmt_double(v, 12);
auto pos = s.find('e');
@@ -33,14 +67,41 @@ inline std::string fmt_iges_real(double v, int /*precision*/ = 12) {
return s;
}
/// Format a Point3D as comma-separated reals
/**
* @brief 格式化三维点为逗号分隔字符串
*
* 输出 "x,y,z" 形式,常用于 IGES/STEP/OBJ 等文本格式的顶点坐标行。
*
* @param p 三维点
* @param use_d 若为 true,使用 IGES 风格 D 指数标记
* @return 逗号分隔的坐标字符串
*
* @code{.cpp}
* Point3D p(1.0, 2.5, -3.14);
* auto s = fmt_point(p);
* // s == "1.000000000000e+00,2.500000000000e+00,-3.140000000000e+00"
* @endcode
*
* @see fmt_double, fmt_iges_real
* @ingroup foundation
*/
inline std::string fmt_point(const core::Point3D& p, bool use_d = false) {
using FmtFn = std::string (*)(double, int);
FmtFn fn = use_d ? static_cast<FmtFn>(fmt_iges_real) : fmt_double;
return fn(p.x(), 12) + "," + fn(p.y(), 12) + "," + fn(p.z(), 12);
}
/// Pad a string to exactly 72 characters with spaces
/**
* @brief 将字符串填充到恰好 72 个字符
*
* IGES 格式中每条数据行固定为 72 字符宽。不足用空格补齐,超出则截断。
*
* @param s 输入字符串
* @return 恰好 72 字符的字符串(不足右侧补空格,超出截断)
*
* @see format_iges_line
* @ingroup foundation
*/
inline std::string pad72(const std::string& s) {
std::string result = s;
if (result.size() > 72) result.resize(72);
@@ -48,7 +109,24 @@ inline std::string pad72(const std::string& s) {
return result;
}
/// Format an IGES section line: data + section marker (80 columns total)
/**
* @brief 格式化 IGES 段落行
*
* 生成一条完整的 IGES 80 列记录行:72 列数据 + 8 列序号(section_char + 7 位数字)。
*
* @param data 数据内容(最多 72 字符,超出截断)
* @param section_char 段落标识符(如 'P' 参数段, 'D' 目录段)
* @param seq 行序号(1-based
* @return 完整 IGES 行(含换行符),共 81 字符
*
* @code{.cpp}
* auto line = format_iges_line("110,1.0,2.0,3.0;", 'P', 1);
* // 输出: "110,1.0,2.0,3.0; P0000001\n"
* @endcode
*
* @see pad72
* @ingroup foundation
*/
inline std::string format_iges_line(const std::string& data, char section_char, int seq) {
std::string line = pad72(data);
char seq_buf[9];
+155 -12
View File
@@ -5,33 +5,94 @@
namespace vde::foundation {
/// 双精度区间算术:存储 [lo, hi] 边界,用于验证计算结果可靠性
/**
* @brief 双精度区间算术类
*
* 存储区间 [lo, hi] 的上下边界,支持区间算术运算和确定性比较。
* 主要用于验证浮点计算的可靠性:若区间结果不跨越零,则可确定符号;
* 若跨越零,则说明双精度不足以确定结果,需要更高精度或精确算术。
*
* 区间运算遵循保守原则(结果区间包含所有可能的真实值),
* 即 out = [min(所有组合), max(所有组合)]。
*
* @ingroup foundation
*/
class Interval {
public:
double lo, hi;
/// 默认构造:空区间 [0, 0]
Interval() : lo(0), hi(0) {}
/**
* @brief 从单值构造精确区间(退化为点)
* @param v 区间值,[v, v]
*/
Interval(double v) : lo(v), hi(v) {}
/**
* @brief 从上下界构造区间
*
* 自动交换以确保 lo ≤ hi。
*
* @param l 下界
* @param h 上界
*/
Interval(double l, double h) : lo(std::min(l,h)), hi(std::max(l,h)) {}
/// 区间中点
/**
* @brief 区间中点
* @return (lo + hi) / 2
*/
[[nodiscard]] double mid() const { return (lo + hi) * 0.5; }
/// 区间宽度
/**
* @brief 区间宽度
* @return hi - lo
*/
[[nodiscard]] double width() const { return hi - lo; }
/// 区间是否包含值
/**
* @brief 判断区间是否包含给定值
* @param v 测试值
* @return true 若 lo ≤ v ≤ hi
*/
[[nodiscard]] bool contains(double v) const { return lo <= v && v <= hi; }
/// 是否精确(宽度 < eps
/**
* @brief 判断区间是否足够窄(收敛)
* @param eps 宽度阈值(默认 1e-12
* @return true 若区间宽度 < eps
*/
[[nodiscard]] bool is_exact(double eps = 1e-12) const { return width() < eps; }
/// 区间是否与另一个区间重叠
/**
* @brief 判断两个区间是否有重叠
* @param o 另一个区间
* @return true 若存在公共点
*/
[[nodiscard]] bool overlaps(const Interval& o) const {
return !(hi < o.lo || o.hi < lo);
}
/// 确定性比较
/**
* @brief 确定性区间比较
*
* 仅当两个区间无重叠时才能确定大小关系。
*
* @param o 另一个区间
* @return -1 若确定 this < o
* @return 1 若确定 this > o
* @return 0 若区间重叠,无法确定
*
* @code{.cpp}
* Interval a(1.0, 2.0), b(3.0, 4.0);
* int c = a.cmp(b); // c == -1 (确定 a < b)
*
* Interval c(1.0, 3.0), d(2.0, 4.0);
* int r = c.cmp(d); // r == 0 (重叠,无法确定)
* @endcode
*/
[[nodiscard]] int cmp(const Interval& o) const {
if (hi < o.lo) return -1; // 确定 this < o
if (lo > o.hi) return 1; // 确定 this > o
@@ -39,16 +100,42 @@ public:
}
// ── 算术运算 ──
/**
* @brief 区间加法
* @return [this.lo + o.lo, this.hi + o.hi]
*/
Interval operator+(const Interval& o) const {
return {lo + o.lo, hi + o.hi};
}
/**
* @brief 区间减法
* @return [this.lo - o.hi, this.hi - o.lo](保守估计)
*/
Interval operator-(const Interval& o) const {
return {lo - o.hi, hi - o.lo};
}
/**
* @brief 区间乘法
*
* 取四项乘积的最小值和最大值作为结果区间。
*
* @return [min(a*c,a*d,b*c,b*d), max(a*c,a*d,b*c,b*d)]
*/
Interval operator*(const Interval& o) const {
double a = lo*o.lo, b = lo*o.hi, c = hi*o.lo, d = hi*o.hi;
return {std::min({a,b,c,d}), std::max({a,b,c,d})};
}
/**
* @brief 区间除法
*
* 若除区间跨越零,返回极大区间表示不确定性。
*
* @return 结果区间或 [-∞, +∞](除数为零区间)
*/
Interval operator/(const Interval& o) const {
if (o.lo <= 0 && o.hi >= 0) return {-std::numeric_limits<double>::max(),
std::numeric_limits<double>::max()};
@@ -56,26 +143,60 @@ public:
return {std::min({a,b,c,d}), std::max({a,b,c,d})};
}
/// 取负
Interval operator-() const { return {-hi, -lo}; }
// ── 数学函数(保守区间)──
/**
* @brief 区间平方根
*
* 下界对 0 取 max 以处理负值(保守处理)。
*
* @return [sqrt(max(0, lo)), sqrt(max(0, hi))]
*/
[[nodiscard]] Interval sqrt() const {
return {std::sqrt(std::max(0.0, lo)), std::sqrt(std::max(0.0, hi))};
}
/// 从 double 创建区间(含舍入误差)
/**
* @brief 从双精度值创建区间(含舍入误差带)
*
* @param v 中心值
* @param error 误差半径(默认 0
* @return [v - error, v + error]
*/
static Interval from_double(double v, double error = 0) {
return {v - error, v + error};
}
/// 将 double 表示的坐标转为区间(含 ULP 误差)
/**
* @brief 从坐标值创建区间(含 ULP 误差带)
*
* 误差估计为 |v| * 2.22e-16 + 1e-300,即双精度最小单位。
*
* @param v 坐标值
* @return 包含浮点舍入误差的区间
*/
static Interval from_coord(double v) {
double eps = std::abs(v) * 2.22e-16 + 1e-300;
return {v - eps, v + eps};
}
};
/// 行列式的区间验证
/**
* @brief 二维定向行列式的区间验证
*
* 用区间算术计算 orient_2d 行列式,结果区间用于判断双精度是否可靠。
*
* @param ax,ay 点 a 的坐标
* @param bx,by 点 b 的坐标
* @param cx,cy 点 c 的坐标
* @return 行列式的区间值
*
* @see orient_2d_verified
* @ingroup foundation
*/
inline Interval orient_2d_interval(double ax, double ay, double bx, double by, double cx, double cy) {
Interval acx = Interval::from_coord(ax - cx);
Interval acy = Interval::from_coord(ay - cy);
@@ -84,8 +205,30 @@ inline Interval orient_2d_interval(double ax, double ay, double bx, double by, d
return acx * bcy - acy * bcx;
}
/// 使用区间算术验证 orient_2d 结果
/// 返回: -1(CW), 0(无法确定), 1(CCW)
/**
* @brief 使用区间算术验证 orient_2d 结果
*
* 若双精度计算结果接近零,调用此函数进行区间验证。
* 若区间不跨越零,则结果可靠;否则需要升级到精确算术。
*
* @param ax,ay 点 a 的坐标
* @param bx,by 点 b 的坐标
* @param cx,cy 点 c 的坐标
* @return -1 确认 Clockwise
* @return 0 无法确定(需升级到 GMP / 自适应精度)
* @return 1 确认 CounterClockwise
*
* @code{.cpp}
* int result = orient_2d_verified(0.0, 0.0, 1.0, 1e-16, 2.0, 2e-16);
* if (result == 0) {
* // 双精度不可靠,需要精确算术
* auto ori = GmpExact::orient_2d(a, b, c);
* }
* @endcode
*
* @see orient_2d_interval, Interval::cmp
* @ingroup foundation
*/
inline int orient_2d_verified(double ax, double ay, double bx, double by, double cx, double cy) {
Interval det = orient_2d_interval(ax, ay, bx, by, cx, cy);
return det.cmp(Interval(0.0));
+64 -12
View File
@@ -7,24 +7,76 @@
namespace vde::foundation {
/// GLTF export options
/**
* @brief glTF 2.0 导出选项
*
* 控制网格以 glTF/GLB 格式导出的参数。
*
* @ingroup foundation
*/
struct GltfOptions {
bool binary = true; // GLB (single file) vs glTF (JSON + .bin)
bool include_normals = true; // Compute and export vertex normals
bool include_colors = false; // Per-vertex colors
std::vector<float> vertex_colors; // RGB per vertex (if include_colors)
/// 是否以二进制 GLB 格式(单文件)输出;false 则输出 JSON + .bin 分离格式
bool binary = true;
/// 是否计算和导出顶点法线
bool include_normals = true;
/// 是否导出逐顶点颜色
bool include_colors = false;
/// 逐顶点 RGB 颜色数据(仅当 include_colors = true 时使用)
std::vector<float> vertex_colors;
};
/// Export mesh to glTF 2.0 / GLB
/// Returns true on success
/**
* @brief 导出网格为 glTF 2.0 / GLB 格式
*
* 将 HalfedgeMesh 写入 glTF 2.0 规范文件。默认输出单文件 GLB 二进制格式,
* 可通过 GltfOptions 控制输出格式和包含的属性。
*
* @param filepath 输出文件路径(.glb 或 .gltf
* @param mesh 要导出的半边网格
* @param options 导出选项(格式、法线、颜色等)
* @return true 导出成功
* @return false 导出失败(文件无法创建等)
*
* @note 生成的 glTF 符合 2.0 规范,兼容 Blender、three.js、Unreal Engine 等工具
*
* @code{.cpp}
* mesh::HalfedgeMesh mesh = /* ... */;
* GltfOptions opts;
* opts.binary = true;
* opts.include_normals = true;
* bool ok = write_gltf("output.glb", mesh, opts);
* @endcode
*
* @see write_brep_gltf, GltfOptions
* @ingroup foundation
*/
bool write_gltf(const std::string& filepath, const mesh::HalfedgeMesh& mesh,
const GltfOptions& options = GltfOptions{});
/// Export B-Rep model to GLB by tessellating faces
/// @param filepath Output file (.glb recommended)
/// @param model B-Rep model to export
/// @param tessellation_res Resolution for curved surfaces (segments per edge)
/// @return true on success
/**
* @brief 导出 B-Rep 模型为 GLB 格式(通过面片化)
*
* 将 B-Rep 模型的参数曲面离散化为三角网格后导出为 GLB。
* 曲面通过 tessellation_res 控制离散精度。
*
* @param filepath 输出文件路径(建议 .glb)
* @param model 要导出的 B-Rep 模型
* @param tessellation_res 每条边的离散段数(默认 32),越大越精确
* @return true 导出成功
* @return false 导出失败
*
* @code{.cpp}
* brep::BrepModel model = /* ... */;
* // 高精度曲面导出
* bool ok = write_brep_gltf("model.glb", model, 64);
* @endcode
*
* @see write_gltf
* @ingroup foundation
*/
bool write_brep_gltf(const std::string& filepath, const brep::BrepModel& model,
int tessellation_res = 32);
+83 -8
View File
@@ -5,31 +5,106 @@
namespace vde::foundation {
/**
* @brief OBJ 文件解析结果数据结构
*
* 存储从 Wavefront OBJ 文件读取的完整几何数据,
* 包括顶点位置、纹理坐标、法线、面信息和材质分组。
*
* 内部索引均为 0-based,与文件中的 1-based 索引不同。
*
* @ingroup foundation
*/
struct ObjMeshData {
/// 顶点位置数组
std::vector<Point3D> vertices;
/// 纹理坐标数组(可能为空)
std::vector<Point2D> texcoords;
/// 顶点法线数组(可能为空)
std::vector<Vector3D> normals;
/// Vertex indices per face (0-based internally).
/// Always populated regardless of face format.
/**
* @brief 每个面的顶点索引列表(0-based)
*
* 无论文件中面的格式如何(纯顶点、"v/vt"、"v/vt/vn"),
* 此字段始终被填充。faces[i] 存储第 i 个面的所有顶点索引。
*/
std::vector<std::vector<int>> faces;
/// Texcoord indices per face (0-based). Empty if the file had no vt data.
/// Same length as faces; each sub-vector same length as corresponding face.
/**
* @brief 每个面的纹理坐标索引列表(0-based)
*
* 与 faces 数组长度相同;若无纹理数据则为空。
*/
std::vector<std::vector<int>> face_texcoords;
/// Normal indices per face (0-based). Empty if the file had no vn data.
/**
* @brief 每个面的法线索引列表(0-based)
*
* 与 faces 数组长度相同;若无法线数据则为空。
*/
std::vector<std::vector<int>> face_normals;
/// Material name per face group (appears before the faces that use it).
/// The i-th entry gives the material active for the i-th face.
/**
* @brief 每个面对应的材质名称
*
* face_materials[i] 给出第 i 个面使用的材质名。
* 材质名来源于 usemtl 指令,在定义后所有后续面使用该材质。
*/
std::vector<std::string> face_materials;
/// Helpers
/// 是否有纹理坐标数据
[[nodiscard]] bool has_texcoords() const { return !texcoords.empty(); }
};
/**
* @brief 读取 Wavefront OBJ 文件
*
* 解析 ASCII OBJ 文件,支持以下 OBJ 特性:
* - v(顶点)、vt(纹理坐标)、vn(法线)
* - f(面):支持三角面和任意多边形面
* - usemtl(材质名称)、g(组名)、o(对象名)
* - # 注释行
*
* @param filepath OBJ 文件路径
* @return 解析后的 ObjMeshData
*
* @throws std::runtime_error 若文件无法打开或格式错误
*
* @note 不解析 MTL 材质文件,仅记录 usemtl 指令中的材质名称
* @note 面索引在内部转换为 0-based;若文件中使用相对索引(负值),自动转换为正向
*
* @code{.cpp}
* auto data = read_obj("model.obj");
* std::cout << "Vertices: " << data.vertices.size() << "\n";
* std::cout << "Faces: " << data.faces.size() << "\n";
* @endcode
*
* @see write_obj, ObjMeshData
* @ingroup foundation
*/
ObjMeshData read_obj(const std::string& filepath);
/**
* @brief 写入 Wavefront OBJ 文件
*
* 将 ObjMeshData 以 ASCII OBJ 格式写出,包含顶点、纹理坐标、
* 法线和面信息。材质名称通过注释嵌入。
*
* @param filepath 输出文件路径
* @param data 要写出的网格数据
*
* @note 输出的面索引为 1-basedOBJ 标准)
*
* @code{.cpp}
* ObjMeshData data;
* // ... 填充 data ...
* write_obj("output.obj", data);
* @endcode
*
* @see read_obj
* @ingroup foundation
*/
void write_obj(const std::string& filepath, const ObjMeshData& data);
} // namespace vde::foundation
+43 -3
View File
@@ -5,11 +5,51 @@
namespace vde::foundation {
/// Read PLY file (Stanford Polygon Format)
/// Supports ASCII and binary little-endian, vertex + face elements
/**
* @brief 读取 PLY 文件(Stanford Polygon Format
*
* 解析 PLY 格式文件,支持 ASCII 和二进制(小端序)两种编码。
* 支持的 PLY 元素:vertex(位置、法线、颜色)、face(顶点索引列表)。
*
* @param filepath PLY 文件路径
* @return 解析后的半边网格
*
* @throws std::runtime_error 若文件无法打开、格式不支持或数据损坏
*
* @note 自动检测文件头部 magic number 判断 ASCII / 二进制格式
* @note 二进制格式仅支持小端序(little-endian
* @note 返回类型为 HalfedgeMesh,内部自动建立半边拓扑
*
* @code{.cpp}
* auto mesh = read_ply("scan.ply");
* std::cout << "Vertices: " << mesh.vertex_count() << "\n";
* std::cout << "Faces: " << mesh.face_count() << "\n";
* @endcode
*
* @see write_ply
* @ingroup foundation
*/
mesh::HalfedgeMesh read_ply(const std::string& filepath);
/// Write PLY file (ASCII format)
/**
* @brief 写入 PLY 文件(ASCII 格式)
*
* 将半边网格以 ASCII PLY 格式写出,包含顶点位置和面信息。
*
* @param filepath 输出文件路径
* @param mesh 要导出的半边网格
*
* @note 输出的 PLY 为 ASCII 编码,易于人工阅读和编辑
* @note 若需要二进制输出以提高性能,请使用 BinarySerializer
*
* @code{.cpp}
* mesh::HalfedgeMesh mesh = /* ... */;
* write_ply("output.ply", mesh);
* @endcode
*
* @see read_ply, BinarySerializer
* @ingroup foundation
*/
void write_ply(const std::string& filepath, const mesh::HalfedgeMesh& mesh);
} // namespace vde::foundation
+70 -3
View File
@@ -5,18 +5,85 @@
namespace vde::foundation {
/**
* @brief STL 三角形面片
*
* 表示一个 STL 三角形,包含法向量和三个顶点。
* 法向量应为单位向量,方向朝外(右手法则)。
*
* @ingroup foundation
*/
struct StlTriangle {
/// 面法向量(单位向量)
Vector3D normal;
/// 三个顶点(右手法则 CCW 顺序)
Point3D v0, v1, v2;
};
/// Auto-detect format (binary or ASCII) and read.
/**
* @brief 读取 STL 文件(自动检测格式)
*
* 通过检查文件头部的 80 字节判断二进制(含 triangle count
* 还是 ASCII(以 "solid" 开头)格式,然后选择对应解析器。
*
* @param filepath STL 文件路径
* @return 三角形面片列表
*
* @throws std::runtime_error 若文件无法打开或格式无效
*
* @note 自动格式检测:二进制 STL 的 80 字节头后跟 4 字节三角形数量
* @note ASCII STL 以 "solid name" 开头
*
* @code{.cpp}
* auto tris = read_stl("part.stl");
* std::cout << "Triangles: " << tris.size() << "\n";
* @endcode
*
* @see write_stl, write_stl_ascii
* @ingroup foundation
*/
std::vector<StlTriangle> read_stl(const std::string& filepath);
/// Always write binary STL.
/**
* @brief 写入二进制 STL 文件
*
* 以紧凑的二进制格式写出 STL 三角形列表。
* 二进制 STL 为业界标准交换格式,文件体积小、读写快。
*
* @param filepath 输出文件路径
* @param tris 三角形面片列表
*
* @note 二进制 STL 格式:80 字节头 + 4 字节三角形数 + 每三角形 50 字节
* @note 法向量在写入时自动归一化
*
* @code{.cpp}
* std::vector<StlTriangle> tris = /* ... */;
* write_stl("output.stl", tris); // 二进制,体积小
* @endcode
*
* @see read_stl, write_stl_ascii
* @ingroup foundation
*/
void write_stl(const std::string& filepath, const std::vector<StlTriangle>& tris);
/// Write ASCII STL (human-readable).
/**
* @brief 写入 ASCII STL 文件
*
* 以人类可读的 ASCII 文本格式写出 STL,便于调试和手动编辑。
*
* @param filepath 输出文件路径
* @param tris 三角形面片列表
*
* @warning ASCII STL 文件体积约为二进制 STL 的 5-10 倍,不适合大规模网格
*
* @code{.cpp}
* std::vector<StlTriangle> tris = /* ... */;
* write_stl_ascii("debug.stl", tris); // 可读文本,便于调试
* @endcode
*
* @see read_stl, write_stl
* @ingroup foundation
*/
void write_stl_ascii(const std::string& filepath, const std::vector<StlTriangle>& tris);
} // namespace vde::foundation
+49
View File
@@ -5,30 +5,79 @@
namespace vde::foundation {
/**
* @brief 通用 N 维点类型(使用 Eigen 列向量)
*
* @tparam T 标量类型(double, float 等)
* @tparam Dim 维度
*
* @ingroup foundation
*/
template <typename T, size_t Dim>
using Point = Eigen::Matrix<T, Dim, 1>;
/**
* @brief 通用 N 维向量类型(与 Point 相同的列向量表示)
*
* 虽然类型相同,但语义上区分几何点(位置)和向量(方向/位移)。
*
* @tparam T 标量类型
* @tparam Dim 维度
*
* @ingroup foundation
*/
template <typename T, size_t Dim>
using Vector = Eigen::Matrix<T, Dim, 1>;
/// 3×3 矩阵模板
template <typename T>
using Matrix3 = Eigen::Matrix<T, 3, 3>;
/// 4×4 矩阵模板
template <typename T>
using Matrix4 = Eigen::Matrix<T, 4, 4>;
/// 双精度二维点
using Point2D = Point<double, 2>;
/// 双精度三维点
using Point3D = Point<double, 3>;
/// 单精度二维点
using Point2f = Point<float, 2>;
/// 单精度三维点
using Point3f = Point<float, 3>;
/// 双精度齐次四维点(用于仿射变换)
using Point4D = Eigen::Matrix<double, 4, 1>;
/// 双精度二维向量
using Vector2D = Vector<double, 2>;
/// 双精度三维向量
using Vector3D = Vector<double, 3>;
/// 单精度二维向量
using Vector2f = Vector<float, 2>;
/// 单精度三维向量
using Vector3f = Vector<float, 3>;
/// 双精度 3×3 矩阵
using Matrix3D = Matrix3<double>;
/// 双精度 4×4 矩阵
using Matrix4D = Matrix4<double>;
} // namespace vde::foundation
/**
* @defgroup foundation 基础模块
*
* 基础模块提供 VDE 引擎的核心基础设施:
* - 数学类型定义(点、向量、矩阵)
* - 精确几何谓词(自适应双精度 + GMP 可选后端)
* - 区间算术验证
* - 分层容差管理
* - 内存池分配
* - 文件 I/OSTL、OBJ、PLY、glTF
* - 格式化工具与序列化
*
* @{
*/
/** @} */ // end of foundation group
+50
View File
@@ -5,12 +5,52 @@
namespace vde::foundation {
/**
* @brief 固定块大小的内存池
*
* 使用空闲链表实现 O(1) 的分配和释放。按固定大小的 chunk 预先分配内存,
* 适合频繁创建/销毁同类型小对象的场景(如半边网格的顶点和面)。
*
* @tparam T 池中存储的对象类型
*
* @note 内存池不调用对象的构造函数/析构函数,仅管理原始内存
* @note 适合 POD 类型或通过 placement new 手动管理生命周期的类型
* @warning 不在单个对象释放时归还内存给操作系统;仅在池析构时统一释放所有 chunk
*
* @code{.cpp}
* MemoryPool<Vertex> pool(4096);
* Vertex* v = pool.allocate(); // O(1)
* // ... 使用 v ...
* pool.deallocate(v); // O(1)
* @endcode
*
* @ingroup foundation
*/
template <typename T>
class MemoryPool {
public:
/**
* @brief 构造内存池
* @param chunk_size 每个内存块可容纳的对象数(默认 1024)
*/
explicit MemoryPool(size_t chunk_size = 1024) : chunk_size_(chunk_size) {}
/**
* @brief 析构内存池,释放所有已分配的内存块
*/
~MemoryPool() { for (auto* p : chunks_) ::operator delete(p); }
/**
* @brief 从池中分配 n 个 T 对象的内存
*
* 当前实现忽略 n 参数,每次始终分配一个对象。
* 若空闲链表为空,自动分配新的 chunk。
*
* @param n 对象数量(当前未使用,保留用于 API 兼容)
* @return 指向未初始化内存的指针
*
* @pre n 应小于 chunk_size_
*/
T* allocate(size_t n = 1) {
if (free_list_ == nullptr) allocate_chunk();
T* p = static_cast<T*>(free_list_);
@@ -18,12 +58,22 @@ public:
return p;
}
/**
* @brief 将内存归还到池中
*
* 将对象内存插入空闲链表头部,不调用析构函数。
*
* @param p 之前从 allocate() 获取的指针
*/
void deallocate(T* p, size_t = 1) {
*reinterpret_cast<void**>(p) = free_list_;
free_list_ = p;
}
private:
/**
* @brief 分配一个新的内存块并初始化空闲链表
*/
void allocate_chunk() {
void* chunk = ::operator new(chunk_size_ * sizeof(T));
chunks_.push_back(chunk);
+64 -1
View File
@@ -6,21 +6,46 @@
namespace vde::foundation {
/// Unified predicate interface — selects GMP or adaptive double at compile time
/**
* @brief 统一谓词接口 — 编译时选择 GMP 或自适应双精度后端
*
* 根据 VDE_USE_GMP 宏自动选择 GmpExact 或 AdaptiveDouble 作为后端。
* 上层代码只需使用 Predicates,无需关心具体实现。
*
* @ingroup foundation
*/
struct Predicates {
#ifdef VDE_USE_GMP
/// 使用 GMP 精确有理数算术(零误差,较慢)
using Backend = exact::GmpExact;
#else
/// 使用自适应双精度算术(Shewchuk 算法,默认)
using Backend = exact::AdaptiveDouble;
#endif
/**
* @brief 二维定向测试
* @param a,b,c 三点
* @return 定向结果
*/
static exact::Orientation orient_2d(const Point2D& a, const Point2D& b, const Point2D& c) {
return Backend::orient_2d(a, b, c);
}
/**
* @brief 三维定向测试
* @param a,b,c,d 四点
* @return 定向结果
*/
static exact::Orientation orient_3d(const Point3D& a, const Point3D& b,
const Point3D& c, const Point3D& d) {
return Backend::orient_3d(a, b, c, d);
}
/**
* @brief 圆内测试
* @param a,b,c 三角形三顶点
* @param d 测试点
* @return 圆测试结果
*/
static exact::CircleTest in_circle(const Point2D& a, const Point2D& b,
const Point2D& c, const Point2D& d) {
return Backend::in_circle(a, b, c, d);
@@ -28,12 +53,50 @@ struct Predicates {
};
// ── Convenience wrappers (preferred API) ──
/**
* @brief 二维定向测试便捷封装
*
* 使用 Predicates::orient_2d,无需指定后端。
* 这是推荐的对外 API。
*
* @param a,b,c 三个二维点
* @return Orientation::Clockwise / CounterClockwise / Collinear
*
* @code{.cpp}
* using namespace vde::foundation;
* Point2D a(0,0), b(1,0), c(0,1);
* auto result = orient(a, b, c); // CounterClockwise
* @endcode
*
* @ingroup foundation
*/
inline exact::Orientation orient(const Point2D& a, const Point2D& b, const Point2D& c) {
return Predicates::orient_2d(a, b, c);
}
/**
* @brief 三维定向测试便捷封装
*
* @param a,b,c 定义平面的三点
* @param d 测试点
* @return Orientation::Clockwise / CounterClockwise / Collinear
*
* @ingroup foundation
*/
inline exact::Orientation orient(const Point3D& a, const Point3D& b, const Point3D& c, const Point3D& d) {
return Predicates::orient_3d(a, b, c, d);
}
/**
* @brief 圆内测试便捷封装
*
* @param a,b,c 三角形三顶点
* @param d 测试点
* @return CircleTest::Inside / On / Outside
*
* @ingroup foundation
*/
inline exact::CircleTest in_circle(const Point2D& a, const Point2D& b, const Point2D& c, const Point2D& d) {
return Predicates::in_circle(a, b, c, d);
}
+93 -13
View File
@@ -6,33 +6,113 @@
namespace vde::foundation {
// Versioned binary format for fast save/load
// Header: 8 bytes magic + 4 bytes version + 4 bytes flags
constexpr uint64_t VDE_MAGIC = 0x56444547454F4D00ULL; // "VDEGEOM\0"
constexpr uint32_t VDE_FORMAT_VERSION = 1;
/**
* @brief 二进制序列化格式的网格数据结构
*
* 存储网格的扁平表示,顶点坐标连续排列,面索引连续排列。
* 所有面均为三角形(face_valence 固定为 3)。
*
* @ingroup foundation
*/
struct SerializedMesh {
/// 顶点数量
uint32_t vertex_count;
/// 面数量
uint32_t face_count;
std::vector<double> vertices; // xyz xyz ... (flat)
std::vector<int32_t> face_indices; // v0 v1 v2 ...
std::vector<int32_t> face_valence; // 3 for all triangles
/// 顶点坐标,扁平排列:x0 y0 z0 x1 y1 z1 ...
std::vector<double> vertices;
/// 面顶点索引,扁平排列:v0_0 v0_1 v0_2 v1_0 v1_1 v1_2 ...
std::vector<int32_t> face_indices;
/// 每面的顶点数(当前固定为 3,即纯三角形网格)
std::vector<int32_t> face_valence;
};
/**
* @brief 二进制序列化器
*
* 提供网格的快速二进制序列化/反序列化,用于 VDE 本地格式(VDE 二进制格式)。
*
* 文件格式:
* - 8 字节魔数 "VDEGEOM\0" (0x56444547454F4D00)
* - 4 字节格式版本号
* - 4 字节标志位
* - 序列化的网格数据
*
* @note 该格式专为 VDE 内部快速读写设计,不适用于跨平台数据交换
* @note 使用 glTF/OBJ/STL 进行外部交换,此格式用于本地缓存
*
* @ingroup foundation
*/
class BinarySerializer {
public:
/// Serialize mesh to binary buffer
/**
* @brief 序列化网格到二进制缓冲区
*
* @param mesh 要序列化的半边网格
* @return 压缩后的二进制数据(含文件头)
*
* @code{.cpp}
* auto data = BinarySerializer::serialize(mesh);
* // data 可直接写入文件或通过网络传输
* @endcode
*/
static std::vector<uint8_t> serialize(const mesh::HalfedgeMesh& mesh);
/// Deserialize mesh from binary buffer
/**
* @brief 从二进制缓冲区反序列化网格
*
* @param data 之前由 serialize() 生成的二进制数据
* @return 恢复的半边网格
*
* @throws std::runtime_error 若数据校验失败(魔数/版本不匹配)
*
* @code{.cpp}
* auto mesh = BinarySerializer::deserialize(data);
* @endcode
*/
static mesh::HalfedgeMesh deserialize(const std::vector<uint8_t>& data);
/// Write to file
/**
* @brief 序列化并写入文件
*
* 等价于 serialize() + 文件写入,一步完成。
*
* @param path 输出文件路径
* @param mesh 要保存的半边网格
* @return true 写入成功
* @return false 文件创建失败
*
* @see read_file
*/
static bool write_file(const std::string& path, const mesh::HalfedgeMesh& mesh);
/// Read from file
/**
* @brief 从文件读取并反序列化
*
* 等价于文件读取 + deserialize(),一步完成。
*
* @param path VDE 二进制格式文件路径
* @return 恢复的半边网格
*
* @throws std::runtime_error 若文件无法打开或格式无效
*
* @see write_file
*/
static mesh::HalfedgeMesh read_file(const std::string& path);
};
/**
* @brief VDE 二进制文件魔数:"VDEGEOM\0"
*
* 用于识别 VDE 二进制网格文件。
*/
constexpr uint64_t VDE_MAGIC = 0x56444547454F4D00ULL; // "VDEGEOM\0"
/**
* @brief VDE 二进制格式当前版本号
*
* 用于向前兼容:读取时检查版本号,若不一致则拒绝或执行迁移。
*/
constexpr uint32_t VDE_FORMAT_VERSION = 1;
} // namespace vde::foundation
+147 -7
View File
@@ -6,44 +6,136 @@
namespace vde::foundation {
/// 分层容差:支持全局 + 局部覆盖
/**
* @brief 分层容差管理类
*
* 提供四级容差(绝对、相对、角度、吸附),支持全局实例和局部作用域覆盖。
* 用于数值比较、几何判定和点合并时的精度控制。
*
* 四级容差含义:
* - **绝对容差 (absolute)**: 对接近零的值使用,直接比较差值的绝对值
* - **相对容差 (relative)**: 对大值使用,比较差值 / max(|a|, |b|)
* - **角度容差 (angular)**: 角度/方向比较时的阈值(弧度)
* - **吸附容差 (snapping)**: 点合并/对齐时的容差,通常比判定容差宽松
*
* 预设精度等级:
* - Modeling(): 1e-6 — 通用建模精度
* - Fitting(): 1e-3 — 近似拟合
* - Intersection(): 1e-8 — 精确求交
* - Snapping(): 1e-4 — 点吸附
*
* @ingroup foundation
*/
class Tolerance {
public:
/**
* @brief 构造容差对象
*
* @param absolute 绝对容差,默认 1e-6
* @param relative 相对容差,默认 1e-8
* @param angular 角度容差(弧度),默认 1e-8
* @param snapping 吸附容差,默认 1e-4
*/
Tolerance(double absolute = 1e-6, double relative = 1e-8,
double angular = 1e-8, double snapping = 1e-4)
: absolute_(absolute), relative_(relative),
angular_(angular), snapping_(snapping) {}
// ── 判定 ──
/**
* @brief 判断两点是否相等(基于绝对容差)
*
* 使用欧氏距离与绝对容差比较。
*
* @param a 第一个点(Eigen 矩阵)
* @param b 第二个点(Eigen 矩阵)
* @return true 若两点的欧氏距离 < absolute_
*/
bool points_equal(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) const {
return (a - b).norm() < absolute_;
}
/**
* @brief 判断值是否接近零
*
* @param value 测试值
* @return true 若 |value| < absolute_
*/
template <typename T>
bool is_zero(T value) const {
return std::abs(value) < absolute_;
}
/**
* @brief 混合容差相等判断
*
* 综合使用绝对和相对容差:
* |a - b| < absolute + relative * max(|a|, |b|)
*
* @tparam T 数值类型
* @param a 第一个值
* @param b 第二个值
* @return true 若两值在容差范围内相等
*
* @code{.cpp}
* Tolerance tol(1e-6, 1e-8);
* tol.equals(1.0000001, 1.0); // true
* tol.equals(1000.0001, 1000.0); // true (相对项起作用)
* @endcode
*/
template <typename T>
bool equals(T a, T b) const {
return std::abs(a - b) < absolute_ + relative_ * std::max(std::abs(a), std::abs(b));
}
/// 获取绝对容差
double absolute() const { return absolute_; }
/// 获取相对容差
double relative() const { return relative_; }
/// 获取角度容差
double angular() const { return angular_; }
/// 获取吸附容差
double snapping() const { return snapping_; }
// ── 分层容差:继承并收紧 ──
/**
* @brief 收紧容差
*
* 所有容差乘以 factor(< 1),用于在子操作中提高精度要求。
*
* @param factor 收紧因子(默认 0.1
* @return 收紧后的新容差对象
*
* @code{.cpp}
* auto rough = Tolerance(Tolerance::Fitting());
* auto precise = rough.tighten(0.01); // 拟合 → 建模级
* @endcode
*/
Tolerance tighten(double factor = 0.1) const {
return Tolerance(absolute_ * factor, relative_ * factor, angular_ * factor, snapping_ * factor);
}
/**
* @brief 放宽容差
*
* 所有容差乘以 factor(> 1),用于在上层操作中降低精度要求。
*
* @param factor 放宽因子(默认 10.0
* @return 放宽后的新容差对象
*/
Tolerance relax(double factor = 10.0) const {
return Tolerance(absolute_ * factor, relative_ * factor, angular_ * factor, snapping_ * factor);
}
// ── 合并:取更严格的那个 ──
/**
* @brief 合并两个容差,取更严格的(更小的)
*
* @param a 第一个容差
* @param b 第二个容差
* @return 每项取 min 的新容差
*/
static Tolerance min(const Tolerance& a, const Tolerance& b) {
return Tolerance(std::min(a.absolute_, b.absolute_),
std::min(a.relative_, b.relative_),
@@ -52,14 +144,35 @@ public:
}
// ── 全局实例 ──
/**
* @brief 获取全局容差实例
*
* 线程局部存储,默认值为建模精度 (1e-6)。
*
* @return 全局容差的引用
*/
static Tolerance& global() { return global_; }
/**
* @brief 设置全局容差
*
* @param tol 新的全局容差
*
* @note 推荐使用 ToleranceScope 进行临时修改,确保自动恢复
*/
static void set_global(const Tolerance& tol) { global_ = tol; }
// ── 默认建模容差等级 ──
static constexpr double Modeling() { return 1e-6; } // 建模级
static constexpr double Fitting() { return 1e-3; } // 拟合级
static constexpr double Intersection() { return 1e-8; } // 求交级
static constexpr double Snapping() { return 1e-4; } // 吸附级
/// 建模级精度 (1e-6):通用几何建模
static constexpr double Modeling() { return 1e-6; }
/// 拟合级精度 (1e-3):曲面拟合、近似计算
static constexpr double Fitting() { return 1e-3; }
/// 求交级精度 (1e-8):精确求交、布尔运算
static constexpr double Intersection() { return 1e-8; }
/// 吸附级精度 (1e-4):点合并、顶点焊接
static constexpr double Snapping() { return 1e-4; }
private:
double absolute_;
@@ -69,13 +182,40 @@ private:
static Tolerance global_;
};
/// 局部容差堆栈:支持作用域内的临时容差
/**
* @brief 局部容差作用域(RAII
*
* 构造时设置新的全局容差,析构时自动恢复为之前的值。
* 用于在特定代码块内临时修改容差。
*
* @code{.cpp}
* void precise_operation() {
* ToleranceScope scope(Tolerance::Intersection());
* // 在此作用域内使用求交级精度
* // ... 布尔运算 ...
* } // scope 析构,自动恢复
* @endcode
*
* @ingroup foundation
*/
class ToleranceScope {
public:
/**
* @brief 进入新的容差作用域
*
* 保存当前全局容差并设置新值。
*
* @param tol 此作用域内使用的容差
*/
explicit ToleranceScope(const Tolerance& tol) : previous_(Tolerance::global()) {
Tolerance::set_global(tol);
}
/**
* @brief 退出容差作用域,恢复之前的全局容差
*/
~ToleranceScope() { Tolerance::set_global(previous_); }
private:
Tolerance previous_;
};