Files
ViewDesignEngine/include/vde/mesh/halfedge_mesh.h
T
茂之钳 4c9ee4f760
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 38s
docs: doxygen annotations for curves + mesh + sketch
2026-07-24 11:04:04 +00:00

253 lines
7.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "vde/core/point.h"
#include "vde/core/aabb.h"
#include <vector>
#include <cstdint>
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<int>& 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<Point3D>& verts,
const std::vector<std::array<int, 3>>& 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<int> face_vertices(int fi) const;
/**
* @brief 获取顶点的邻面索引环
* @param vi 顶点索引
* @return 所有以 vi 为顶点的面索引
*/
[[nodiscard]] std::vector<int> 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<Point3D> vertices_; ///< 顶点位置
std::vector<Halfedge> halfedges_; ///< 半边数组(成对存储)
std::vector<Face> faces_; ///< 面数组
std::vector<Vector3D> face_normals_; ///< 面法向缓存
std::vector<Vector3D> 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