docs: doxygen annotations for sdf + brep + capi
This commit is contained in:
+302
-18
@@ -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<curves::NurbsCurve> curve;
|
||||
bool reversed = false;
|
||||
};
|
||||
struct TopoLoop { int id; std::vector<int> edges; bool is_outer = true; };
|
||||
struct TopoFace { int id, surface_id; std::vector<int> loops; bool reversed = false; };
|
||||
struct TopoShell { int id; std::vector<int> faces; bool closed = true; };
|
||||
struct TopoBody { int id; std::vector<int> 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<curves::NurbsCurve> curve; ///< 边的几何曲线(可为空,表示直线)
|
||||
bool reversed = false; ///< 是否相对于曲线参数方向反转
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 拓扑环(Loop)
|
||||
*
|
||||
* 一组首尾相连的边构成的封闭边界。
|
||||
* - 外环 (is_outer = true): 面的外部边界,逆时针为正
|
||||
* - 内环 (is_outer = false): 面上的孔,顺时针为正
|
||||
*/
|
||||
struct TopoLoop {
|
||||
int id; ///< 唯一环 ID
|
||||
std::vector<int> edges; ///< 边 ID 序列(按环的遍历顺序)
|
||||
bool is_outer = true; ///< true = 外环, false = 内环(孔)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 拓扑面
|
||||
*
|
||||
* 曲面上由一个或多个环界定的区域。
|
||||
* 面的正方向与曲面的法向量一致。
|
||||
* reversed = true 表示法向量取反。
|
||||
*/
|
||||
struct TopoFace {
|
||||
int id; ///< 唯一面 ID
|
||||
int surface_id; ///< 关联的曲面 ID
|
||||
std::vector<int> loops; ///< 环 ID 列表(第一个为外环,后续为内环)
|
||||
bool reversed = false; ///< 面法向量是否反转
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 拓扑壳
|
||||
*
|
||||
* 一组面的连续集合,构成空间的封闭或开放边界。
|
||||
* closed = true 表示壳封闭一个体积(水密)。
|
||||
*/
|
||||
struct TopoShell {
|
||||
int id; ///< 唯一壳 ID
|
||||
std::vector<int> faces; ///< 面 ID 列表
|
||||
bool closed = true; ///< 是否为封闭壳
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 拓扑体
|
||||
*
|
||||
* 一个或多个壳的集合,构成完整的几何实体。
|
||||
* name 用于在 STEP/IGES 文件中标识零件。
|
||||
*/
|
||||
struct TopoBody {
|
||||
int id; ///< 唯一体 ID
|
||||
std::vector<int> 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<int>& edges, bool outer = true);
|
||||
|
||||
/**
|
||||
* @brief 添加面
|
||||
* @param surface_id 关联曲面 ID
|
||||
* @param loops 环 ID 列表
|
||||
* @return 新面的 ID
|
||||
*/
|
||||
int add_face(int surface_id, const std::vector<int>& loops);
|
||||
|
||||
/**
|
||||
* @brief 添加壳
|
||||
* @param faces 面 ID 列表
|
||||
* @param closed 是否为封闭壳
|
||||
* @return 新壳的 ID
|
||||
*/
|
||||
int add_shell(const std::vector<int>& faces, bool closed = true);
|
||||
|
||||
/**
|
||||
* @brief 添加体
|
||||
* @param shells 壳 ID 列表
|
||||
* @param name 体名称
|
||||
* @return 新体的 ID
|
||||
*/
|
||||
int add_body(const std::vector<int>& 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<TopoLoop>& 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<int> face_edges(int face_id) const;
|
||||
[[nodiscard]] std::vector<int> edge_faces(int edge_id) const;
|
||||
[[nodiscard]] std::vector<int> 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<TopoLoop>& all_loops() const { return loops_; }
|
||||
/**
|
||||
* @brief 获取面的所有边
|
||||
*
|
||||
* 展开面的所有环,收集其中包含的边 ID。
|
||||
*
|
||||
* @param face_id 面 ID
|
||||
* @return 边 ID 列表(去重)
|
||||
*/
|
||||
[[nodiscard]] std::vector<int> face_edges(int face_id) const;
|
||||
|
||||
/**
|
||||
* @brief 获取边的所有相邻面
|
||||
*
|
||||
* 反向查找:哪些面包含此边。
|
||||
* 在流形网格中每条边应有恰好 2 个相邻面。
|
||||
*
|
||||
* @param edge_id 边 ID
|
||||
* @return 相邻面 ID 列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<int> edge_faces(int edge_id) const;
|
||||
|
||||
/**
|
||||
* @brief 获取顶点的所有相邻边
|
||||
*
|
||||
* @param vertex_id 顶点 ID
|
||||
* @return 相邻边 ID 列表
|
||||
*/
|
||||
[[nodiscard]] std::vector<int> vertex_edges(int vertex_id) const;
|
||||
|
||||
private:
|
||||
std::vector<TopoVertex> vertices_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <vector>
|
||||
#include <string>
|
||||
@@ -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<std::string> errors;
|
||||
|
||||
/** @brief 警告信息列表(不影响 valid) */
|
||||
std::vector<std::string> 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<double>::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
|
||||
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<BrepModel>& 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<BrepModel>& bodies);
|
||||
|
||||
} // namespace vde::brep
|
||||
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<BrepModel> 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<BrepModel> 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
|
||||
|
||||
+233
-18
@@ -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<curves::NurbsCurve>& 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
|
||||
|
||||
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<BrepModel>& 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<BrepModel>& bodies);
|
||||
|
||||
} // namespace vde::brep
|
||||
|
||||
+109
-14
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<BrepModel> 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<BrepModel> 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
|
||||
|
||||
@@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
+337
-49
@@ -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 <vector>
|
||||
@@ -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<double> loss_history;
|
||||
SdfNodePtr optimized_shape; ///< 优化后的 SDF 树(参数已更新)
|
||||
double final_loss; ///< 最终损失值
|
||||
int iterations; ///< 实际迭代次数
|
||||
bool converged; ///< 是否收敛
|
||||
std::vector<double> loss_history; ///< 每次迭代的损失记录
|
||||
};
|
||||
|
||||
/// Loss function type: takes SDF evaluator + target, returns scalar loss
|
||||
/**
|
||||
* @brief 损失函数类型
|
||||
*
|
||||
* 接受一个 SDF 求值函数(封装了参数化的形状),返回标量损失。
|
||||
*
|
||||
* @see fit_to_point_cloud 使用该类型的内置实现
|
||||
*/
|
||||
using LossFn = std::function<double(const std::function<double(const Point3D&)>&)>;
|
||||
|
||||
// ── 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<Point3D> 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<Point3D>& 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<Point3D>& 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<double(const Point3D&)>& target_sdf,
|
||||
@@ -60,19 +154,46 @@ using LossFn = std::function<double(const std::function<double(const Point3D&)>&
|
||||
|
||||
// ── 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<double(const std::function<double(const Point3D&)>&
|
||||
|
||||
// ── 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<double(const std::function<double(const Point3D&)>&
|
||||
|
||||
// ── 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(const std::function<double(const Point3D&)>&
|
||||
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<ParamRef> 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<double> numerical_gradient(
|
||||
const std::vector<ParamRef>& params,
|
||||
const std::function<double()>& 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<double>& vals, std::vector<double>& grad_out)
|
||||
* 接受当前参数值,填充 grad_out 并返回损失标量。
|
||||
* @return 当前损失值
|
||||
*/
|
||||
double step(std::vector<double>& params,
|
||||
const std::function<double(const std::vector<double>&,
|
||||
std::vector<double>&)>& 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:
|
||||
|
||||
+93
-11
@@ -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<double> pts = {0,0,0, 1,0,0, 2,0,0}; // 3 points
|
||||
* std::vector<double> 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<double> 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,
|
||||
|
||||
Reference in New Issue
Block a user