#pragma once #include "vde/core/point.h" #include "vde/core/aabb.h" #include #include namespace vde::mesh { using core::AABB3D; using core::Vector3D; using core::Point3D; /** * @brief 半边数据结构元素 * * 每条半边存储:起点顶点索引、所属面索引、前驱/后继/对侧半边索引。 * 对侧半边通过 opposite_index 配对,相邻两半边索引总是 {2k, 2k+1}。 * * @ingroup mesh */ struct Halfedge { int vertex_index = -1; ///< 半边起点(终点 = next 半边的起点) int face_index = -1; ///< 所属面的索引(边界半边为 -1) int next_index = -1; ///< 沿面的下一条半边 int prev_index = -1; ///< 沿面的上一条半边 int opposite_index = -1; ///< 对侧半边索引(反方向半边) }; /** * @brief 面元素 * * 存储面的起始半边索引和边数(阶)。 * * @ingroup mesh */ struct Face { int halfedge_index = -1; ///< 该面的任意一条半边 int valence = 3; ///< 边数(三角面 = 3) }; /** * @brief 半边数据结构三角网格 * * 核心拓扑数据结构,支持 O(1) 访问面的邻面、顶点的邻面环、 * 边界检测等拓扑查询。数据内部一致性由构造方法保证。 * * 数据成员: * - vertices_: 顶点位置数组 * - halfedges_: 半边数组,相邻索引为对侧半边(0↔1, 2↔3, ...) * - faces_: 面数组,每个面对应一条起始半边 * - face_normals_ / vertex_normals_: 延迟计算的法向缓存 * * @ingroup mesh */ class HalfedgeMesh { public: HalfedgeMesh() = default; // ── Construction ────────────────────────────────────── /** * @brief 清空所有数据 */ void clear(); /** * @brief 添加顶点 * @param p 3D 坐标 * @return 新顶点的索引(0-based) */ int add_vertex(const Point3D& p); /** * @brief 添加面 * @param vertex_indices 按逆时针顺序排列的顶点索引(至少 3 个) * @return 新面的索引,失败返回 -1 * @note 自动创建/查找半边并设置对侧关系;面法向遵循右手定则 */ int add_face(const std::vector& vertex_indices); /** * @brief 从三角形列表批量构建半边形网格 * @param verts 顶点位置数组 * @param tris 三角形索引数组,每个 {i0,i1,i2} * @note 等价于反复调用 add_vertex + add_face,但内部做批处理优化 * @code{.cpp} * HalfedgeMesh mesh; * mesh.build_from_triangles(vertices, triangles); * @endcode */ void build_from_triangles(const std::vector& verts, const std::vector>& tris); // ── Accessors ───────────────────────────────────────── /** * @brief 顶点数量 * @return vertices_ 大小 */ [[nodiscard]] size_t num_vertices() const { return vertices_.size(); } /** * @brief 面数量 * @return faces_ 大小 */ [[nodiscard]] size_t num_faces() const { return faces_.size(); } /** * @brief 边数量 * @return halfedges_.size() / 2 */ [[nodiscard]] size_t num_edges() const { return halfedges_.size() / 2; } /** * @brief 访问顶点 * @param idx 顶点索引,0 ≤ idx < num_vertices() * @return 顶点坐标只读引用 */ [[nodiscard]] const Point3D& vertex(size_t idx) const { return vertices_[idx]; } /** * @brief 访问面 * @param idx 面索引 * @return Face 只读引用 */ [[nodiscard]] const Face& face(size_t idx) const { return faces_[idx]; } /** * @brief 访问半边 * @param idx 半边索引 * @return Halfedge 只读引用 */ [[nodiscard]] const Halfedge& halfedge(size_t idx) const { return halfedges_[idx]; } /** * @brief 更新顶点位置 * @param idx 顶点索引 * @param p 新坐标 * @note 标记法向缓存为脏,下次查询时重新计算 */ void set_vertex(size_t idx, const Point3D& p) { vertices_[idx] = p; normals_dirty_ = true; } // ── Topology queries ────────────────────────────────── /** * @brief 获取面的所有顶点索引(按环绕顺序) * @param fi 面索引 * @return 顶点索引序列 */ [[nodiscard]] std::vector face_vertices(int fi) const; /** * @brief 获取顶点的邻面索引环 * @param vi 顶点索引 * @return 所有以 vi 为顶点的面索引 */ [[nodiscard]] std::vector vertex_faces(int vi) const; /** * @brief 是否为边界半边 * @param hei 半边索引 * @return 没有所属面时为 true */ [[nodiscard]] bool is_boundary_edge(int hei) const { return halfedges_[hei].face_index < 0; } /** * @brief 是否为边界顶点 * @param vi 顶点索引 * @return 邻接任何边界半边时为 true */ [[nodiscard]] bool is_boundary_vertex(int vi) const; // ── Normals ─────────────────────────────────────────── /** * @brief 面法向(几何法向,非归一化?由实现决定) * @param fi 面索引 * @return (v1-v0)×(v2-v0) 归一化结果 */ [[nodiscard]] Vector3D face_normal(int fi) const; /** * @brief 顶点法向(邻面法向的面积加权平均) * @param vi 顶点索引 * @return 归一化顶点法向 */ [[nodiscard]] Vector3D vertex_normal(int vi) const; /** * @brief 重新计算所有面法向和顶点法向 * @note 修改顶点位置后自动标记脏位,调用此方法触发重新计算 */ void update_normals(); // ── Bounds ──────────────────────────────────────────── /** * @brief 网格包围盒 * @return AABB3D 轴对齐包围盒 */ [[nodiscard]] AABB3D bounds() const; // ── Iterators for range-based for ───────────────────── /** * @brief 面迭代器范围(支持 for-range) */ class FaceRange; /** * @brief 顶点单邻环迭代器 */ class VertexOneRing; /** * @brief 获取所有面范围(range-based for 支持) * @return FaceRange 对象 * @code{.cpp} * for (auto& f : mesh.faces_range()) { ... } * @endcode */ [[nodiscard]] FaceRange faces_range() const; /** * @brief 获取顶点的 1-ring 邻域迭代器 * @param vi 顶点索引 * @return VertexOneRing 对象 * @code{.cpp} * for (int vj : mesh.vertex_one_ring(vi)) { * // vj 是 vi 的直接邻居 * } * @endcode */ [[nodiscard]] VertexOneRing vertex_one_ring(int vi) const; private: std::vector vertices_; ///< 顶点位置 std::vector halfedges_; ///< 半边数组(成对存储) std::vector faces_; ///< 面数组 std::vector face_normals_; ///< 面法向缓存 std::vector vertex_normals_; ///< 顶点法向缓存 bool normals_dirty_ = true; ///< 法向脏标记 /** * @brief 查找或创建半边 (v0→v1) * @param v0 起点顶点索引 * @param v1 终点顶点索引 * @return 半边索引 */ int find_or_create_edge(int v0, int v1); }; } // namespace vde::mesh