docs: doxygen annotations for curves + mesh + sketch
This commit is contained in:
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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-based(OBJ 标准)
|
||||
*
|
||||
* @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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/O(STL、OBJ、PLY、glTF)
|
||||
* - 格式化工具与序列化
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @} */ // end of foundation group
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user