#pragma once /** * @file ssi_boolean.h * @brief 基于曲面-曲面求交 (SSI) 的 B-Rep 布尔运算 * * 这是 B-Rep 布尔运算的完整 SSI 管线实现,与 brep_boolean.h * 中基于平面切割的旧实现并存。SSI 方法使用精确的曲面交线来 * 分割和分类面,从而获得更高质量的布尔结果。 * * ## SSI 管线 (4 步) * * 1. **SSI 计算** — 对所有面对并行计算曲面交线 * 2. **面分割** — 用交线分割面,生成 TrimmedSurface 片段 * 3. **分类** — 射线投射 + 精确谓词分类每个面片段 * 4. **缝合** — 缝合保留的面片段,调用 heal_orientation * * ## 精确谓词 * * 分类阶段集成 GMP 精确谓词 (exact_orient3d, exact_point_in_polyhedron), * 使用自适应精度:先 double,不确定时自动升级到 GMP。 * * ## 退化检测(七类) * * 布尔运算前进行七类退化检测,避免因退化几何导致运算失败: * - detect_surface_overlap: 共面重叠检测 * - detect_curve_overlap: 共线重叠检测 * - detect_high_order_contact: G2+ 高阶接触检测 * - detect_boundary_contact: 交线-面边界接触检测 * - detect_vertex_contact: 多面汇聚接触检测 * - detect_non_manifold_contact: 非流形边检测 * * ## 精度追踪 * * PrecisionTracker 累积追踪每次布尔操作的精度损失, * 超阈值时自动触发容差升级。 * * @ingroup brep */ #include "vde/brep/brep.h" #include #include #include namespace vde::brep { // ═══════════════════════════════════════════════════════════ // 分类结果枚举 // ═══════════════════════════════════════════════════════════ /** @brief 点/面片段相对于实体的分类结果 */ enum ClassResult { IN = -1, ON = 0, OUT = 1 }; // ═══════════════════════════════════════════════════════════ // 精度追踪器 // ═══════════════════════════════════════════════════════════ /** * @brief 精度追踪器 * * 累积追踪每次布尔操作的精度损失。每次 SSI、分割、分类操作 * 后调用 record(),超阈值时触发容差升级策略。 * * 使用场景: * - 布尔操作开始前创建 tracker,传入后通过 accumulate 累积 * - accumulated_loss() 返回累积精度损失 * - exceeds_threshold(t) 判断是否超过给定阈值 * * @code{.cpp} * PrecisionTracker pt; * pt.record("ssi_compute", 1e-8); * pt.record("face_split", 2e-7); * if (pt.exceeds_threshold(1e-6)) { * // 触发容差升级 * } * @endcode */ class PrecisionTracker { public: /// 默认构造,累积损失初始为 0 PrecisionTracker() = default; /** * @brief 记录一次操作的精度损失 * @param operation 操作名称(用于诊断) * @param precision_loss 本次操作的精度损失(绝对值) */ void record(const std::string& operation, double precision_loss); /** * @brief 返回累积精度损失 */ [[nodiscard]] double accumulated_loss() const { return accumulated_; } /** * @brief 判断累积损失是否超过给定阈值 * @param threshold 精度阈值 * @return true 如果累计损失超过阈值 */ [[nodiscard]] bool exceeds_threshold(double threshold) const; /** * @brief 重置累积损失(新一轮布尔操作前调用) */ void reset(); /** * @brief 获取操作记录的条目数 */ [[nodiscard]] size_t record_count() const { return count_; } /** * @brief 获取最近一次记录的操作名称 */ [[nodiscard]] const std::string& last_operation() const { return last_op_; } private: double accumulated_ = 0.0; size_t count_ = 0; std::string last_op_; }; // ═══════════════════════════════════════════════════════════ // SSI 布尔运算结果 // ═══════════════════════════════════════════════════════════ /** * @brief SSI 布尔运算的诊断结果 * * 包含运算过程中的统计信息和诊断数据。 */ struct SSIBooleanResult { BrepModel result; ///< 运算结果模型 // ── 诊断信息 ── int num_ssi_curves = 0; ///< 计算的曲面交线总数 int num_fragments = 0; ///< 生成的面片段总数 int num_classified_in = 0; ///< 分类为 IN 的片段数 int num_classified_out = 0; ///< 分类为 OUT 的片段数 int num_classified_on = 0; ///< 分类为 ON 的片段数 int num_kept = 0; ///< 最终保留的片段数 int num_exact_upgrades = 0; ///< 升级到 GMP 精确谓词的次数 double ssi_time_ms = 0.0; ///< SSI 计算耗时 (ms) double split_time_ms = 0.0; ///< 面分割耗时 (ms) double classify_time_ms = 0.0; ///< 分类耗时 (ms) double sew_time_ms = 0.0; ///< 缝合耗时 (ms) std::string error_message; ///< 错误信息(空表示成功) /// 精度追踪器(累积布尔操作期间的精度损失) PrecisionTracker precision_tracker; /// 总耗时 (ms) [[nodiscard]] double total_time_ms() const { return ssi_time_ms + split_time_ms + classify_time_ms + sew_time_ms; } }; // ═══════════════════════════════════════════════════════════ // 退化检测数据结构 // ═══════════════════════════════════════════════════════════ /// 曲面重叠检测结果 struct SurfaceOverlapResult { bool overlapping = false; ///< 是否存在共面重叠 BrepModel overlap_region; ///< 重叠区域的面片段(用于2D布尔替代) }; /// 曲线重叠检测结果 struct CurveOverlapResult { bool overlapping = false; ///< 是否存在共线段 double t_start_a = 0.0; ///< 曲线A上重叠区间的起始参数 double t_end_a = 0.0; ///< 曲线A上重叠区间的终止参数 double t_start_b = 0.0; ///< 曲线B上重叠区间的起始参数 double t_end_b = 0.0; ///< 曲线B上重叠区间的终止参数 }; /// 高阶接触检测结果(G2+ 曲率连续接触) struct HighOrderContactResult { bool has_contact = false; ///< 是否存在高阶接触 Point3D contact_point; ///< 接触点 Vector3D offset_direction; ///< 需要微偏移的方向(沿法向) double offset_magnitude = 0.0; ///< 建议的偏移量 }; /// 边界接触检测结果 struct BoundaryContactResult { bool has_boundary_contact = false; ///< 是否存在边界接触 Point3D extended_start; ///< 延伸后的起点 Point3D extended_end; ///< 延伸后的终点 }; /// 顶点接触检测结果(多面汇聚于一点) struct VertexContactResult { bool has_vertex_contact = false; ///< 是否存在顶点接触 Point3D contact_point; ///< 汇聚点 std::vector involved_faces_a; ///< 模型A中涉及的面ID std::vector involved_faces_b; ///< 模型B中涉及的面ID double sphere_radius = 0.0; ///< 球面参数化半径(用于局部球面展开) }; /// 非流形接触检测结果 struct NonManifoldContactResult { bool is_non_manifold = false; ///< 是否为非流形边 int edge_id = -1; ///< 边ID std::vector incident_faces; ///< 与此边相邻的面ID列表 }; // ═══════════════════════════════════════════════════════════ // 退化检测函数 // ═══════════════════════════════════════════════════════════ /** * @brief 检测两面是否存在共面重叠 * * 比较两面法向量和面方程常数项。如果共面且包围盒有重叠, * 返回 true 表示应优先使用 2D 布尔替代 3D SSI。 * * @param a 模型A * @param b 模型B * @param face_a 模型A中的面ID * @param face_b 模型B中的面ID * @param tol 容差(角度容差弧度,距离容差) * @return 重叠检测结果 */ [[nodiscard]] SurfaceOverlapResult detect_surface_overlap( const BrepModel& a, const BrepModel& b, int face_a, int face_b, double tol = 1e-6); /** * @brief 检测两条交线是否共线重叠 * * 比较两条曲线的方向和位置。如果共线,返回重叠参数区间, * 用于去重或合并。 * * @param curve_a 曲线A上的采样点序列 * @param curve_b 曲线B上的采样点序列 * @param tol 共线容差 * @return 重叠检测结果(含重叠区间参数) */ [[nodiscard]] CurveOverlapResult detect_curve_overlap( const std::vector& curve_a, const std::vector& curve_b, double tol = 1e-6); /** * @brief 检测两面是否存在 G2+ 高阶接触 * * 高阶接触(曲率连续)会导致 SSI 数值不稳定。 * 检测到后返回微偏移方向和偏移量。 * * @param a 模型A * @param b 模型B * @param face_a 模型A中的面ID * @param face_b 模型B中的面ID * @param tol 接触检测容差 * @return 高阶接触检测结果 */ [[nodiscard]] HighOrderContactResult detect_high_order_contact( const BrepModel& a, const BrepModel& b, int face_a, int face_b, double tol = 1e-6); /** * @brief 检测交线端点是否触碰面边界 * * 当交线端点落在面边界上时,布尔分类容易出错。 * 检测到后返回延伸后的端点,使交线稍微超出面边界。 * * @param intersection_points 交线上的采样点序列 * @param a 模型A * @param b 模型B * @param face_a 模型A中的面ID * @param face_b 模型B中的面ID * @param tol 容差 * @return 边界接触检测结果(含延伸后的端点) */ [[nodiscard]] BoundaryContactResult detect_boundary_contact( const std::vector& intersection_points, const BrepModel& a, const BrepModel& b, int face_a, int face_b, double tol = 1e-6); /** * @brief 检测多面是否汇聚于一点 * * 当 3+ 个面共享一个接触点时,需要球面参数化 * 来处理局部拓扑。 * * @param intersection_points 交线上的采样点 * @param a 模型A * @param b 模型B * @param face_a 模型A中的面ID * @param face_b 模型B中的面ID * @param tol 容差 * @return 顶点接触检测结果 */ [[nodiscard]] VertexContactResult detect_vertex_contact( const std::vector& intersection_points, const BrepModel& a, const BrepModel& b, int face_a, int face_b, double tol = 1e-6); /** * @brief 检测边是否为非流形边(属于 3+ 个面) * * 非流形边会导致拓扑不一致,必须先检测后 * 使用特殊处理路径。 * * @param body B-Rep 模型 * @param edge_id 边ID * @return 非流形检测结果 */ [[nodiscard]] NonManifoldContactResult detect_non_manifold_contact( const BrepModel& body, int edge_id); // ═══════════════════════════════════════════════════════════ // 多点采样面片段分类 // ═══════════════════════════════════════════════════════════ /** * @brief 多点采样分类面片段(鲁棒性增强版本) * * 替换简单的 centroid 分类,采用多点投票策略: * 1. 基于面 bounding box 均匀撒 N = sqrt(面积/容差²) 个采样点(最少4个) * 2. 每个点独立调用 classify_point_adaptive() 分类 * 3. 投票:若 >75% 一致则返回结果;否则撒 2N 个点重新投票 * 4. 边界点(距离 < 容差)使用 exact_orient3d 判定 * * @param body 面片段所在模型 * @param other_body 分类参考模型(另一实体) * @param face_id 面片段ID * @param num_exact_upgrades [in/out] 升级到 GMP 精确谓词的计数 * @return IN / ON / OUT */ [[nodiscard]] ClassResult classify_face_fragment( const BrepModel& body, const BrepModel& other_body, int face_id, int& num_exact_upgrades); // ═══════════════════════════════════════════════════════════ // SSI 布尔运算 API // ═══════════════════════════════════════════════════════════ /** * @brief SSI 布尔并集: A ∪ B * * 合并两个 B-Rep 实体为一个。完整的 SSI 管线: * 1. 并行计算所有面对之间的曲面交线 * 2. 用交线分割面生成面片段 * 3. 射线投射 + 精确谓词分类每个面片段 * 4. 缝合保留的面片段并统一方向 * * @param a 第一个 B-Rep 实体 * @param b 第二个 B-Rep 实体 * @return SSIBooleanResult 含结果模型和诊断信息 * * @code{.cpp} * auto box = make_box(2, 2, 2); * auto sphere = make_sphere(1.5); * auto r = ssi_boolean_union(box, sphere); * if (r.error_message.empty()) { * // r.result 是有效的 B-Rep 模型 * } * @endcode */ [[nodiscard]] SSIBooleanResult ssi_boolean_union( const BrepModel& a, const BrepModel& b); /** * @brief SSI 布尔交集: A ∩ B * * 保留两个实体共有的区域。 * * @param a 第一个 B-Rep 实体 * @param b 第二个 B-Rep 实体 * @return SSIBooleanResult 含结果模型和诊断信息 */ [[nodiscard]] SSIBooleanResult ssi_boolean_intersection( const BrepModel& a, const BrepModel& b); /** * @brief SSI 布尔差集: A \\ B * * 从 A 中减去 B 所占据的区域。 * * @param a 被减实体 A * @param b 减去实体 B * @return SSIBooleanResult 含结果模型和诊断信息 */ [[nodiscard]] SSIBooleanResult ssi_boolean_difference( const BrepModel& a, const BrepModel& b); } // namespace vde::brep