From ac4c4300ed80b801917b03a28ba147df32a92bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E4=B9=8B=E9=92=B3?= Date: Fri, 24 Jul 2026 11:09:45 +0000 Subject: [PATCH] docs: doxygen annotations for sdf + brep + capi --- include/vde/brep/brep.h | 320 +++++++++++++++- include/vde/brep/brep_boolean.h | 96 ++++- include/vde/brep/brep_validate.h | 89 ++++- include/vde/brep/iges_export.h | 90 ++++- include/vde/brep/iges_import.h | 103 ++++- include/vde/brep/modeling.h | 251 +++++++++++- include/vde/brep/step_export.h | 68 +++- include/vde/brep/step_import.h | 123 +++++- include/vde/capi/vde_capi.h | 640 +++++++++++++++++++++++++++++++ include/vde/sdf/sdf_optimize.h | 386 ++++++++++++++++--- include/vde/sdf/sdf_torch.h | 104 ++++- 11 files changed, 2119 insertions(+), 151 deletions(-) create mode 100644 include/vde/capi/vde_capi.h diff --git a/include/vde/brep/brep.h b/include/vde/brep/brep.h index e6accdd..8ba082d 100644 --- a/include/vde/brep/brep.h +++ b/include/vde/brep/brep.h @@ -1,4 +1,47 @@ #pragma once +/** + * @file brep.h + * @brief 边界表示(B-Rep)核心数据结构 + * + * B-Rep(Boundary Representation)是 CAD 系统中最常用的实体表示方法, + * 通过拓扑层次结构描述三维几何体的边界。 + * + * ## 拓扑层次结构 + * + * ``` + * Body (体) — 一个或多个封闭壳(含名称) + * └─ Shell (壳) — 一组面的连续集合 + * └─ Face (面) — 曲面上由环界定的区域 + * └─ Loop (环) — 封闭的边界曲线(外环 + 内环/孔) + * └─ Edge (边) — 曲面的边界段 + * └─ Vertex (顶点) — 边的端点 + * ``` + * + * ## 实体引用关系 + * + * 每个实体通过整数 ID 唯一标识。父实体通过 ID 向量引用子实体。 + * 曲面(NURBS Surface)独立存储,由面通过 surface_id 引用。 + * 曲线(NURBS Curve)作为边的几何定义存储。 + * + * ## 使用模式 + * + * @code{.cpp} + * BrepModel model; + * // 从顶点层开始构建 + * int v0 = model.add_vertex(Point3D(0, 0, 0)); + * int v1 = model.add_vertex(Point3D(1, 0, 0)); + * int e0 = model.add_edge(v0, v1); + * int loop0 = model.add_loop({e0}); + * int s0 = model.add_surface(plane_surface); + * int f0 = model.add_face(s0, {loop0}); + * int shell0 = model.add_shell({f0}); + * int body0 = model.add_body({shell0}, "MyPart"); + * @endcode + * + * 更常见的方式是使用 modeling.h 中的高层 API(make_box, extrude 等)。 + * + * @ingroup brep + */ #include "vde/core/point.h" #include "vde/core/aabb.h" #include "vde/curves/nurbs_curve.h" @@ -13,55 +56,296 @@ using core::Point3D; using core::Vector3D; using core::AABB3D; +/** + * @brief 边曲线类型枚举 + * + * 指定边所使用的曲线表示类别, + * 影响 tessellation、布尔运算和文件交换时的处理方式。 + */ enum class CurveType { Line, Circle, Bezier, BSpline, Nurbs }; -struct TopoVertex { int id; Point3D point; double tolerance = 1e-6; }; -struct TopoEdge { - int id, v_start, v_end; - std::shared_ptr curve; - bool reversed = false; -}; -struct TopoLoop { int id; std::vector edges; bool is_outer = true; }; -struct TopoFace { int id, surface_id; std::vector loops; bool reversed = false; }; -struct TopoShell { int id; std::vector faces; bool closed = true; }; -struct TopoBody { int id; std::vector shells; std::string name; }; +// ═══════════════════════════════════════════════ +// 拓扑实体定义 +// ═══════════════════════════════════════════════ +/** + * @brief 拓扑顶点 + * + * 三维空间中的一个点,带有容差信息。 + * 容差用于判断顶点的"等同性"——在容差范围内的点被视为同一顶点。 + */ +struct TopoVertex { + int id; ///< 唯一定点 ID + Point3D point; ///< 三维坐标 + double tolerance = 1e-6; ///< 几何容差 +}; + +/** + * @brief 拓扑边 + * + * 连接两个顶点的有向曲线段。 + * - `reversed = false`: 方向从 v_start 到 v_end + * - `reversed = true`: 方向从 v_end 到 v_start + * - `curve` 可以是 nullptr(隐含直线边) + */ +struct TopoEdge { + int id; ///< 唯一边 ID + int v_start, v_end; ///< 起点和终点顶点 ID + std::shared_ptr curve; ///< 边的几何曲线(可为空,表示直线) + bool reversed = false; ///< 是否相对于曲线参数方向反转 +}; + +/** + * @brief 拓扑环(Loop) + * + * 一组首尾相连的边构成的封闭边界。 + * - 外环 (is_outer = true): 面的外部边界,逆时针为正 + * - 内环 (is_outer = false): 面上的孔,顺时针为正 + */ +struct TopoLoop { + int id; ///< 唯一环 ID + std::vector edges; ///< 边 ID 序列(按环的遍历顺序) + bool is_outer = true; ///< true = 外环, false = 内环(孔) +}; + +/** + * @brief 拓扑面 + * + * 曲面上由一个或多个环界定的区域。 + * 面的正方向与曲面的法向量一致。 + * reversed = true 表示法向量取反。 + */ +struct TopoFace { + int id; ///< 唯一面 ID + int surface_id; ///< 关联的曲面 ID + std::vector loops; ///< 环 ID 列表(第一个为外环,后续为内环) + bool reversed = false; ///< 面法向量是否反转 +}; + +/** + * @brief 拓扑壳 + * + * 一组面的连续集合,构成空间的封闭或开放边界。 + * closed = true 表示壳封闭一个体积(水密)。 + */ +struct TopoShell { + int id; ///< 唯一壳 ID + std::vector faces; ///< 面 ID 列表 + bool closed = true; ///< 是否为封闭壳 +}; + +/** + * @brief 拓扑体 + * + * 一个或多个壳的集合,构成完整的几何实体。 + * name 用于在 STEP/IGES 文件中标识零件。 + */ +struct TopoBody { + int id; ///< 唯一体 ID + std::vector shells; ///< 壳 ID 列表 + std::string name; ///< 体名称(对应 STEP 中的 PRODUCT) +}; + +// ═══════════════════════════════════════════════ +// B-Rep 模型类 +// ═══════════════════════════════════════════════ + +/** + * @brief B-Rep 模型 + * + * 完整的三维边界表示模型,包含几何和拓扑数据。 + * + * ## 主要职责 + * + * - **构建**: 通过 add_* 方法从底层构建拓扑结构 + * - **查询**: 获取顶点、面、边及它们的拓扑关系 + * - **导出**: 转换为三角网格(用于渲染)或导出到文件格式 + * - **验证**: 检查水密性和拓扑一致性 + * + * ## 容差约定 + * + * | 容差 | 典型值 | 用途 | + * |-------------|---------|--------------------------| + * | 顶点容差 | 1e-6 | 判断两点是否视为同一点 | + * | 边曲线容差 | 1e-5 | 曲线拟合精度 | + * | tessellation | 0.01 | 曲面转网格的弦高误差 | + * + * @see brep_validate.h 验证 + * @see modeling.h 高层建模 API + * @see brep_boolean.h 布尔运算 + */ class BrepModel { public: BrepModel() = default; + + /** + * @brief 添加顶点 + * @param p 顶点坐标 + * @return 新顶点的 ID + */ int add_vertex(const Point3D& p); + + /** + * @brief 添加直线边 + * @param v0 起点顶点 ID + * @param v1 终点顶点 ID + * @return 新边的 ID + */ int add_edge(int v0, int v1); + + /** + * @brief 添加曲线边 + * @param v0 起点顶点 ID + * @param v1 终点顶点 ID + * @param curve 边的几何曲线 + * @return 新边的 ID + */ int add_edge(int v0, int v1, const curves::NurbsCurve& curve); + + /** + * @brief 添加环 + * @param edges 环中边的 ID 序列(顺序连接) + * @param outer true = 外环, false = 内环 + * @return 新环的 ID + */ int add_loop(const std::vector& edges, bool outer = true); + + /** + * @brief 添加面 + * @param surface_id 关联曲面 ID + * @param loops 环 ID 列表 + * @return 新面的 ID + */ int add_face(int surface_id, const std::vector& loops); + + /** + * @brief 添加壳 + * @param faces 面 ID 列表 + * @param closed 是否为封闭壳 + * @return 新壳的 ID + */ int add_shell(const std::vector& faces, bool closed = true); + + /** + * @brief 添加体 + * @param shells 壳 ID 列表 + * @param name 体名称 + * @return 新体的 ID + */ int add_body(const std::vector& shells, const std::string& name = ""); + + /** + * @brief 添加 NURBS 曲面 + * @param surf NURBS 曲面定义 + * @return 新曲面的 ID + */ int add_surface(const curves::NurbsSurface& surf); + // ── 计数查询 ── + + /** @brief 顶点数量 */ [[nodiscard]] size_t num_vertices() const { return vertices_.size(); } + + /** @brief 边数量 */ [[nodiscard]] size_t num_edges() const { return edges_.size(); } + + /** @brief 面数量 */ [[nodiscard]] size_t num_faces() const { return faces_.size(); } + + /** @brief 体数量 */ [[nodiscard]] size_t num_bodies() const { return bodies_.size(); } - // Look up vertex by array index (faster, for sequential access) + /** @brief 曲面数量 */ + [[nodiscard]] size_t num_surfaces() const { return surfaces_.size(); } + + // ── 元素访问(按数组索引 — 更快,适合顺序遍历) ── + + /** @brief 按数组索引访问顶点 */ [[nodiscard]] const TopoVertex& vertex(int id) const { return vertices_[id]; } - // Look up vertex by unique ID (correct for ID-based references from edge data) + + /** @brief 按唯一 ID 访问顶点(适用于边数据中的 ID 引用) */ [[nodiscard]] const TopoVertex& vertex_by_id(int id) const; + + /** @brief 按数组索引访问边 */ [[nodiscard]] const TopoEdge& edge(int id) const { return edges_[id]; } + + /** @brief 按数组索引访问面 */ [[nodiscard]] const TopoFace& face(int id) const { return faces_[id]; } + + /** @brief 按数组索引访问曲面 */ [[nodiscard]] const curves::NurbsSurface& surface(int id) const { return surfaces_[id]; } + /** @brief 按唯一 ID 访问环 */ + [[nodiscard]] const TopoLoop& loop_by_id(int id) const; + + /** @brief 获取所有环的只读引用 */ + [[nodiscard]] const std::vector& all_loops() const { return loops_; } + + // ── 几何/拓扑查询 ── + + /** + * @brief 计算模型的轴对齐包围盒 + * + * 遍历所有顶点,取最小/最大坐标。 + * + * @return 包围盒(AABB) + */ [[nodiscard]] core::AABB3D bounds() const; + + /** + * @brief 检查模型有效性(快速版本) + * + * 进行基本检查:是否有顶点/边/面,ID 引用是否有效。 + * 完整验证请使用 brep_validate.h 中的 validate()。 + * + * @return true 如果模型通过基本检查 + * + * @see validate() 完整验证(水密性、方向一致性等) + */ [[nodiscard]] bool is_valid() const; + + /** + * @brief 将 B-Rep 模型 tessellate 为三角网格 + * + * 所有曲面和曲线以给定的弦高误差 tessellate 为三角形。 + * + * @param deflection 弦高误差(控制 tessellation 精度,默认 0.01) + * @return HalfedgeMesh 三角网格 + * + * @note deflection 越小网格越精细,但三角形数量显著增加。 + */ [[nodiscard]] mesh::HalfedgeMesh to_mesh(double deflection = 0.01) const; - [[nodiscard]] std::vector face_edges(int face_id) const; - [[nodiscard]] std::vector edge_faces(int edge_id) const; - [[nodiscard]] std::vector vertex_edges(int vertex_id) const; + // ── 邻接关系查询 ── - [[nodiscard]] size_t num_surfaces() const { return surfaces_.size(); } - [[nodiscard]] const TopoLoop& loop_by_id(int id) const; - [[nodiscard]] const std::vector& all_loops() const { return loops_; } + /** + * @brief 获取面的所有边 + * + * 展开面的所有环,收集其中包含的边 ID。 + * + * @param face_id 面 ID + * @return 边 ID 列表(去重) + */ + [[nodiscard]] std::vector face_edges(int face_id) const; + + /** + * @brief 获取边的所有相邻面 + * + * 反向查找:哪些面包含此边。 + * 在流形网格中每条边应有恰好 2 个相邻面。 + * + * @param edge_id 边 ID + * @return 相邻面 ID 列表 + */ + [[nodiscard]] std::vector edge_faces(int edge_id) const; + + /** + * @brief 获取顶点的所有相邻边 + * + * @param vertex_id 顶点 ID + * @return 相邻边 ID 列表 + */ + [[nodiscard]] std::vector vertex_edges(int vertex_id) const; private: std::vector vertices_; diff --git a/include/vde/brep/brep_boolean.h b/include/vde/brep/brep_boolean.h index 9074d4f..0417c40 100644 --- a/include/vde/brep/brep_boolean.h +++ b/include/vde/brep/brep_boolean.h @@ -1,15 +1,105 @@ #pragma once +/** + * @file brep_boolean.h + * @brief B-Rep 级别的布尔运算 + * + * 对 BrepModel 实体执行精确的布尔运算,区别于基于网格的布尔运算 + * (后者通过 mesh_boolean.h 提供)。 + * + * ## B-Rep 布尔运算 vs 网格布尔运算 + * + * | 特性 | B-Rep 布尔 | 网格布尔 | + * |-------------------|------------------------|-------------------------| + * | 精度 | 精确(解析曲面) | 近似(离散三角形) | + * | 速度 | 较慢(需要曲面交线计算) | 较快 | + * | 输出 | B-Rep 模型 | 三角网格 | + * | 适用场景 | CAD 精确建模 | 实时/渲染/快速原型 | + * + * ## 典型工作流 + * + * 1. 通过 modeling.h 创建基本体(如 make_box + make_cylinder) + * 2. 使用本文件的布尔运算组合它们 + * 3. 通过 to_mesh() 或 export_step() 导出结果 + * + * @ingroup brep + */ #include "vde/brep/brep.h" namespace vde::brep { -/// B-Rep level boolean union: A ∪ B +/** + * @brief B-Rep 布尔并集: A ∪ B + * + * 合并两个 B-Rep 实体为一个,自动处理相交曲面的分割和重新连接。 + * + * **算法概要:** + * 1. 计算两个模型之间的所有曲面交线 + * 2. 沿交线分割曲面 + * 3. 根据布尔操作类型保留或丢弃分割后的面片 + * 4. 重新连接拓扑结构(边、环、壳) + * + * @param a 第一个 B-Rep 实体 + * @param b 第二个 B-Rep 实体 + * @return 新的 B-Rep 实体(a ∪ b) + * + * @warning 两个输入实体必须都是水密(watertight)且非自交(non-self-intersecting)。 + * 如果任一输入未通过验证,结果未定义。 + * + * @code{.cpp} + * auto box = make_box(2, 2, 2); + * auto sphere = make_sphere(1.5); + * auto result = brep_union(box, sphere); + * @endcode + * + * @see brep_intersection 交集 + * @see brep_difference 差集 + */ [[nodiscard]] BrepModel brep_union(const BrepModel& a, const BrepModel& b); -/// B-Rep level boolean intersection: A ∩ B +/** + * @brief B-Rep 布尔交集: A ∩ B + * + * 保留两个实体共有的区域。 + * + * @param a 第一个 B-Rep 实体 + * @param b 第二个 B-Rep 实体 + * @return 新的 B-Rep 实体(a ∩ b) + * + * @pre a 和 b 必须是有效的封闭实体 + * + * @code{.cpp} + * // 长方体与球体的交集 = 八分之一球(角部) + * auto box = make_box(2, 2, 2); + * auto sphere = make_sphere(1.8); + * auto result = brep_intersection(box, sphere); + * @endcode + * + * @see brep_union 并集 + * @see brep_difference 差集 + */ [[nodiscard]] BrepModel brep_intersection(const BrepModel& a, const BrepModel& b); -/// B-Rep level boolean difference: A \ B +/** + * @brief B-Rep 布尔差集: A \ B + * + * 从 A 中减去 B 所占据的区域。 + * + * @param a 被减实体 A + * @param b 减去实体 B + * @return 新的 B-Rep 实体(a \ b) + * + * @pre a 和 b 必须是有效的封闭实体 + * + * @code{.cpp} + * // 从盒子中减去球体 = 带球形凹陷的盒子 + * auto box = make_box(2, 2, 2); + * auto sphere = make_sphere(0.5); + * auto result = brep_difference(box, sphere); + * @endcode + * + * @see brep_union 并集 + * @see brep_intersection 交集 + */ [[nodiscard]] BrepModel brep_difference(const BrepModel& a, const BrepModel& b); } // namespace vde::brep diff --git a/include/vde/brep/brep_validate.h b/include/vde/brep/brep_validate.h index 6a4b8f3..f32194f 100644 --- a/include/vde/brep/brep_validate.h +++ b/include/vde/brep/brep_validate.h @@ -1,4 +1,24 @@ #pragma once +/** + * @file brep_validate.h + * @brief B-Rep 模型综合验证 + * + * 对 BrepModel 进行完整性、水密性和拓扑一致性检查。 + * 在生产环境中,验证应在布尔运算、文件导入/导出前后执行。 + * + * ## 验证项 + * + * | 检查项 | 字段 | 通过标准 | + * |--------------------------|-------------------------|-------------------| + * | 水密性 (watertightness) | watertightness | 1.0 (每条边恰好 2 个面) | + * | 无自交 | self_intersection_free | 1.0 | + * | 流形性 | non_manifold_edges | 0 | + * | 无悬挂边 | dangling_edges | 0 | + * | 方向一致性 | inconsistent_orientations | 0 | + * | 边的尺寸 | min/max_edge_length | min > 容差 | + * + * @ingroup brep + */ #include "vde/brep/brep.h" #include #include @@ -6,30 +26,87 @@ namespace vde::brep { -/// Comprehensive validation result for B-Rep models. +/** + * @brief B-Rep 模型的综合验证结果 + * + * 包含所有检查项的详细报告,用于诊断和修复几何问题。 + * + * ## 结果解读 + * + * - `valid == true`: 模型通过所有严格检查,可用于布尔运算和生产用途 + * - `valid == false`: 存在一个或多个问题,参见 errors 列表了解详情 + * - `warnings` 中的内容不影响 valid 判定,但值得关注(如边过长等) + */ struct ValidationResult { + /** @brief 是否通过所有必要检查 */ bool valid = true; + + /** @brief 错误信息列表(导致 valid = false) */ std::vector errors; + + /** @brief 警告信息列表(不影响 valid) */ std::vector warnings; - /// Fraction of edges with exactly 2 adjacent faces (1.0 = perfect) + /** @brief 水密性比率:恰好有 2 个相邻面的边的比例(1.0 = 完美水密) */ double watertightness = 1.0; - /// Fraction of face pairs that don't intersect except at shared edges + /** @brief 无自交比率:不与其他面内部相交的面对比例(1.0 = 无自交) */ double self_intersection_free = 1.0; + /** @brief 最短边的长度(用于检测退化边) */ double min_edge_length = std::numeric_limits::max(); + + /** @brief 最长边的长度(用于检测超长边) */ double max_edge_length = 0.0; + + /** @brief 非流形边数(相邻面 ≠ 2 的边) */ size_t non_manifold_edges = 0; + + /** @brief 悬挂边数(无相邻面的边) */ size_t dangling_edges = 0; + + /** @brief 方向不一致的面数 */ size_t inconsistent_orientations = 0; + + /** @brief 总边数 */ size_t total_edges = 0; + + /** @brief 总面数 */ size_t total_faces = 0; }; -/// Perform comprehensive validation on a BrepModel. -/// Checks watertightness, orientation consistency, self-intersection, -/// edge length bounds, Euler characteristic, and closed shells. +/** + * @brief 对 BrepModel 执行综合验证 + * + * 检查以下项: + * + * 1. **基本完整性**: 顶点/边/面计数 > 0,ID 引用有效 + * 2. **水密性**: 每条边恰好有 2 个相邻面,构成封闭流形 + * 3. **方向一致性**: 相邻面的法向量方向一致(没有翻转的面) + * 4. **自交检测**: 面片内部不相交(共享边除外) + * 5. **边尺寸**: 无退化边(太短)或异常长边 + * 6. **壳闭合性**: 每个标记为 closed 的壳在几何上确实封闭 + * + * @param body 待验证的 B-Rep 模型 + * @return ValidationResult 包含所有检查结果的详细报告 + * + * @note 自交检测是最昂贵的检查项(O(n²) 面对数), + * 对于大模型(> 1000 面)可能需要较长时间。 + * + * @code{.cpp} + * auto result = validate(my_model); + * if (!result.valid) { + * for (auto& err : result.errors) { + * std::cerr << "Error: " << err << std::endl; + * } + * } + * if (result.watertightness < 0.99) { + * std::cerr << "Model is not watertight!" << std::endl; + * } + * @endcode + * + * @see BrepModel::is_valid() 快速有效性检查(无详细报告) + */ [[nodiscard]] ValidationResult validate(const BrepModel& body); } // namespace vde::brep diff --git a/include/vde/brep/iges_export.h b/include/vde/brep/iges_export.h index bc488a9..bd1f4b6 100644 --- a/include/vde/brep/iges_export.h +++ b/include/vde/brep/iges_export.h @@ -1,27 +1,87 @@ #pragma once +/** + * @file iges_export.h + * @brief IGES 文件导出(版本 5.3) + * + * 将 B-Rep 模型序列化为 IGES 格式。IGES 5.3 是广泛兼容的版本, + * 支持 B-Rep 拓扑结构和常见解析曲面类型。 + * + * ## 输出格式 + * + * 产生的 IGES 文件遵循 ANSI Y14.26M 标准,使用固定宽度 80 字符记录格式。 + * 兼容于大多数 CAD 系统的 IGES 导入器。 + * + * ## 输出的实体类型 + * + * | 类型号 | 实体名 | 说明 | + * |--------|----------------------------|---------------------| + * | 116 | Point | 三维点 | + * | 110 | Line | 直线段 | + * | 100 | Circular Arc | 圆弧 | + * | 126 | Rational B-Spline Curve | 有理 B 样条曲线 | + * | 108 | Plane | 平面 | + * | 128 | Rational B-Spline Surface | 有理 B 样条曲面 | + * | 502 | Vertex | B-Rep 顶点 | + * | 504 | Edge | B-Rep 边 | + * | 508 | Loop | B-Rep 环 | + * | 510 | Face | B-Rep 面 | + * | 514 | Shell | B-Rep 壳 | + * | 186 | Manifold Solid B-Rep Object | B-Rep 实体 | + * + * @ingroup brep + */ #include "vde/brep/brep.h" #include #include namespace vde::brep { -/// Export B-Rep bodies to IGES format string (version 5.3) -/// Supports common entity types for maximum compatibility: -/// - Type 116: Point -/// - Type 110: Line -/// - Type 100: Circular Arc -/// - Type 126: Rational B-Spline Curve -/// - Type 108: Plane -/// - Type 128: Rational B-Spline Surface -/// - Type 502: Vertex (B-Rep topology) -/// - Type 504: Edge -/// - Type 508: Loop -/// - Type 510: Face -/// - Type 514: Shell -/// - Type 186: Manifold Solid B-Rep Object +/** + * @brief 将 B-Rep 体导出为 IGES 格式字符串(版本 5.3) + * + * 序列化所有传入的体及其几何/拓扑数据为 IGES 固定宽度格式。 + * + * **输出结构:** + * ``` + * S 1H,,1H;, ... S0000001 + * G ... G0000003 + * D 116,1,2,... (Point entity) D0000001 + * D 110,3,4,... (Line entity) D0000002 + * ... ... + * P 116,0.,0.,0.; 1P0000001 + * P 110,0.,0.,0.,1.,0.,0.; 3P0000002 + * T 1 T0000001 + * ``` + * + * ## 实体输出策略 + * + * 1. **几何实体**(点/线/弧/曲线/面/曲面)先输出并使 Directory 条目索引递增 + * 2. **拓扑实体**(顶点/边/环/面/壳/B-Rep Object)随后输出,通过索引引用于几何实体 + * 3. 如果体有 name,作为 B-Rep Object 的属性输出 + * + * @param bodies B-Rep 体列表 + * @return IGES 5.3 格式字符串 + * + * @code{.cpp} + * auto body = make_box(10, 5, 3); + * std::string iges_str = export_iges({body}); + * @endcode + * + * @see export_iges_file 直接写入文件 + * @see export_step STEP 格式导出 + */ [[nodiscard]] std::string export_iges(const std::vector& bodies); -/// Export to IGES file +/** + * @brief 将 B-Rep 体导出为 IGES 文件 + * + * 调用 export_iges() 后写入文件。 + * + * @param filepath 输出文件路径(建议扩展名 .igs 或 .iges) + * @param bodies B-Rep 体列表 + * + * @see export_iges 获取字符串输出 + */ void export_iges_file(const std::string& filepath, const std::vector& bodies); } // namespace vde::brep diff --git a/include/vde/brep/iges_import.h b/include/vde/brep/iges_import.h index 4e12d3e..6008ae8 100644 --- a/include/vde/brep/iges_import.h +++ b/include/vde/brep/iges_import.h @@ -1,33 +1,108 @@ #pragma once +/** + * @file iges_import.h + * @brief IGES 文件导入(ANSI Y14.26M) + * + * 解析 IGES 格式文件(.igs / .iges)并转换为 B-Rep 模型。 + * + * ## IGES 格式概述 + * + * IGES (Initial Graphics Exchange Specification) 是一种旧式但广泛使用的 CAD 数据交换格式。 + * 采用 80 字符固定宽度记录格式,分为 5 个区域: + * + * - **S (Start)**: 自由格式注释 + * - **G (Global)**: 参数分隔符、单位、精度等全局设置 + * - **D (Directory)**: 实体目录(类型、线型、颜色等属性) + * - **P (Parameter)**: 实体参数数据 + * - **T (Terminate)**: 文件结束标记 + * + * ## 支持的实体类型 + * + * 主要支持几何和拓扑实体: + * - Type 100: Circular Arc + * - Type 110: Line + * - Type 116: Point + * - Type 126: Rational B-Spline Curve + * - Type 128: Rational B-Spline Surface + * - Type 108: Plane + * - Type 186: Manifold Solid B-Rep Object + * - Type 502/504/508/510/514: B-Rep 拓扑(顶点/边/环/面/壳) + * + * @ingroup brep + */ #include "vde/brep/brep.h" #include #include namespace vde::brep { -/// IGES import error codes +/** + * @brief IGES 导入错误码 + */ enum class IgesError { - Ok = 0, - FileNotFound, - ParseError, - InvalidSection, - UnsupportedEntity, - MissingEntity, - EntityTypeError, + Ok = 0, ///< 导入成功 + FileNotFound, ///< 文件不存在或无法读取 + ParseError, ///< IGES 语法错误(固定宽度格式异常) + InvalidSection, ///< 段标记无效或缺失 + UnsupportedEntity, ///< 遇到了不支持但合法的实体类型 + MissingEntity, ///< 引用了未定义的实体 + EntityTypeError, ///< 实体类型解析失败 }; -/// Parse an IGES file (.igs/.iges) and return B-Rep bodies -/// IGES format: 80-character fixed-width records (ANSI Y14.26M) -/// Supports the most common entity types found in CAD files +/** + * @brief 从文件导入 IGES + * + * 解析 .igs 或 .iges 文件并返回其中的 B-Rep 体。 + * + * **算法概要:** + * 1. 读取文件并按 80 字符行分割 + * 2. 解析 Start, Global, Directory, Parameter 各段 + * 3. 根据 Directory Entry 识别实体类型和参数位置 + * 4. 构建 B-Rep 拓扑结构(B-Rep Object 或独立几何实体) + * + * @param filepath .igs 或 .iges 文件路径 + * @return B-Rep 体列表 + * + * @note IGES 格式较为宽松,不同 CAD 系统的实现差异很大。 + * 对于非常规实体或自定义类型,可能会返回 IgesError::UnsupportedEntity。 + * + * @code{.cpp} + * auto bodies = import_iges("part.igs"); + * for (auto& body : bodies) { + * auto mesh = body.to_mesh(0.01); + * // 处理 mesh + * } + * @endcode + * + * @see import_iges_from_string 从字符串导入 + * @see iges_last_error 获取错误码 + */ [[nodiscard]] std::vector import_iges(const std::string& filepath); -/// Parse IGES data from a string (for testing) +/** + * @brief 从字符串导入 IGES(用于测试) + * + * @param data IGES 文件完整内容(字符串形式) + * @return B-Rep 体列表 + * + * @see import_iges 从文件导入 + */ [[nodiscard]] std::vector import_iges_from_string(const std::string& data); -/// Get the last error from import_iges / import_iges_from_string +/** + * @brief 获取最近一次 IGES 导入的错误码 + * @return IgesError 枚举值 + * + * @see iges_last_error_message 获取可读描述 + */ [[nodiscard]] IgesError iges_last_error(); -/// Get a human-readable description of the last error +/** + * @brief 获取最近一次 IGES 导入的可读错误描述 + * @return 错误描述字符串 + * + * @see iges_last_error 获取枚举错误码 + */ [[nodiscard]] const std::string& iges_last_error_message(); } // namespace vde::brep diff --git a/include/vde/brep/modeling.h b/include/vde/brep/modeling.h index db834f0..fdad300 100644 --- a/include/vde/brep/modeling.h +++ b/include/vde/brep/modeling.h @@ -1,4 +1,32 @@ #pragma once +/** + * @file modeling.h + * @brief 高层 B-Rep 建模 API + * + * 提供 B-Rep 实体的创建和编辑操作,类比于 CAD 系统的特征建模功能。 + * + * ## 功能概览 + * + * | 类别 | 操作 | + * |----------------|---------------------------------------------| + * | 基本体 | make_box, make_cylinder, make_sphere | + * | 扫掠特征 | extrude, revolve, sweep, loft | + * | 编辑操作 | fillet, chamfer, shell | + * + * ## 使用模式 + * + * 每个函数返回一个新的 BrepModel(值语义),适合链式构建: + * + * @code{.cpp} + * auto body = make_box(10, 5, 3); + * // 或者更复杂的构建: + * auto profile = /* NURBS 曲线 */; + * auto swept = extrude(profile, Vector3D(0, 0, 10)); + * auto rounded = fillet(swept, 0, 2.0); + * @endcode + * + * @ingroup brep + */ #include "vde/core/point.h" #include "vde/brep/brep.h" @@ -7,46 +35,233 @@ using core::Point3D; using core::Vector3D; using core::AABB3D; -/// Extrude a planar face/wire along a direction -/// @param profile Profile curve (must be planar) -/// @param dir Extrusion direction (length = distance) +// ═══════════════════════════════════════════════ +// 扫掠特征 +// ═══════════════════════════════════════════════ + +/** + * @brief 沿方向挤出平面轮廓 + * + * 将平面曲线沿给定方向扫掠,生成柱状实体。 + * + * **算法:** 曲线的每个点沿方向向量平移,两端加盖平面形成封闭实体。 + * + * @param profile 轮廓曲线(必须位于一个平面内) + * @param dir 挤出方向向量(其长度决定挤出距离) + * @return 挤出后的 B-Rep 实体 + * + * @pre profile 必须是平面曲线(存在于某个平面内)且闭合 + * + * @code{.cpp} + * // 沿 Z 轴挤出 5 个单位 + * auto circle = NurbsCurve::create_circle(Point3D(0,0,0), 2.0); + * auto cylinder = extrude(circle, Vector3D(0, 0, 5)); + * @endcode + * + * @see revolve 旋转扫掠 + * @see sweep 沿任意路径扫掠 + */ [[nodiscard]] BrepModel extrude(const curves::NurbsCurve& profile, const Vector3D& dir); -/// Revolve a profile around an axis +/** + * @brief 绕轴旋转平面轮廓 + * + * 将轮廓绕给定轴旋转,生成旋转体(如轮子、瓶子等)。 + * + * @param profile 轮廓曲线(通常位于轴的半平面中) + * @param axis_origin 旋转轴上的一个点 + * @param axis_dir 旋转轴方向(单位向量) + * @param angle_rad 旋转角度(弧度,默认 2π = 完整的旋转体) + * @return 旋转后的 B-Rep 实体 + * + * @pre profile 必须在轴的同一侧(不应跨越轴线) + * + * @code{.cpp} + * // 绕 Y 轴旋转半圆,生成球体的一半 + * NurbsCurve semicircle = /* 在 XZ 平面中的半圆弧 */; + * auto half_sphere = revolve(semicircle, Point3D(0,0,0), + * Vector3D(0,1,0), M_PI); + * @endcode + * + * @see extrude 线性挤出 + * @see make_sphere 直接创建球体 + */ [[nodiscard]] BrepModel revolve(const curves::NurbsCurve& profile, const Point3D& axis_origin, const Vector3D& axis_dir, double angle_rad = 2.0 * M_PI); -/// Sweep a profile along a path curve +/** + * @brief 沿任意路径扫掠轮廓 + * + * 轮廓沿一条空间路径曲线运动,生成管道或异形截面杆。 + * + * @param profile 截面轮廓(需位于路径起点的垂直平面内) + * @param path 扫掠路径曲线 + * @return 扫掠后的 B-Rep 实体 + * + * @note 路径曲率过大处可能出现自交,需要控制路径的光滑性。 + * + * @code{.cpp"} + * auto circle = NurbsCurve::create_circle(Point3D(0,0,0), 0.5); + * auto helix = NurbsCurve::create_helix(/* ... */); + * auto pipe = sweep(circle, helix); + * @endcode + * + * @see extrude 直线挤出 + * @see revolve 旋转扫掠 + */ [[nodiscard]] BrepModel sweep(const curves::NurbsCurve& profile, const curves::NurbsCurve& path); -/// Loft between two or more profile curves +/** + * @brief 在两个或多个轮廓曲线之间放样 + * + * 通过线性或平滑插值连接多个平行或非平行轮廓, + * 生成过渡实体。 + * + * @param profiles 轮廓曲线列表(至少 2 条) + * @return 放样后的 B-Rep 实体 + * + * @pre profiles.size() ≥ 2 + * @pre 所有轮廓应是平面曲线(逐一) + * + * @code{.cpp} + * auto profile_a = NurbsCurve::create_circle(Point3D(0,0,0), 5.0); + * auto profile_b = NurbsCurve::create_circle(Point3D(0,0,10), 2.0); + * auto cone = loft({profile_a, profile_b}); + * @endcode + * + * @see extrude 单轮廓挤出 + */ [[nodiscard]] BrepModel loft(const std::vector& profiles); -/// Create a solid box +// ═══════════════════════════════════════════════ +// 基本体 +// ═══════════════════════════════════════════════ + +/** + * @brief 创建立方体 + * + * 以原点为中心、轴对齐的立方体。 + * + * @param w 宽度(X 方向),必须 > 0 + * @param h 高度(Y 方向),必须 > 0 + * @param d 深度(Z 方向),必须 > 0 + * @return 实心立方体 B-Rep + * + * @code{.cpp} + * auto box = make_box(10, 5, 3); // 10×5×3 的盒子 + * @endcode + * + * @see make_cylinder 圆柱体 + * @see make_sphere 球体 + */ [[nodiscard]] BrepModel make_box(double w, double h, double d); -/// Create a solid cylinder +/** + * @brief 创建圆柱体 + * + * 沿 Y 轴、以原点为中心的圆柱体。 + * + * @param radius 截面半径,必须 > 0 + * @param height 总高度,沿 Y 轴从 -h/2 到 +h/2 + * @param segments 截面分段数(边数,默认 32) + * @return 实心圆柱体 B-Rep + * + * @note segments 越大圆柱越光滑,但面和边数增加。 + * + * @code{.cpp} + * auto cyl = make_cylinder(2.0, 10.0, 64); // 高精度圆柱 + * @endcode + * + * @see make_box 立方体 + * @see make_sphere 球体 + */ [[nodiscard]] BrepModel make_cylinder(double radius, double height, int segments = 32); -/// Create a solid sphere +/** + * @brief 创建球体 + * + * 以原点为中心的球体。 + * + * @param radius 球半径,必须 > 0 + * @param segments_u 经度方向分段数(默认 32) + * @param segments_v 纬度方向分段数(默认 16) + * @return 实心球体 B-Rep + * + * @code{.cpp} + * auto sphere = make_sphere(5.0, 32, 32); + * @endcode + * + * @see make_cylinder 圆柱体 + */ [[nodiscard]] BrepModel make_sphere(double radius, int segments_u = 32, int segments_v = 16); -/// Fillet an edge (constant radius) -/// @param body Input body -/// @param edge_id Edge to fillet -/// @param radius Fillet radius +// ═══════════════════════════════════════════════ +// 编辑操作 +// ═══════════════════════════════════════════════ + +/** + * @brief 对边做圆角处理 + * + * 在指定的边上施加恒定半径的圆角(倒圆)。 + * 适用于凹边和凸边。 + * + * @param body 输入实体 + * @param edge_id 要倒圆的边 ID + * @param radius 圆角半径,必须 > 0 + * @return 圆角后的新实体 + * + * @note 圆角半径不能超过相邻面的最小宽度,否则会产生自交。 + * @note 只支持恒定半径圆角。变半径圆角尚未实现。 + * + * @code{.cpp} + * auto box = make_box(10, 5, 3); + * auto rounded = fillet(box, 0, 2.0); // 对边 0 做半径 2 的圆角 + * @endcode + * + * @see chamfer 倒角 + * @see shell 抽壳 + */ [[nodiscard]] BrepModel fillet(const BrepModel& body, int edge_id, double radius); -/// Chamfer an edge +/** + * @brief 对边做倒角 + * + * 将尖锐边替换为 45° 斜面。 + * + * @param body 输入实体 + * @param edge_id 要倒角的边 ID + * @param distance 倒角距离(从原边沿两个面各偏移的距离) + * @return 倒角后的新实体 + * + * @pre distance > 0 + * + * @see fillet 圆角 + */ [[nodiscard]] BrepModel chamfer(const BrepModel& body, int edge_id, double distance); -/// Shell a solid body (hollow it out) -/// @param body Input solid body -/// @param face_id Face to remove (opening), -1 for closed shell -/// @param thickness Wall thickness (positive = outward offset) +/** + * @brief 抽壳(挖空实体) + * + * 将实体变为等厚度的壳,去除指定面形成开口。 + * + * @param body 输入实心实体 + * @param face_id 要移除的面 ID(形成开口),-1 表示封闭壳(不开口) + * @param thickness 壁厚(正值 = 向外偏移,负值 = 向内偏移) + * @return 抽壳后的新实体 + * + * @note 壁厚不应超过实体最小尺寸的一半。 + * @note 当 face_id = -1 时,结果是一个封闭的壳(中空但没有开口)。 + * + * @code{.cpp"} + * auto box = make_box(10, 5, 3); + * auto shelled = shell(box, 0, 0.5); // 移除面 0,壁厚 0.5 + * @endcode + * + * @see fillet 圆角 + */ [[nodiscard]] BrepModel shell(const BrepModel& body, int face_id, double thickness); } // namespace vde::brep diff --git a/include/vde/brep/step_export.h b/include/vde/brep/step_export.h index 0a8d03a..418865b 100644 --- a/include/vde/brep/step_export.h +++ b/include/vde/brep/step_export.h @@ -1,15 +1,77 @@ #pragma once +/** + * @file step_export.h + * @brief STEP 文件导出(ISO 10303-214) + * + * 将 B-Rep 模型序列化为 STEP AP214 格式。 + * + * ## 输出格式 + * + * 生成的 STEP 文件符合 ISO 10303-214 (AP214) 标准, + * 可以在大多数商用 CAD 软件(SolidWorks, CATIA, NX, FreeCAD 等)中打开。 + * + * ## 支持的几何类型 + * + * - **曲线**: LINE, CIRCLE, B_SPLINE_CURVE_WITH_KNOTS + * - **曲面**: PLANE, CYLINDRICAL_SURFACE, B_SPLINE_SURFACE_WITH_KNOTS + * - **拓扑**: CLOSED_SHELL, ADVANCED_FACE, EDGE_LOOP, ORIENTED_EDGE, VERTEX_POINT + * - **实体**: MANIFOLD_SOLID_BREP, PRODUCT, PRODUCT_DEFINITION + * + * @ingroup brep + */ #include "vde/brep/brep.h" #include #include namespace vde::brep { -/// Export bodies to STEP AP214 (ISO 10303-214) format string. -/// Handles planes, cylinders, B-spline surfaces, lines, circles, and B-spline curves. +/** + * @brief 将 B-Rep 体导出为 STEP AP214 字符串 + * + * 序列化所有传入的体及其几何数据为 STEP 文本格式。 + * + * **输出结构:** + * ``` + * ISO-10303-21; + * HEADER; + * FILE_DESCRIPTION(...) + * FILE_NAME(...) + * FILE_SCHEMA(('AUTOMOTIVE_DESIGN')) + * ENDSEC; + * DATA; + * #1 = PRODUCT(...) + * #2 = CARTESIAN_POINT(...) + * ... + * ENDSEC; + * END-ISO-10303-21; + * ``` + * + * @param bodies B-Rep 体列表(每个体对应一个 PRODUCT) + * @return STEP AP214 格式字符串 + * + * @note 输出包含所有必要的 ISO 10303-21 头部信息。 + * + * @code{.cpp} + * auto body = make_box(10, 5, 3); + * std::string step_str = export_step({body}); + * // 写入文件或通过网络发送 + * @endcode + * + * @see export_step_file 直接写入文件 + * @see export_iges IGES 格式导出 + */ [[nodiscard]] std::string export_step(const std::vector& bodies); -/// Export and write to file (AP214 compliant). +/** + * @brief 将 B-Rep 体导出为 STEP 文件 + * + * 便捷函数:调用 export_step() 后写入文件。 + * + * @param filepath 输出文件路径(建议扩展名 .step 或 .stp) + * @param bodies B-Rep 体列表 + * + * @see export_step 获取字符串输出 + */ void export_step_file(const std::string& filepath, const std::vector& bodies); } // namespace vde::brep diff --git a/include/vde/brep/step_import.h b/include/vde/brep/step_import.h index 39d0d7f..78e7054 100644 --- a/include/vde/brep/step_import.h +++ b/include/vde/brep/step_import.h @@ -1,33 +1,128 @@ #pragma once +/** + * @file step_import.h + * @brief STEP 文件导入(ISO 10303) + * + * 解析 STEP AP203/AP214 格式文件并转换为 B-Rep 模型。 + * + * ## STEP 格式概述 + * + * STEP (STandard for the Exchange of Product model data) 是 ISO 10303 标准 + * 定义的 CAD 数据交换格式。AP203 和 AP214 是最常用子集,支持: + * - 实体和曲面几何体 + * - 产品结构和装配关系 + * - 材料、颜色等元数据 + * + * ## 支持的实体类型 + * + * - **曲线**: LINE, CIRCLE, B_SPLINE_CURVE_WITH_KNOTS + * - **曲面**: PLANE, CYLINDRICAL_SURFACE, B_SPLINE_SURFACE_WITH_KNOTS + * - **拓扑**: CLOSED_SHELL, ADVANCED_FACE, FACE_OUTER_BOUND, EDGE_LOOP, ORIENTED_EDGE, VERTEX_POINT + * + * ## 错误处理 + * + * 使用 step_last_error() 和 step_last_error_message() 检查最近一次导入的失败原因。 + * + * @ingroup brep + */ #include "vde/brep/brep.h" #include #include namespace vde::brep { -/// STEP import error codes +/** + * @brief STEP 导入错误码 + * + * 枚举 import_step() 和 import_step_from_string() 可能返回的所有错误类型。 + */ enum class StepError { - Ok = 0, - FileNotFound, - ParseError, - InvalidHeader, - UnsupportedSchema, - MissingEntity, - EntityTypeError, + Ok = 0, ///< 导入成功 + FileNotFound, ///< 文件不存在或无法读取 + ParseError, ///< STEP 语法错误(令牌/格式异常) + InvalidHeader, ///< HEADER 段格式无效 + UnsupportedSchema, ///< 不支持的 SCHEMA(非 AP203/AP214) + MissingEntity, ///< 引用了未定义的实体 + EntityTypeError, ///< 实体类型未识别或类型不匹配 }; -/// Parse a STEP file (AP203/AP214) and return B-Rep bodies -/// @param filepath Path to .step/.stp file -/// @return Vector of BrepModel bodies (one per PRODUCT in the file) +/** + * @brief 从文件导入 STEP(AP203/AP214) + * + * 解析 .step / .stp 文件并提取所有 PRODUCT 对应的 B-Rep 体。 + * 每个 PRODUCT 定义对应返回向量的一个元素。 + * + * **算法概要:** + * 1. 读取文件并分割为 HEADER, DATA 段 + * 2. 解析 DATA 段中的实体定义(#ID = TYPE(params);) + * 3. 构建实体依赖图并按拓扑层次重建模型 + * 4. 将每个 CLOSED_SHELL 转换为 BrepModel + * + * @param filepath .step 或 .stp 文件路径 + * @return B-Rep 体列表(每个文件中的 PRODUCT 一个) + * + * @note 大型 STEP 文件(> 100 MB)可能需要数秒到数十秒进行解析。 + * + * @code{.cpp"} + * auto bodies = import_step("assembly.step"); + * for (auto& body : bodies) { + * auto mesh = body.to_mesh(); + * // 渲染或处理 mesh + * } + * @endcode + * + * @see import_step_from_string 从内存字符串导入(测试用) + * @see step_last_error 获取错误码 + */ [[nodiscard]] std::vector import_step(const std::string& filepath); -/// Parse STEP data from a string (for testing) +/** + * @brief 从字符串导入 STEP(用于测试和嵌入式场景) + * + * 与 import_step() 功能完全相同,但从内存中的 STEP 数据字符串解析。 + * + * @param step_data 完整的 STEP 文件内容(字符串形式) + * @return B-Rep 体列表 + * + * @code{.cpp} + * std::string step_str = R"( + * ISO-10303-21; + * HEADER; ... ENDSEC; + * DATA; ... ENDSEC; + * END-ISO-10303-21; + * )"; + * auto bodies = import_step_from_string(step_str); + * @endcode + * + * @see import_step 从文件导入 + */ [[nodiscard]] std::vector import_step_from_string(const std::string& step_data); -/// Get the last error from import_step / import_step_from_string +/** + * @brief 获取最近一次 STEP 导入的错误码 + * + * 线程安全的:返回当前线程最近一次导入的错误。 + * + * @return StepError 枚举值 + * + * @see step_last_error_message 获取可读的错误描述 + */ [[nodiscard]] StepError step_last_error(); -/// Get a human-readable description of the last error +/** + * @brief 获取最近一次 STEP 导入的可读错误描述 + * + * @return 错误描述字符串(如 "Missing entity reference #42") + * + * @code{.cpp} + * auto bodies = import_step("nonexistent.step"); + * if (bodies.empty()) { + * std::cerr << "Import failed: " << step_last_error_message() << std::endl; + * } + * @endcode + * + * @see step_last_error 获取枚举错误码 + */ [[nodiscard]] const std::string& step_last_error_message(); } // namespace vde::brep diff --git a/include/vde/capi/vde_capi.h b/include/vde/capi/vde_capi.h new file mode 100644 index 0000000..e7a2533 --- /dev/null +++ b/include/vde/capi/vde_capi.h @@ -0,0 +1,640 @@ +#pragma once +/** + * @file vde_capi.h + * @brief ViewDesignEngine C API — 跨语言边界接口 + * + * 提供稳定的 C ABI 接口,使 Python、Rust、C# 等语言可以通过 FFI 调用 + * ViewDesignEngine 的核心功能,无需依赖 C++ ABI。 + * + * ## 设计原则 + * + * 1. **不透明句柄**: 所有内部对象(网格、曲线、求解器等)通过 `typedef` 前向声明 + * 隐藏为不透明指针类型,外部代码只能通过 API 函数操作 + * 2. **C 兼容类型**: 仅使用 C 原生类型(int, double, const char*)作为参数和返回值 + * 3. **输出通过指针**: 复数返回值(如顶点坐标、包围盒)通过输出参数传递,避免结构体 ABI 问题 + * 4. **显式生命周期管理**: 每个 create/load 函数都有对应的 free/destroy 函数 + * + * ## 句柄类型 + * + * | C 类型 | 内部类型 | 创建方式 | 释放方式 | + * |------------------|--------------------|-----------------------------|-------------------| + * | VdeHandle | VdeContext | vde_create() | vde_destroy() | + * | VdeMeshHandle | HalfedgeMesh | vde_load_mesh(), 工厂函数 | vde_mesh_free() | + * | VdeCurveHandle | NurbsCurve | vde_curve_create_bezier() | vde_curve_free() | + * | VdeSolverHandle | ConstraintSolver | vde_solver_create() | vde_solver_free() | + * | VdeBodyHandle | BrepModel | vde_body_create_*() | vde_body_free() | + * + * ## 布尔操作编码 + * + * | 操作 | 整数值 | + * |------|--------| + * | 并集 | 0 | + * | 交集 | 1 | + * | 差集 | 2 | + * + * ## 线程安全性 + * + * - 每个 VdeHandle 是独立的上下文,不同句柄之间线程安全 + * - 同一句柄上的操作不是线程安全的(调用方负责同步) + * + * @ingroup capi + * @see vde.h C++ 等价接口 + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// ═══════════════════════════════════════════════════════════ +// 不透明句柄类型(在 vde.h 中已有前向声明) +// ═══════════════════════════════════════════════════════════ + +/** @brief 引擎上下文句柄 — 管理错误状态和内部资源 */ +#ifndef VDE_HANDLE_DEFINED +typedef struct VdeContext* VdeHandle; +#endif + +/** @brief 三角网格句柄 — 顶点+面集合 */ +#ifndef VDE_MESH_HANDLE_DEFINED +typedef struct VdeMesh* VdeMeshHandle; +#endif + +/** @brief 参数曲线句柄 — NURBS 曲线 */ +#ifndef VDE_CURVE_HANDLE_DEFINED +typedef struct VdeCurve* VdeCurveHandle; +#endif + +/** @brief 约束求解器句柄 — 二维草图约束 */ +#ifndef VDE_SOLVER_HANDLE_DEFINED +typedef struct VdeSolver* VdeSolverHandle; +#endif + +/** @brief B-Rep 实体句柄 — 边界表示模型 */ +#ifndef VDE_BODY_HANDLE_DEFINED +typedef struct VdeBody* VdeBodyHandle; +#endif + +// ═══════════════════════════════════════════════════════════ +// 引擎生命周期 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 创建 ViewDesignEngine 上下文 + * + * 初始化一个独立的引擎实例。几乎所有后续操作都需要传入此句柄。 + * + * @return 新的引擎句柄,不需要时通过 vde_destroy() 释放 + * + * @code{.py} + * // Python ctypes 示例 + * ctx = lib.vde_create() + * @endcode + * + * @see vde_destroy 释放上下文 + */ +VdeHandle vde_create(void); + +/** + * @brief 销毁引擎上下文 + * + * 释放上下文及其内部资源。销毁后不能再使用该句柄。 + * + * @param ctx 引擎上下文句柄 + * + * @see vde_create + */ +void vde_destroy(VdeHandle ctx); + +/** + * @brief 获取引擎版本字符串 + * + * @return 语义版本号字符串(如 "0.5.0"),静态生命周期,无需释放 + * + * @code{.c} + * printf("VDE version: %s\n", vde_version()); + * @endcode + */ +const char* vde_version(void); + +/** + * @brief 获取最后一次操作的错误描述 + * + * 当某操作失败(如加载不存在的文件)时,可通过此函数获取详情。 + * + * @param ctx 引擎上下文句柄 + * @return 错误描述字符串(上下文的内部缓冲区,静态生命周期) + * + * @note 每次新的失败操作会覆盖此描述。成功操作不会清除它。 + */ +const char* vde_last_error(VdeHandle ctx); + +// ═══════════════════════════════════════════════════════════ +// 网格 I/O +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 从文件加载三角网格 + * + * 支持 OBJ (.obj), STL (.stl), PLY (.ply) 三种格式。 + * 格式通过文件扩展名自动检测。 + * + * @param ctx 引擎上下文句柄 + * @param path 文件路径(UTF-8 编码) + * @return 网格句柄,失败时返回 NULL(通过 vde_last_error() 获取原因) + * + * @code{.c} + * VdeMeshHandle mesh = vde_load_mesh(ctx, "model.obj"); + * if (!mesh) { + * fprintf(stderr, "Load failed: %s\n", vde_last_error(ctx)); + * return; + * } + * @endcode + * + * @see vde_mesh_free 释放网格 + * @see vde_save_mesh 保存网格 + */ +VdeMeshHandle vde_load_mesh(VdeHandle ctx, const char* path); + +/** + * @brief 将三角网格保存为文件 + * + * 当前支持 OBJ 格式输出。 + * + * @param ctx 引擎上下文句柄 + * @param mesh 网格句柄 + * @param path 输出文件路径 + * @param format 输出格式(当前仅 "obj" 有效) + * @return 成功返回 1,失败返回 0 + * + * @see vde_load_mesh 加载网格 + */ +int vde_save_mesh(VdeHandle ctx, VdeMeshHandle mesh, const char* path, const char* format); + +// ═══════════════════════════════════════════════════════════ +// 网格查询 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 获取网格的顶点数量 + * + * @param mesh 网格句柄(必须有效且非 NULL) + * @return 顶点数 + * + * @see vde_mesh_num_faces 面数 + */ +size_t vde_mesh_num_vertices(VdeMeshHandle mesh); + +/** + * @brief 获取网格的三角面数量 + * + * @param mesh 网格句柄 + * @return 面数 + * + * @see vde_mesh_num_vertices 顶点数 + */ +size_t vde_mesh_num_faces(VdeMeshHandle mesh); + +/** + * @brief 获取指定索引的顶点坐标 + * + * 顶点索引从 0 开始,到 num_vertices - 1。 + * + * @param mesh 网格句柄 + * @param idx 顶点索引 [0, num_vertices) + * @param out_x [out] 顶点 X 坐标 + * @param out_y [out] 顶点 Y 坐标 + * @param out_z [out] 顶点 Z 坐标 + * + * @pre idx < vde_mesh_num_vertices(mesh) + */ +void vde_mesh_get_vertex(VdeMeshHandle mesh, size_t idx, + double* out_x, double* out_y, double* out_z); + +/** + * @brief 获取网格的轴对齐包围盒 + * + * @param mesh 网格句柄 + * @param out_min_x [out] 最小 X 坐标 + * @param out_min_y [out] 最小 Y 坐标 + * @param out_min_z [out] 最小 Z 坐标 + * @param out_max_x [out] 最大 X 坐标 + * @param out_max_y [out] 最大 Y 坐标 + * @param out_max_z [out] 最大 Z 坐标 + */ +void vde_mesh_get_bounds(VdeMeshHandle mesh, + double* out_min_x, double* out_min_y, double* out_min_z, + double* out_max_x, double* out_max_y, double* out_max_z); + +/** + * @brief 释放网格句柄及其资源 + * + * 释放后将不能再使用该句柄。 + * + * @param mesh 网格句柄 + */ +void vde_mesh_free(VdeMeshHandle mesh); + +// ═══════════════════════════════════════════════════════════ +// 网格处理 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 网格简化 + * + * 通过 edge-collapse 或类似算法减少三角形数量, + * 同时尽可能保留几何特征。 + * + * @param ctx 引擎上下文句柄 + * @param mesh 输入网格句柄(不修改输入) + * @param target_ratio 目标简化比率 [0, 1](0 = 不变,1 = 最大简化) + * @return 简化后的新网格句柄 + * + * @code{.c} + * VdeMeshHandle simple = vde_mesh_simplify(ctx, mesh, 0.5); // 减少 50% + * @endcode + * + * @see vde_mesh_smooth 网格平滑 + */ +VdeMeshHandle vde_mesh_simplify(VdeHandle ctx, VdeMeshHandle mesh, double target_ratio); + +/** + * @brief 网格平滑(Laplacian 或 Taubin 平滑) + * + * 通过迭代平滑顶点位置改善网格质量,减少噪声。 + * + * @param ctx 引擎上下文句柄 + * @param mesh 输入网格句柄 + * @param iterations 平滑迭代次数 + * @return 平滑后的新网格句柄 + * + * @see vde_mesh_simplify 网格简化 + */ +VdeMeshHandle vde_mesh_smooth(VdeHandle ctx, VdeMeshHandle mesh, int iterations); + +/** + * @brief 网格布尔运算 + * + * 对两个三角网格执行布尔操作。 + * + * @param ctx 引擎上下文句柄 + * @param a 网格 A + * @param b 网格 B + * @param operation 布尔操作类型: 0 = 并集, 1 = 交集, 2 = 差集 (A \ B) + * @return 布尔结果的新网格句柄 + * + * @warning 两个输入网格必须是水密的(closed manifold),否则结果不可预测。 + * + * @code{.c} + * VdeMeshHandle result = vde_mesh_boolean(ctx, box, sphere, 0); // 并集 + * @endcode + */ +VdeMeshHandle vde_mesh_boolean(VdeHandle ctx, VdeMeshHandle a, VdeMeshHandle b, int operation); + +// ═══════════════════════════════════════════════════════════ +// 曲线 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 创建 Bézier 曲线 + * + * @param ctx 引擎上下文句柄 + * @param control_points 控制点数组,每 3 个 double 表示一个三维点 (x,y,z) + * @param num_points 控制点数量(次数 = num_points - 1) + * @param dim 维度(固定为 3) + * @return 曲线句柄 + * + * @code{.c} + * double cp[] = {0,0,0, 1,2,0, 3,0,0}; // 3 控制点,2 次 Bézier + * VdeCurveHandle curve = vde_curve_create_bezier(ctx, cp, 3, 3); + * @endcode + * + * @see vde_curve_evaluate 求值 + * @see vde_curve_free 释放 + */ +VdeCurveHandle vde_curve_create_bezier(VdeHandle ctx, const double* control_points, + int num_points, int dim); + +/** + * @brief 在参数 t 处求值曲线 + * + * @param curve 曲线句柄 + * @param t 参数值 [0, 1] + * @param out_x [out] 曲线上点的 X 坐标 + * @param out_y [out] 曲线上点的 Y 坐标 + * @param out_z [out] 曲线上点的 Z 坐标 + * + * @pre 0 ≤ t ≤ 1 + */ +void vde_curve_evaluate(VdeCurveHandle curve, double t, + double* out_x, double* out_y, double* out_z); + +/** + * @brief 获取曲线的次数 + * + * 对于 Bézier 曲线,次数 = 控制点数 - 1。 + * + * @param curve 曲线句柄 + * @return 曲线次数 + */ +int vde_curve_degree(VdeCurveHandle curve); + +/** + * @brief 释放曲线句柄 + * + * @param curve 曲线句柄 + */ +void vde_curve_free(VdeCurveHandle curve); + +// ═══════════════════════════════════════════════════════════ +// 碰撞检测 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 两球体碰撞检测(GJK 算法) + * + * 用于验证和测试 GJK 碰撞检测算法的轻量级接口。 + * + * @param ctx 引擎上下文句柄 + * @param cx1, cy1, cz1 第一个球体的球心坐标 + * @param r1 第一个球体的半径 + * @param cx2, cy2, cz2 第二个球体的球心坐标 + * @param r2 第二个球体的半径 + * @return 碰撞返回 1,不碰撞返回 0 + * + * @code{.c} + * int hit = vde_collision_gjk_spheres(ctx, 0,0,0, 1.0, 1.5,0,0, 0.5); + * @endcode + * + * @see vde_collision_ray_mesh 射线-网格相交 + */ +int vde_collision_gjk_spheres(VdeHandle ctx, + double cx1, double cy1, double cz1, double r1, + double cx2, double cy2, double cz2, double r2); + +/** + * @brief 射线与网格的最近交点查询 + * + * 使用 BVH 加速结构加速查询。 + * + * @param ctx 引擎上下文句柄 + * @param mesh 目标网格句柄 + * @param ox, oy, oz 射线起点 + * @param dx, dy, dz 射线方向(无需归一化) + * @param out_t [out] 交点参数 t(沿射线的距离) + * @param out_x, out_y, out_z [out] 交点坐标 + * @return 命中返回 1,未命中返回 0 + * + * @code{.c} + * double t, hx, hy, hz; + * int hit = vde_collision_ray_mesh(ctx, mesh, + * 0,0,-10, // 射线起点 + * 0,0,1, // 射线方向 (沿 Z 轴正方向) + * &t, &hx, &hy, &hz); + * if (hit) printf("Hit at (%f,%f,%f), t=%f\n", hx, hy, hz, t); + * @endcode + * + * @see vde_collision_gjk_spheres GJK 球体碰撞 + */ +int vde_collision_ray_mesh(VdeHandle ctx, VdeMeshHandle mesh, + double ox, double oy, double oz, + double dx, double dy, double dz, + double* out_t, double* out_x, double* out_y, double* out_z); + +// ═══════════════════════════════════════════════════════════ +// 约束求解器(二维草图) +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 创建二维约束求解器 + * + * 用于求解草图几何约束(距离、水平、垂直等)。 + * + * @return 求解器句柄 + * + * @see vde_solver_free 释放 + */ +VdeSolverHandle vde_solver_create(void); + +/** + * @brief 向求解器添加点 + * + * @param solver 求解器句柄 + * @param x 初始 X 坐标 + * @param y 初始 Y 坐标 + * @param fixed 固定点标记(1 = 固定不移动) + * @return 点的 ID(用于后续约束引用) + * + * @see vde_solver_add_line 添加线段 + */ +int vde_solver_add_point(VdeSolverHandle solver, double x, double y, int fixed); + +/** + * @brief 向求解器添加线段(由两个点定义) + * + * @param solver 求解器句柄 + * @param p0 起点 ID + * @param p1 终点 ID + * @return 线段的 ID + * + * @see vde_solver_add_point 添加点 + * @see vde_solver_add_horizontal 添加水平约束 + * @see vde_solver_add_vertical 添加垂直约束 + */ +int vde_solver_add_line(VdeSolverHandle solver, int p0, int p1); + +/** + * @brief 添加距离约束(两点之间) + * + * @param solver 求解器句柄 + * @param p0 第一个点的 ID + * @param p1 第二个点的 ID + * @param d 目标距离(必须 > 0) + * + * @see vde_solver_add_horizontal 水平约束 + */ +void vde_solver_add_distance_constraint(VdeSolverHandle solver, int p0, int p1, double d); + +/** + * @brief 添加水平约束(线段水平) + * + * @param solver 求解器句柄 + * @param line_id 线段 ID + * + * @see vde_solver_add_vertical 垂直约束 + * @see vde_solver_add_line 添加线段 + */ +void vde_solver_add_horizontal(VdeSolverHandle solver, int line_id); + +/** + * @brief 添加垂直约束(线段垂直) + * + * @param solver 求解器句柄 + * @param line_id 线段 ID + * + * @see vde_solver_add_horizontal 水平约束 + */ +void vde_solver_add_vertical(VdeSolverHandle solver, int line_id); + +/** + * @brief 求解约束系统 + * + * 通过迭代优化使所有点的位置满足所有约束。 + * + * @param solver 求解器句柄 + * @param max_iter 最大迭代次数 + * @param tol 收敛容差 + * @return 收敛返回 1,未收敛返回 0 + * + * @code{.c} + * int converged = vde_solver_solve(solver, 100, 1e-6); + * @endcode + * + * @see vde_solver_get_point 获取求解后的点坐标 + */ +int vde_solver_solve(VdeSolverHandle solver, int max_iter, double tol); + +/** + * @brief 获取求解后的点坐标 + * + * @param solver 求解器句柄 + * @param idx 点 ID + * @param out_x [out] 求解后 X 坐标 + * @param out_y [out] 求解后 Y 坐标 + * + * @pre 必须先调用 vde_solver_solve() + */ +void vde_solver_get_point(VdeSolverHandle solver, int idx, double* out_x, double* out_y); + +/** + * @brief 释放求解器句柄 + * + * @param solver 求解器句柄 + */ +void vde_solver_free(VdeSolverHandle solver); + +// ═══════════════════════════════════════════════════════════ +// B-Rep 建模 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 创建实心立方体 B-Rep + * + * 以原点为中心的轴对齐立方体。 + * + * @param ctx 引擎上下文句柄 + * @param w 宽度(X 方向),> 0 + * @param h 高度(Y 方向),> 0 + * @param d 深度(Z 方向),> 0 + * @return B-Rep 实体句柄 + * + * @see vde_body_create_cylinder 圆柱体 + * @see vde_body_create_sphere 球体 + */ +VdeBodyHandle vde_body_create_box(VdeHandle ctx, double w, double h, double d); + +/** + * @brief 创建实心圆柱体 B-Rep + * + * 沿 Y 轴、以原点为中心的圆柱体。 + * + * @param ctx 引擎上下文句柄 + * @param r 截面半径,> 0 + * @param h 总高度(从 -h/2 到 +h/2) + * @param segs 截面分段数(边数) + * @return B-Rep 实体句柄 + * + * @see vde_body_create_box 立方体 + */ +VdeBodyHandle vde_body_create_cylinder(VdeHandle ctx, double r, double h, int segs); + +/** + * @brief 创建球体 B-Rep + * + * @param ctx 引擎上下文句柄 + * @param r 球半径,> 0 + * @param su 经度方向分段数 + * @param sv 纬度方向分段数 + * @return B-Rep 实体句柄 + */ +VdeBodyHandle vde_body_create_sphere(VdeHandle ctx, double r, int su, int sv); + +/** + * @brief 将 B-Rep 实体转换为三角网格 + * + * 以指定的弦高误差对曲面进行 tessellation。 + * + * @param ctx 引擎上下文句柄 + * @param body B-Rep 实体句柄 + * @param deflection 弦高误差(越小 = 越精细,推荐 0.01) + * @return 三角网格句柄 + * + * @code{.c} + * VdeBodyHandle body = vde_body_create_box(ctx, 10, 5, 3); + * VdeMeshHandle mesh = vde_body_to_mesh(ctx, body, 0.01); + * vde_save_mesh(ctx, mesh, "box.obj", "obj"); + * @endcode + * + * @see vde_body_free 释放 B-Rep 实体 + */ +VdeMeshHandle vde_body_to_mesh(VdeHandle ctx, VdeBodyHandle body, double deflection); + +/** + * @brief 释放 B-Rep 实体句柄 + * + * @param body B-Rep 实体句柄 + */ +void vde_body_free(VdeBodyHandle body); + +// ═══════════════════════════════════════════════════════════ +// 序列化 +// ═══════════════════════════════════════════════════════════ + +/** + * @brief 将网格序列化为二进制字节流 + * + * 返回的缓冲区需要在消费后通过 vde_free_buffer() 释放。 + * + * @param ctx 引擎上下文句柄 + * @param mesh 网格句柄 + * @param out_data [out] 指向序列化字节数组的指针 + * @return 字节数 + * + * @note 输出的缓冲区由库管理,调用方不得直接 free/delete。 + * + * @code{.c} + * uint8_t* data = NULL; + * size_t len = vde_serialize_mesh(ctx, mesh, &data); + * // ... 使用/存储 data ... + * vde_free_buffer(data); + * @endcode + * + * @see vde_deserialize_mesh 反序列化 + * @see vde_free_buffer 释放序列化缓冲区 + */ +size_t vde_serialize_mesh(VdeHandle ctx, VdeMeshHandle mesh, uint8_t** out_data); + +/** + * @brief 从二进制字节流反序列化为网格 + * + * @param ctx 引擎上下文句柄 + * @param data 序列化数据缓冲区 + * @param len 数据长度(字节数) + * @return 反序列化后的网格句柄 + * + * @see vde_serialize_mesh 序列化 + */ +VdeMeshHandle vde_deserialize_mesh(VdeHandle ctx, const uint8_t* data, size_t len); + +/** + * @brief 释放由 vde_serialize_mesh 分配的缓冲区 + * + * @param ptr 由 vde_serialize_mesh 返回的缓冲区指针 + */ +void vde_free_buffer(uint8_t* ptr); + +#ifdef __cplusplus +} +#endif diff --git a/include/vde/sdf/sdf_optimize.h b/include/vde/sdf/sdf_optimize.h index 087a94b..d6a7fdd 100644 --- a/include/vde/sdf/sdf_optimize.h +++ b/include/vde/sdf/sdf_optimize.h @@ -1,4 +1,33 @@ #pragma once +/** + * @file sdf_optimize.h + * @brief 基于梯度的 SDF 形状优化 + * + * 提供 SDF 形状的拟合、碰撞解析、可及性分析和对称性检测等功能。 + * + * ## 主要功能 + * + * | 功能 | 说明 | + * |------------------------|-------------------------------------------------| + * | Shape Fitting | 将参数化形状拟合到点云或目标 SDF | + * | Collision Avoidance | 平移形状以解决穿透 | + * | Accessibility | 采样半球以评估表面上点的可及性 | + * | Symmetry Detection | 检测反射对称性并找到最佳对称平面 | + * | Volume & Mass | 通过蒙地卡罗法估计体积和质心 | + * | Parameter Optimization | 收集所有可优化的参数并计算数值梯度 | + * + * ## 使用模式 + * + * ```cpp + * auto shape = SdfNode::sphere(0.5); + * auto result = fit_to_point_cloud(shape, points, 0.01, 200); + * if (result.converged) { + * // result.optimized_shape 包含优化后的参数 + * } + * ``` + * + * @ingroup sdf + */ #include "vde/sdf/sdf_tree.h" #include "vde/core/point.h" #include @@ -13,43 +42,108 @@ using core::Vector3D; // Shape Optimization // ──────────────────────────────────────────────── -/// Optimization result +/** + * @brief 优化结果 + * + * 包含优化后的形状、损失值、收敛标志和迭代历史。 + */ struct OptimizeResult { - SdfNodePtr optimized_shape; - double final_loss; - int iterations; - bool converged; - std::vector loss_history; + SdfNodePtr optimized_shape; ///< 优化后的 SDF 树(参数已更新) + double final_loss; ///< 最终损失值 + int iterations; ///< 实际迭代次数 + bool converged; ///< 是否收敛 + std::vector loss_history; ///< 每次迭代的损失记录 }; -/// Loss function type: takes SDF evaluator + target, returns scalar loss +/** + * @brief 损失函数类型 + * + * 接受一个 SDF 求值函数(封装了参数化的形状),返回标量损失。 + * + * @see fit_to_point_cloud 使用该类型的内置实现 + */ using LossFn = std::function&)>; // ── Shape Fitting ─────────────────────────────── -/// Fit a parameterized shape to a target point cloud by minimizing -/// distance of surface to target points. -/// @param initial Initial shape (sphere, box, cylinder, etc.) -/// @param target_points Target point cloud (surface samples) -/// @param learning_rate Gradient descent step size -/// @param max_iterations Maximum iterations -/// @return Optimized shape + convergence info +/** + * @brief 将参数化形状拟合到目标点云 + * + * 最小化目标表面点到拟合形状表面的有符号距离。 + * + * **算法概要:** + * 1. 遍历 target_points 计算每个点到初始形状的 SDF 值 + * 2. 用有限差分计算形状参数对损失的梯度 + * 3. 梯度下降更新参数 + * 4. 重复直至收敛或达到最大迭代次数 + * + * @param initial 初始形状(sphere, box, cylinder 等),其参数将被优化 + * @param target_points 目标点云(表面采样点) + * @param learning_rate 梯度下降步长(默认 0.01) + * @param max_iterations 最大迭代次数(默认 100) + * @return 优化结果(含优化后的形状和收敛信息) + * + * @note 损失函数为 L = mean(|sdf(p_i)|),即点到表面的平均绝对距离。 + * @note initial 的共享子节点不会被复制——返回的 optimize_shape 共享未修改的子树。 + * + * @code{.cpp} + * std::vector points = { /* point cloud data */ }; + * auto sphere = SdfNode::sphere(1.0); + * auto result = fit_to_point_cloud(sphere, points, 0.01, 200); + * // result.optimized_shape 的 params.radius 已被优化 + * @endcode + * + * @see fit_surface_to_points 类似但仅最小化 |sdf| 而非有符号距离 + * @see fit_to_sdf 拟合到另一个 SDF + */ [[nodiscard]] OptimizeResult fit_to_point_cloud( const SdfNodePtr& initial, const std::vector& target_points, double learning_rate = 0.01, int max_iterations = 100); -/// Fit a shape to minimize SDF values at target points (drive surface to points) -/// Loss = mean(|sdf(p_i)|) for p_i in targets +/** + * @brief 将形状表面移动到目标采样点 + * + * 与 fit_to_point_cloud 类似,但损失函数使用 L = mean(|sdf(p_i)|)。 + * 这强制采样点位于表面上(而非仅靠近表面)。 + * + * @param initial 初始形状 + * @param surface_points 目标表面点 + * @param learning_rate 学习率 + * @param max_iterations 最大迭代次数 + * @return 优化结果 + * + * @see fit_to_point_cloud 更通用的点云拟合(可包含内部/外部点) + */ [[nodiscard]] OptimizeResult fit_surface_to_points( const SdfNodePtr& initial, const std::vector& surface_points, double learning_rate = 0.01, int max_iterations = 100); -/// Fit a shape to match another SDF (shape matching) -/// Loss = mean((sdf_a(p_i) - sdf_b(p_i))^2) over sample grid +/** + * @brief 拟合一个 SDF 形状以匹配另一个目标 SDF + * + * 在规则网格上采样两个 SDF,最小化它们之间的均方差。 + * + * **损失函数:** L = mean((sdf_source(p_i) - sdf_target(p_i))²) + * + * 适合将一个参数化形状"画像"成一个更复杂的隐式形状。 + * + * @param source 被优化的参数化形状 + * @param target_sdf 目标 SDF 函数 + * @param bmin 采样网格最小角点 + * @param bmax 采样网格最大角点 + * @param grid_resolution 采样网格分辨率(默认 16) + * @param learning_rate 学习率 + * @param max_iterations 最大迭代次数 + * @return 优化结果 + * + * @warning 网格分辨率每增加一倍,计算量增加 8 倍。使用 8~32 可获得合理速度。 + * + * @see fit_to_point_cloud 拟合到点云 + */ [[nodiscard]] OptimizeResult fit_to_sdf( const SdfNodePtr& source, const std::function& target_sdf, @@ -60,19 +154,46 @@ using LossFn = std::function& // ── Collision Avoidance ───────────────────────── -/// Push shapes apart to resolve interpenetration. -/// Modifies shapes in-place by translating them. -/// @param shape_a First shape -/// @param shape_b Second shape -/// @param step_size Translation step per iteration -/// @param max_iterations Max iterations -/// @return True if collision resolved +/** + * @brief 通过平移解决两个形状之间的穿透 + * + * 在穿透区域采样点,沿梯度方向平移形状以分离它们。 + * 修改传入的形状(原地修改 translate_offset 参数)。 + * + * @param shape_a 形状 A(原地修改) + * @param shape_b 形状 B(原地修改) + * @param step_size 每次迭代的平移步长(默认 0.1) + * @param max_iterations 最大迭代次数(默认 50) + * @return true 如果碰撞被成功解决;false 如果在迭代次数内未能解决 + * + * @code{.cpp} + * auto sphere_a = SdfNode::sphere(1.0); + * auto sphere_b = SdfNode::translate(SdfNode::sphere(1.0), Point3D(1.5, 0, 0)); + * bool resolved = resolve_collision(sphere_a, sphere_b, 0.1, 100); + * @endcode + * + * @see penetration_depth 估算穿透深度 + */ [[nodiscard]] bool resolve_collision( SdfNodePtr& shape_a, SdfNodePtr& shape_b, double step_size = 0.1, int max_iterations = 50); -/// Find minimum translation distance to avoid collision +/** + * @brief 估算两个形状之间的最小穿透距离 + * + * 在包围盒内随机采样,找到两个 SDF 均 ≤ 0 的区域(重叠区), + * 计算重叠点到表面的最大距离。 + * + * @param shape_a 形状 A + * @param shape_b 形状 B + * @param bmin 公共包围盒最小角点 + * @param bmax 公共包围盒最大角点 + * @param samples 蒙地卡罗采样数(默认 1000) + * @return 最大穿透深度(负值表示不重叠时的最小间距) + * + * @see resolve_collision 解决穿透 + */ [[nodiscard]] double penetration_depth( const SdfNodePtr& shape_a, const SdfNodePtr& shape_b, @@ -81,16 +202,42 @@ using LossFn = std::function& // ── Reachability / Accessibility ───────────────── -/// Compute accessibility score at point p on surface of shape. -/// Returns 0 (inaccessible) to 1 (fully accessible). -/// Uses hemisphere sampling + occlusion testing. +/** + * @brief 计算形状表面上点的可及性分数 + * + * 从表面点 p 发射半球光线,统计未被形状遮挡的比例。 + * 返回 0(完全不可及)到 1(完全可及)之间的分数。 + * + * @param shape SDF 形状 + * @param p 表面点(应满足 |sdf(p)| < ε) + * @param direction 接近方向 + * @param hemisphere_samples 半球采样光线数量(默认 64) + * @return 可及性分数 [0, 1] + * + * @note 采样数越多越精确,但线性增加计算量。 + * + * @see find_accessible_point 找到最大可及性的表面点 + */ [[nodiscard]] double accessibility( const SdfNodePtr& shape, const Point3D& p, // surface point const Vector3D& direction, // approach direction int hemisphere_samples = 64); -/// Find point with maximum accessibility +/** + * @brief 找到给定接近方向下具有最大可及性的表面点 + * + * 在包围盒内的规则网格上搜索,计算每个网格点处的可及性分数。 + * + * @param shape SDF 形状 + * @param approach_dir 接近方向(如工具接近方向) + * @param bmin 搜索包围盒最小角点 + * @param bmax 搜索包围盒最大角点 + * @param grid_res 搜索网格分辨率(默认 32) + * @return 最大可及性的表面点 + * + * @see accessibility 单点可及性计算 + */ [[nodiscard]] Point3D find_accessible_point( const SdfNodePtr& shape, const Vector3D& approach_dir, @@ -99,8 +246,22 @@ using LossFn = std::function& // ── Symmetry Detection ────────────────────────── -/// Detect if shape has reflective symmetry in given direction. -/// Returns symmetry score: 0 = asymmetric, 1 = perfectly symmetric. +/** + * @brief 检测形状在给定方向上的反射对称性 + * + * 在形状的包围盒内随机采样点对 (p, p') 其中 p' 是关于给定平面的反射。 + * 比较两点的 SDF 值以评估对称程度。 + * + * @param shape SDF 形状 + * @param bmin 包围盒最小角点 + * @param bmax 包围盒最大角点 + * @param plane_normal 反射平面的法向量 + * @param plane_offset 反射平面的偏移量(沿法向量方向,默认 0) + * @param samples 采样点对数(默认 1000) + * @return 对称性分数 [0, 1](0 = 完全不对称,1 = 完美对称) + * + * @see find_symmetry_plane 自动寻找最佳对称平面 + */ [[nodiscard]] double symmetry_score( const SdfNodePtr& shape, const Point3D& bmin, const Point3D& bmax, @@ -108,12 +269,29 @@ using LossFn = std::function& double plane_offset = 0.0, int samples = 1000); -/// Find the best symmetry plane +/** + * @brief 对称检测结果 + */ struct SymmetryResult { - double score; - Vector3D normal; - double offset; + double score; ///< 对称性分数 [0, 1] + Vector3D normal; ///< 最佳对称平面的法向量 + double offset; ///< 最佳对称平面的偏移量 }; + +/** + * @brief 自动找到形状的最佳反射对称平面 + * + * 尝试多个候选平面方向(沿主轴和若干采样方向), + * 返回对称性分数最高的平面。 + * + * @param shape SDF 形状 + * @param bmin 包围盒最小角点 + * @param bmax 包围盒最大角点 + * @param samples 每个候选方向的采样数(默认 1000) + * @return 最佳对称平面及其分数 + * + * @see symmetry_score 给定方向的对称性检测 + */ [[nodiscard]] SymmetryResult find_symmetry_plane( const SdfNodePtr& shape, const Point3D& bmin, const Point3D& bmax, @@ -121,13 +299,45 @@ struct SymmetryResult { // ── SDF Volume Computation ────────────────────── -/// Approximate volume of implicit shape via Monte Carlo sampling +/** + * @brief 通过蒙地卡罗法估算隐式形状的体积 + * + * 在包围盒内均匀随机采样,统计落在形状内部的点比例, + * 乘以包围盒体积得到近似体积。 + * + * **公式:** V ≈ V_box · N_inside / N_total,其中 N_inside = count(sdf(p_i) < 0) + * + * @param shape SDF 形状 + * @param bmin 采样包围盒最小角点 + * @param bmax 采样包围盒最大角点 + * @param samples 蒙地卡罗采样数(默认 10000) + * @return 近似体积 + * + * @note 精度与 √samples 成正比。10000 采样 ≈ 1% 相对误差。 + * @note 包围盒应尽可能紧致以减小方差。 + * + * @see center_of_mass 蒙地卡罗质心估算 + */ [[nodiscard]] double estimate_volume( const SdfNodePtr& shape, const Point3D& bmin, const Point3D& bmax, int samples = 10000); -/// Compute center of mass via Monte Carlo +/** + * @brief 通过蒙地卡罗法估算形状的质心 + * + * 对形状内部的采样点取算术平均。 + * + * **公式:** COM ≈ Σ p_i / N_inside,其中 sdf(p_i) < 0 + * + * @param shape SDF 形状 + * @param bmin 采样包围盒最小角点 + * @param bmax 采样包围盒最大角点 + * @param samples 蒙地卡罗采样数(默认 10000) + * @return 近似质心坐标 + * + * @see estimate_volume 体积估算 + */ [[nodiscard]] Point3D center_of_mass( const SdfNodePtr& shape, const Point3D& bmin, const Point3D& bmax, @@ -135,16 +345,63 @@ struct SymmetryResult { // ── Parameter Collection ──────────────────────── -/// A reference to a mutable double parameter inside an SDF tree +/** + * @brief SDF 树中可变参数的引用 + * + * 指向 SdfNode::params 中具体数值字段的指针, + * 配合 collect_params() 收集所有可优化参数。 + */ struct ParamRef { - double* value; - std::string name; + double* value; ///< 指向参数值的可写指针 + std::string name; ///< 参数名称(调试用) }; -/// Collect all mutable numeric parameters from primitive leaf nodes +/** + * @brief 从叶节点收集所有可变数值参数 + * + * 遍历 SDF 树,收集所有图元节点中可优化的参数引用。 + * 返回的 ParamRef 列表可传递给 numerical_gradient() 进行优化。 + * + * @param root SDF 树根节点(可写引用,因为参数将被原地修改) + * @return 可变参数引用列表 + * + * @code{.cpp} + * auto root = SdfNode::op_union( + * SdfNode::sphere(1.0), + * SdfNode::box(Point3D(0.5, 0.5, 0.5)) + * ); + * auto params = collect_params(root); + * // params[0].value 指向球体半径 + * // params[1..4] 指向盒子的 extents 分量 + * @endcode + * + * @see numerical_gradient 计算损失对参数的数值梯度 + */ [[nodiscard]] std::vector collect_params(SdfNodePtr& root); -/// Compute numerical gradient of a scalar loss w.r.t. collected parameters +/** + * @brief 计算标量损失对收集到的参数的数值梯度 + * + * 对每个参数逐个做中心差分,返回梯度向量。 + * + * @param params collect_params() 返回的参数引用列表 + * @param loss_fn 损失函数(无参数的可调用对象) + * @param eps 差分步长(默认 1e-6) + * @return 梯度向量 grad_loss,grad_loss[i] = ∂loss / ∂params[i].value + * + * @code{.cpp} + * auto params = collect_params(root); + * auto loss = [&]() { + * double s = 0; + * for (auto& pt : target_points) s += std::abs(evaluate(root, pt)); + * return s / target_points.size(); + * }; + * auto grad = numerical_gradient(params, loss, 1e-6); + * @endcode + * + * @see collect_params 收集参数 + * @see GradientDescent 梯度下降优化器 + */ [[nodiscard]] std::vector numerical_gradient( const std::vector& params, const std::function& loss_fn, @@ -152,22 +409,53 @@ struct ParamRef { // ── Gradient Descent Utility ──────────────────── -/// Simple gradient descent with fixed learning rate +/** + * @brief 固定学习率的简单梯度下降优化器 + * + * 管理迭代计数和学习率,提供 step() 方法执行单步参数更新。 + * + * @code{.cpp} + * auto params = collect_params(root); + * GradientDescent gd(0.01); + * for (int i = 0; i < 100; ++i) { + * auto loss = gd.step(param_vals, [&](auto& vals, auto& grad) { + * // 填充 grad,返回 loss + * }); + * if (loss < tol) break; + * } + * @endcode + */ class GradientDescent { public: + /** + * @brief 构造优化器 + * @param lr 学习率(步长因子) + */ explicit GradientDescent(double lr) : lr_(lr) {} - /// Step parameters toward minimizing loss. - /// @param params Parameter vector (mutable) - /// @param grad_fn Function that computes gradient for given params; - /// returns loss as scalar and fills gradient vector. - /// @return Current loss + /** + * @brief 执行单步梯度下降 + * + * 调用 grad_fn 获取当前参数的梯度和损失值, + * 然后沿负梯度方向更新参数: param_i -= lr · grad_i。 + * + * @param params 参数向量(原地修改) + * @param grad_fn 梯度计算函数,签名为 + * double(const std::vector& vals, std::vector& grad_out) + * 接受当前参数值,填充 grad_out 并返回损失标量。 + * @return 当前损失值 + */ double step(std::vector& params, const std::function&, std::vector&)>& grad_fn); + /** @brief 设置学习率 */ void set_learning_rate(double lr) { lr_ = lr; } + + /** @brief 获取学习率 */ [[nodiscard]] double learning_rate() const { return lr_; } + + /** @brief 获取当前迭代次数 */ [[nodiscard]] int iteration() const { return iteration_; } private: diff --git a/include/vde/sdf/sdf_torch.h b/include/vde/sdf/sdf_torch.h index e596712..955991f 100644 --- a/include/vde/sdf/sdf_torch.h +++ b/include/vde/sdf/sdf_torch.h @@ -1,4 +1,31 @@ #pragma once +/** + * @file sdf_torch.h + * @brief SDF 批量求值与梯度计算(CPU 后端) + * + * 提供吞吐量优化的 SDF 批量求值接口,适合大量点或实时场景。 + * + * ## 设计意图 + * + * 虽然命名为 sdf_torch,但本头的实现是纯 C++ CPU 版本, + * 不依赖 PyTorch。接口设计为"可替换为 CUDA/Torch 后端"的占位, + * 函数签名与假设的 GPU 版本保持一致。 + * + * ## 数据布局 + * + * - **输入数组:** x,y,z 交替存储(SoA 未使用),n 个点 × 3 个 double + * - **输出距离:** 连续 n 个 double + * - **输出梯度:** 连续 3n 个 double,x,y,z 交替存储 + * + * ## 使用场景 + * + * - Ray-marching:沿射线采样大量点 + * - 网格求值:Marching Cubes 前的密集采样 + * - 可视化:距离场的 heatmap 生成 + * - 物理仿真:碰撞检测批量查询 + * + * @ingroup sdf + */ #include "vde/sdf/sdf_gradient.h" #include "vde/sdf/sdf_tree.h" #include "vde/core/point.h" @@ -8,25 +35,80 @@ namespace vde::sdf { -/// Evaluate SDF on a batch of points (for batched computation) -/// @param root SDF tree -/// @param points Flat array of x,y,z triplets (size = 3*n) -/// @param n Number of points -/// @param distances Output array (size = n) +/** + * @brief 在批处理模式下求值 SDF + * + * 对 n 个三维点逐一求值 SDF 树,将距离值写入预分配的输出数组。 + * + * **性能特征:** + * - O(n) 时间复杂度,每个点独立求值 + * - 内存访问模式: 顺序读取 3n 个 double,顺序写入 n 个 double + * - 无 SIMD 优化(当前 CPU 后端) + * + * @param root SDF 表达式树根节点 + * @param points 点数组,length = 3·n,x,y,z 交替存储 + * @param n 点数 + * @param distances 输出数组,length = n,写入有符号距离值 + * + * @pre points 和 distances 必须非空,且各自有足够长度 + * + * @code{.cpp} + * std::vector pts = {0,0,0, 1,0,0, 2,0,0}; // 3 points + * std::vector dists(3); + * evaluate_batch(root, pts.data(), 3, dists.data()); + * @endcode + * + * @see evaluate 单点求值 + * @see gradient_batch 批量梯度计算 + */ void evaluate_batch(const SdfNodePtr& root, const double* points, int n, double* distances); -/// Compute gradients for a batch of points -/// @param root SDF tree -/// @param points Flat array of x,y,z triplets -/// @param n Number of points -/// @param gradients Output array (size = 3*n, interleaved x,y,z) +/** + * @brief 在批处理模式下求值 SDF 的空间梯度 + * + * 对 n 个点逐一计算空间梯度 ∇f = (df/dx, df/dy, df/dz), + * 结果写入预分配的梯度数组。 + * + * @param root SDF 表达式树根节点 + * @param points 点数组,length = 3·n + * @param n 点数 + * @param gradients 输出数组,length = 3·n,x,y,z 梯度分量交替存储 + * + * @pre points 和 gradients 必须非空,且有足够长度 + * + * @see evaluate_batch 批量距离求值 + * @see evaluate_with_gradient_batch 同时获取距离和梯度(减少重复采样) + */ void gradient_batch(const SdfNodePtr& root, const double* points, int n, double* gradients); -/// Compute both values and gradients in one pass +/** + * @brief 一次遍历同时求值 SDF 值和梯度 + * + * 相比于分别调用 evaluate_batch + gradient_batch, + * 本函数在一次遍历中完成两者,避免重复遍历树。 + * + * @param root SDF 表达式树根节点 + * @param points 点数组,length = 3·n + * @param n 点数 + * @param distances 输出距离数组,length = n + * @param gradients 输出梯度数组,length = 3·n + * + * @note 对于大量点的场景(n > 1000),推荐使用本函数以获得 ~2× 速度提升。 + * + * @code{.cpp} + * int n = 10000; + * std::vector pts(3*n), dists(n), grads(3*n); + * // ... fill pts ... + * evaluate_with_gradient_batch(root, pts.data(), n, dists.data(), grads.data()); + * @endcode + * + * @see evaluate_batch 仅求距离 + * @see gradient_batch 仅求梯度 + */ void evaluate_with_gradient_batch(const SdfNodePtr& root, const double* points, int n, double* distances,