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