#pragma once /** * @file euler_op.h * @brief 欧拉操作 — B-Rep 拓扑编辑原语 * * 实现 Baumgart-Mäntylä 欧拉操作,是 B-Rep 底层拓扑编辑的标准原语集合。 * 每个操作保持欧拉-庞加莱公式不变: * V - E + F - (L - F) = 2(S - H) + R * * 其中 V=顶点数, E=边数, F=面数, L=环数, S=壳数, H=贯穿孔数, R=内环数 * * ## 操作清单 * * | 操作 | 含义 | ΔV | ΔE | ΔF | ΔL | * |-------|------|----|----|----|----| * | MEV | 边上插入顶点 | +1 | +1 | 0 | 0 | * | KEV | 移除边上顶点 | -1 | -1 | 0 | 0 | * | MEF | 面内建边分裂面 | 0 | +1 | +1 | +1 | * | KEF | 删除边合并面 | 0 | -1 | -1 | -1 | * | KEMR | 删边建内环 | 0 | -1 | 0 | 0 | * | MEKR | 建边删内环 | 0 | +1 | 0 | 0 | * * 六个操作涵盖所有拓扑编辑需求。对偶操作(MEV↔KEV, MEF↔KEF, KEMR↔MEKR) * 互为逆操作。 * * @ingroup brep */ #include "vde/brep/brep.h" #include #include #include namespace vde::brep { // ═══════════════════════════════════════════════════════════ // Euler operation result // ═══════════════════════════════════════════════════════════ /** * @brief 欧拉操作结果 * * 记录操作创建和删除的拓扑元素 ID。 * 成功时 created/deleted 字段根据操作类型填充。 * 失败时 error 字段包含错误信息。 */ struct EulerOpResult { bool success = false; std::string error; // Created elements (操作新建的) int new_vertex = -1; int new_edge = -1; int new_edge_2 = -1; int new_face = -1; int new_face_2 = -1; int new_loop = -1; int new_loop_2 = -1; // Deleted elements (操作移除的,保留供查询) int deleted_vertex = -1; int deleted_edge = -1; int deleted_face = -1; }; // ═══════════════════════════════════════════════════════════ // EulerOp — 欧拉操作引擎 // ═══════════════════════════════════════════════════════════ /** * @brief 欧拉操作引擎 * * 提供六个核心欧拉操作的静态方法。 * 所有操作在 BrepModel 上就地执行,保持拓扑一致性。 * * 使用模式:欧拉操作后应调用 euler_poincare() 验证公式不变。 */ class EulerOp { public: // ─── 操作 1: MEV — Make Edge Vertex ─── /** * @brief 在边上插入顶点,将边分裂为两条边 * * @code * Before: V1 ────E──── V2 * After: V1 ─E1─ Vnew ─E2─ V2 * @endcode * * @param body 目标 B-Rep 模型 * @param edge_idx 要分裂的边索引 * @param t 插入位置参数 (0 < t < 1),0=起点 1=终点 * @return 操作结果(含 new_vertex, new_edge=E1, new_edge_2=E2) */ static EulerOpResult mev(BrepModel& body, int edge_idx, double t); // ─── 操作 2: KEV — Kill Edge Vertex ─── /** * @brief 移除顶点并合并两侧边 * * 逆操作:MEV。 * * @code * Before: Va ─E1─ Vb ─E2─ Vc * After: Va ────Enew──── Vc * @endcode * * 要求 E1 和 E2 共线,Vb 的度为 2。 * * @param body 目标 B-Rep 模型 * @param vertex_idx 要移除的顶点索引 * @return 操作结果(含 new_edge=Enew, deleted_vertex=Vb) */ static EulerOpResult kev(BrepModel& body, int vertex_idx); // ─── 操作 3: MEF — Make Edge Face ─── /** * @brief 在面内建边,将面分裂为两个面 * * @code * Before: ┌─────────────┐ * After: ├──────┬──────┤ (新边垂直分割面) * @endcode * * @param body 目标 B-Rep 模型 * @param face_idx 要分裂的面索引 * @param va_idx 新边起点顶点索引(必须在面内) * @param vb_idx 新边终点顶点索引(必须在面内) * @return 操作结果(含 new_edge, new_face, new_face_2) */ static EulerOpResult mef(BrepModel& body, int face_idx, int va_idx, int vb_idx); // ─── 操作 4: KEF — Kill Edge Face ─── /** * @brief 删除边并合并两侧面 * * 逆操作:MEF。 * * @code * Before: Fa │ E │ Fb (E 两侧是不同面) * After: Fa+Fb merged * @endcode * * 要求边的两个相邻面共面。 * * @param body 目标 B-Rep 模型 * @param edge_idx 要删除的边索引 * @return 操作结果(含 new_face, deleted_edge, deleted_face) */ static EulerOpResult kef(BrepModel& body, int edge_idx); // ─── 操作 5: KEMR — Kill Edge Make Ring ─── /** * @brief 删除内环上的边,合并内环 * * @code * Before: ┌─────┐ 内环 ┌──┐ * After: ┌──────┐ (内环扩大) * @endcode * * 要求边两侧是同一个面(即边在内环上)。 * * @param body 目标 B-Rep 模型 * @param edge_idx 要删除的边索引(必须在面的内环上) * @return 操作结果 */ static EulerOpResult kemr(BrepModel& body, int edge_idx); // ─── 操作 6: MEKR — Make Edge Kill Ring ─── /** * @brief 在内环上建边,分割内环 * * 逆操作:KEMR。 * * @param body 目标 B-Rep 模型 * @param face_idx 包含内环的面 * @param va_idx 内环上的起点顶点 * @param vb_idx 内环上的终点顶点(同内环) * @return 操作结果(含 new_edge) */ static EulerOpResult mekr(BrepModel& body, int face_idx, int va_idx, int vb_idx); // ─── 验证 ─── /** * @brief 计算欧拉-庞加莱特征数 * * EP = V - E + F - (L - F) = 2(S - H) + R * * 对于简单封闭实体(S=1, H=0, R=0):EP = 2 * * @return 特征数 */ [[nodiscard]] static int euler_poincare(const BrepModel& body); /** * @brief 验证模型满足欧拉-庞加莱公式 * * 计算 EP 并与期望值比较。对简单实体期望 EP=2。 * * @return true 如果公式成立 */ [[nodiscard]] static bool verify_euler(const BrepModel& body); /// 计算顶点度(相连边数) static int vertex_degree(const BrepModel& body, int vertex_idx); private: /// 获取顶点所在面的边界环索引(-1 表示不在任何环中) static int find_vertex_in_loop(const BrepModel& body, int vertex_idx, const TopoLoop& loop); /// 在环中查找顶点出现的位置 static std::vector find_vertex_positions_in_loop( const BrepModel& body, int vertex_idx, const TopoLoop& loop); /// 重建壳和体以引用新的面映射 static void rebuild_shells_with_new_faces(BrepModel& body, const std::map& face_map); }; } // namespace vde::brep