docs: doxygen annotations for curves + mesh + sketch
This commit is contained in:
@@ -8,12 +8,45 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/// Alpha Shapes: extract surface from point cloud
|
||||
/// For alpha → ∞, returns convex hull; for alpha → 0, returns all faces
|
||||
/// @param alpha Radius threshold: faces with circumradius > alpha are removed
|
||||
/**
|
||||
* @brief Alpha Shapes 点云表面重建
|
||||
*
|
||||
* 从 3D 点云的 Delaunay 四面体化中提取表面,通过参数 α 控制细节级别:
|
||||
* - α → ∞:返回凸包(所有边界四面体面都保留)
|
||||
* - α → 0:返回所有 Delaunay 面(非常详细,含内部结构)
|
||||
* - 中间值:剔除外接球半径 > α 的四面体,保留细节适中的表面
|
||||
*
|
||||
* 算法:对每个 Delaunay 四面体,检查其外接球半径。
|
||||
* 若半径 ≤ α,四面体保留;否则剔除。最终表面 = 保留四面体与
|
||||
* 被剔除四面体(或外部)之间的边界三角形面。
|
||||
*
|
||||
* @param points 输入 3D 点云
|
||||
* @param alpha α 半径阈值,通常取平均点间距的 1~3 倍
|
||||
* @return TetrahedronMesh 保留的四面体网格
|
||||
*
|
||||
* @code{.cpp}
|
||||
* // 从点云重建表面
|
||||
* auto tets = alpha_shapes(point_cloud, 0.5);
|
||||
* auto [verts, tris] = alpha_shapes_surface(tets);
|
||||
* @endcode
|
||||
* @see alpha_shapes_surface delaunay_3d
|
||||
* @ingroup mesh
|
||||
*/
|
||||
TetrahedronMesh alpha_shapes(const std::vector<Point3D>& points, double alpha);
|
||||
|
||||
/// Convert alpha shapes tet mesh to triangle surface
|
||||
/**
|
||||
* @brief 将 Alpha Shapes 四面体网格的表面提取为三角形网格
|
||||
*
|
||||
* 遍历四面体网格,提取所有仅出现在一个四面体上的面(边界面),
|
||||
* 即为重建的表面三角形。
|
||||
*
|
||||
* @param tet_mesh Alpha Shapes 输出的四面体网格
|
||||
* @return {顶点数组, 三角形索引数组}
|
||||
*
|
||||
* @note 表面三角形法向指向四面体外部(右手定则)
|
||||
* @see alpha_shapes
|
||||
* @ingroup mesh
|
||||
*/
|
||||
std::pair<std::vector<Point3D>, std::vector<std::array<int,3>>>
|
||||
alpha_shapes_surface(const TetrahedronMesh& tet_mesh);
|
||||
|
||||
|
||||
@@ -8,8 +8,29 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/// Constrained Delaunay Triangulation (CDT) in 2D
|
||||
/// Ensures specified constraint edges appear in the triangulation
|
||||
/**
|
||||
* @brief 约束 Delaunay 三角化(CDT)
|
||||
*
|
||||
* 在标准 Delaunay 三角化基础上强制指定的约束边必须出现在三角化中。
|
||||
* 通过边翻转(edge flip)反复消除与约束边相交的三角形边,直到所有
|
||||
* 约束边都成为三角形边。
|
||||
*
|
||||
* 约束边在输出三角化中一定存在(作为三角形边),但不保证满足
|
||||
* 空圆性质——靠近约束边的三角形可能违反 Delaunay 条件。
|
||||
*
|
||||
* @param points 输入 2D 点集
|
||||
* @param constraints 约束边列表,每项为 {起点索引, 终点索引}
|
||||
* @return DelaunayResult 含约束边的三角化结果
|
||||
*
|
||||
* @note 约束边不能相交(否则行为未定义);自相交约束需先分割交点
|
||||
* @code{.cpp}
|
||||
* // 带孔洞的三角化:约束边标记孔洞边界
|
||||
* auto result = constrained_delaunay_2d(points,
|
||||
* {{0,1}, {1,2}, {2,1}, {3,0}});
|
||||
* @endcode
|
||||
* @see delaunay_2d
|
||||
* @ingroup mesh
|
||||
*/
|
||||
DelaunayResult constrained_delaunay_2d(
|
||||
const std::vector<Point2D>& points,
|
||||
const std::vector<std::pair<int, int>>& constraints);
|
||||
|
||||
@@ -8,12 +8,40 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief 2D Delaunay 三角化结果
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct DelaunayResult {
|
||||
std::vector<Point2D> vertices;
|
||||
std::vector<std::array<int, 3>> triangles; // indices into vertices
|
||||
std::vector<Point2D> vertices; ///< 顶点坐标(可能含超级三角形的辅助点)
|
||||
std::vector<std::array<int, 3>> triangles; ///< 三角形索引(CCW 顺序)
|
||||
};
|
||||
|
||||
/// Bowyer-Watson incremental Delaunay triangulation, O(n²) worst, O(n log n) typical
|
||||
/**
|
||||
* @brief 2D Delaunay 三角化(Bowyer-Watson 增量算法)
|
||||
*
|
||||
* 构建点集的 Delaunay 三角化:任意三角形的外接圆内不含其他点。
|
||||
* 该性质使三角化的最小角最大化,避免狭长三角形。
|
||||
*
|
||||
* 算法流程:
|
||||
* 1. 创建包围所有输入点的超级三角形
|
||||
* 2. 逐点插入:
|
||||
* a. 找出外接圆包含新点的所有三角形("坏三角形")
|
||||
* b. 删除坏三角形,形成多边形空洞
|
||||
* c. 将新点与空洞边界各边连接形成新三角形
|
||||
* 3. 删除与超级三角形顶点相关的三角形
|
||||
*
|
||||
* @param points 输入 2D 点集(至少 3 个不共线点)
|
||||
* @return DelaunayResult 三角化结果
|
||||
*
|
||||
* @note 时间复杂度:平均 O(n log n),最坏 O(n²)
|
||||
* @code{.cpp}
|
||||
* auto result = delaunay_2d({{0,0}, {1,0}, {0.5, 0.5}, {0,1}, {1,1}});
|
||||
* // result.triangles 为 Delaunay 三角形索引
|
||||
* @endcode
|
||||
* @see constrained_delaunay_2d
|
||||
* @ingroup mesh
|
||||
*/
|
||||
DelaunayResult delaunay_2d(const std::vector<Point2D>& points);
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
@@ -8,12 +8,35 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief 四面体网格结构
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct TetrahedronMesh {
|
||||
std::vector<Point3D> vertices;
|
||||
std::vector<std::array<int, 4>> tetrahedra; // 4 vertex indices
|
||||
std::vector<Point3D> vertices; ///< 顶点坐标
|
||||
std::vector<std::array<int, 4>> tetrahedra; ///< 四面体索引,每个 {i0,i1,i2,i3}
|
||||
};
|
||||
|
||||
/// 3D Delaunay tetrahedralization (Bowyer-Watson)
|
||||
/**
|
||||
* @brief 3D Delaunay 四面体化(Bowyer-Watson 增量算法)
|
||||
*
|
||||
* 将 3D 点集剖分为 Delaunay 四面体网格:任意四面体的外接球内不含其他顶点。
|
||||
* 算法是 2D 版本的自然推广:
|
||||
* 1. 构建超级四面体包含所有点
|
||||
* 2. 逐点插入:删除外接球包含新点的四面体 → 空洞 → 重连
|
||||
* 3. 去除超级四面体相关单元
|
||||
*
|
||||
* @param points 输入 3D 点集(至少 4 个不共面点)
|
||||
* @return TetrahedronMesh 四面体网格
|
||||
*
|
||||
* @note 时间复杂度与 2D 类似:平均 O(n log n),最坏 O(n²)
|
||||
* @note 输出包括内部和边界四面体;通过 alpha_shapes 可提取表面
|
||||
* @code{.cpp}
|
||||
* auto tet_mesh = delaunay_3d(point_cloud);
|
||||
* @endcode
|
||||
* @see alpha_shapes
|
||||
* @ingroup mesh
|
||||
*/
|
||||
TetrahedronMesh delaunay_3d(const std::vector<Point3D>& points);
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
@@ -7,10 +7,32 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/// Geodesic distance from source vertices using the Heat Method (Crane et al. 2013)
|
||||
/// Returns per-vertex distance from the nearest source
|
||||
/// @param sources Source vertex indices
|
||||
/// @param t Time step (default = average edge length squared)
|
||||
/**
|
||||
* @brief 热方法(Heat Method)计算测地距离
|
||||
*
|
||||
* 基于 Crane et al. 2013 的算法,通过求解三个线性系统高效计算
|
||||
* 曲面上任意点到源点集的测地距离(Geodesic Distance)。
|
||||
*
|
||||
* 算法三步:
|
||||
* 1. 热扩散:求解 (I - t·L)·u = u₀,其中 u₀ 在源点为 1 其余为 0
|
||||
* 2. 梯度归一化:X = -∇u / |∇u|
|
||||
* 3. 泊松重建:L·φ = div(X),得到测地距离 φ
|
||||
*
|
||||
* 相比精确测地线算法(如 MMP),热方法更快(~线性时间)且易于实现,
|
||||
* 但精度受时间步长 t 和网格分辨率影响。
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @param sources 源顶点索引列表(距离为 0 的顶点)
|
||||
* @param t 时间步长,< 0 时自动设为平均边长平方
|
||||
* @return 每顶点到最近源点的测地距离
|
||||
*
|
||||
* @note 要求网格为连通流形;时间步长 t 越小精度越高但数值越不稳定
|
||||
* @code{.cpp}
|
||||
* auto dist = geodesic_distance(mesh, {0, 10, 20});
|
||||
* // dist[i] = 顶点 i 到 {0,10,20} 中最近顶点的测地距离
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
std::vector<double> geodesic_distance(const HalfedgeMesh& mesh,
|
||||
const std::vector<int>& sources,
|
||||
double t = -1.0);
|
||||
|
||||
@@ -9,67 +9,243 @@ using core::AABB3D;
|
||||
using core::Vector3D;
|
||||
using core::Point3D;
|
||||
|
||||
/**
|
||||
* @brief 半边数据结构元素
|
||||
*
|
||||
* 每条半边存储:起点顶点索引、所属面索引、前驱/后继/对侧半边索引。
|
||||
* 对侧半边通过 opposite_index 配对,相邻两半边索引总是 {2k, 2k+1}。
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct Halfedge {
|
||||
int vertex_index = -1;
|
||||
int face_index = -1;
|
||||
int next_index = -1;
|
||||
int prev_index = -1;
|
||||
int opposite_index = -1;
|
||||
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;
|
||||
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 ──
|
||||
// ── 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 ──
|
||||
// ── 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 ──
|
||||
// ── 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 ──
|
||||
// ── 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 ──
|
||||
// ── Bounds ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @brief 网格包围盒
|
||||
* @return AABB3D 轴对齐包围盒
|
||||
*/
|
||||
[[nodiscard]] AABB3D bounds() const;
|
||||
|
||||
// ── Iterators for range-based for ──
|
||||
// ── 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;
|
||||
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);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,30 +3,82 @@
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace vde::mesh {
|
||||
using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief Marching Cubes 输出网格
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct MCMesh {
|
||||
std::vector<Point3D> vertices;
|
||||
std::vector<std::array<int, 3>> triangles;
|
||||
std::vector<Point3D> vertices; ///< 顶点坐标
|
||||
std::vector<std::array<int, 3>> triangles; ///< 三角形索引
|
||||
};
|
||||
|
||||
/// Marching Cubes for scalar field f(x,y,z)
|
||||
/// Extracts isosurface at value = iso_level within the bounding box
|
||||
/**
|
||||
* @brief Marching Cubes 等值面提取
|
||||
*
|
||||
* 从标量场 f(x,y,z) 中提取等值面 S = { (x,y,z) | f(x,y,z) = iso_level }。
|
||||
* 将包围盒划分为 resolution×resolution×resolution 体素网格,
|
||||
* 在每个体素的 8 个角求值 f,查表确定等值面穿越该体素的三角形配置。
|
||||
*
|
||||
* @param f 标量场函数 f(x,y,z) → double
|
||||
* @param iso_level 等值面值(默认 0)
|
||||
* @param bmin 包围盒最小角
|
||||
* @param bmax 包围盒最大角
|
||||
* @param resolution 每轴采样分辨率(≥ 1),总网格 = resolution³ 个立方体
|
||||
* @return MCMesh 三角形网格
|
||||
*
|
||||
* @note 使用经典的 256 查表法(Lorensen & Cline 1987)
|
||||
* @code{.cpp}
|
||||
* // 提取球面等值面
|
||||
* auto f = [](double x,double y,double z) {
|
||||
* return sdf_sphere(x,y,z, 0,0,0, 1.0);
|
||||
* };
|
||||
* auto mesh = marching_cubes(f, 0.0,
|
||||
* {-2,-2,-2}, {2,2,2}, 64);
|
||||
* @endcode
|
||||
* @see sdf_sphere sdf_box
|
||||
* @ingroup mesh
|
||||
*/
|
||||
MCMesh marching_cubes(const std::function<double(double,double,double)>& f,
|
||||
double iso_level,
|
||||
const Point3D& bmin, const Point3D& bmax,
|
||||
int resolution);
|
||||
|
||||
/// SDF sphere
|
||||
/**
|
||||
* @brief SDF 球体函数
|
||||
*
|
||||
* @param x,y,z 采样点坐标
|
||||
* @param cx,cy,cz 球心坐标
|
||||
* @param r 半径
|
||||
* @return 有符号距离:内部 < 0,表面 = 0,外部 > 0
|
||||
*
|
||||
* @code{.cpp}
|
||||
* auto sphere_sdf = [](double x,double y,double z) {
|
||||
* return sdf_sphere(x,y,z, 0,0,0, 1.0);
|
||||
* };
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
inline double sdf_sphere(double x, double y, double z, double cx, double cy, double cz, double r) {
|
||||
return std::sqrt((x-cx)*(x-cx) + (y-cy)*(y-cy) + (z-cz)*(z-cz)) - r;
|
||||
}
|
||||
|
||||
/// SDF box
|
||||
/**
|
||||
* @brief SDF 立方体函数(轴对齐,中心在原点)
|
||||
*
|
||||
* @param x,y,z 采样点坐标
|
||||
* @param hx,hy,hz 半边长
|
||||
* @return 有符号距离
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
inline double sdf_box(double x, double y, double z, double hx, double hy, double hz) {
|
||||
double dx = std::abs(x) - hx, dy = std::abs(y) - hy, dz = std::abs(z) - hz;
|
||||
return std::sqrt(std::max(dx,0.0)*std::max(dx,0.0)
|
||||
@@ -35,14 +87,34 @@ inline double sdf_box(double x, double y, double z, double hx, double hy, double
|
||||
+ std::min(std::max({dx,dy,dz}), 0.0);
|
||||
}
|
||||
|
||||
/// SDF smooth union
|
||||
/**
|
||||
* @brief SDF 平滑并集(Smooth Union)
|
||||
*
|
||||
* 对两个 SDF 做平滑混合,k 控制过渡区宽度。
|
||||
*
|
||||
* @param d1,d2 两个 SDF 值
|
||||
* @param k 平滑宽度(> 0),值越大过渡越平滑
|
||||
* @return 混合后的 SDF 值
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
namespace { inline double lerp_impl(double a, double b, double t) { return a + t * (b - a); } }
|
||||
inline double sdf_smooth_union(double d1, double d2, double k) {
|
||||
double h = std::clamp(0.5 + 0.5*(d2-d1)/k, 0.0, 1.0);
|
||||
return lerp_impl(d2, d1, h) - k*h*(1.0-h);
|
||||
}
|
||||
|
||||
/// SDF smooth subtraction
|
||||
/**
|
||||
* @brief SDF 平滑差集(Smooth Subtraction)
|
||||
*
|
||||
* d1 减去 d2,过渡区由 k 控制。
|
||||
*
|
||||
* @param d1,d2 两个 SDF 值
|
||||
* @param k 平滑宽度(> 0)
|
||||
* @return 混合后的 SDF 值
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
inline double sdf_smooth_subtraction(double d1, double d2, double k) {
|
||||
double h = std::clamp(0.5 - 0.5*(d2+d1)/k, 0.0, 1.0);
|
||||
return lerp_impl(d2, -d1, h) + k*h*(1.0-h);
|
||||
|
||||
@@ -6,8 +6,39 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
enum class BooleanOp { Union, Intersection, Difference, SymDiff };
|
||||
/**
|
||||
* @brief 布尔运算类型(CSG 组合)
|
||||
* @ingroup mesh
|
||||
*/
|
||||
enum class BooleanOp {
|
||||
Union, ///< A ∪ B 并集
|
||||
Intersection, ///< A ∩ B 交集
|
||||
Difference, ///< A \ B 差集
|
||||
SymDiff ///< A Δ B 对称差集
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 三角网格布尔运算
|
||||
*
|
||||
* 对两个封闭三角网格执行 CSG(Constructive Solid Geometry)布尔操作。
|
||||
* 内部流程:
|
||||
* 1. 计算两个网格的三角形-三角形交线
|
||||
* 2. 沿交线细分三角形(conforming 三角化)
|
||||
* 3. 根据布尔类型对每个子面片做内/外分类
|
||||
* 4. 缝合属于结果的部分形成新网格
|
||||
*
|
||||
* @param a 第一个三角网格(必须为封闭流形)
|
||||
* @param b 第二个三角网格(必须为封闭流形)
|
||||
* @param op 布尔运算类型
|
||||
* @return 结果三角网格
|
||||
*
|
||||
* @note 要求输入网格为封闭流形(watertight),否则分类不可靠
|
||||
* @code{.cpp}
|
||||
* auto result = mesh_boolean(cube, sphere, BooleanOp::Difference);
|
||||
* // result = 立方体减去球体的交集部分
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh mesh_boolean(const HalfedgeMesh& a, const HalfedgeMesh& b, BooleanOp op);
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
@@ -7,15 +7,42 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief 离散曲率计算结果
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct CurvatureResult {
|
||||
std::vector<double> gaussian; // per-vertex Gaussian curvature
|
||||
std::vector<double> mean; // per-vertex mean curvature
|
||||
std::vector<double> area; // per-vertex Voronoi area
|
||||
std::vector<double> gaussian; ///< 每顶点高斯曲率 K = κ₁·κ₂
|
||||
std::vector<double> mean; ///< 每顶点平均曲率 H = (κ₁+κ₂)/2
|
||||
std::vector<double> area; ///< 每顶点 Voronoi 面积(曲率归一化权重)
|
||||
};
|
||||
|
||||
/// Discrete curvature using Cotan formula (Meyer et al. 2003)
|
||||
/// Gaussian: K(v) = (2π - Σθ_j) / A_v (interior vertices)
|
||||
/// Mean: H(v) = ||Σ(cot α + cot β)·e|| / (2·A_v)
|
||||
/**
|
||||
* @brief 离散曲率计算(Cotan 公式,Meyer et al. 2003)
|
||||
*
|
||||
* 对三角网格每个顶点计算离散高斯曲率和平均曲率。
|
||||
*
|
||||
* 高斯曲率(内点):
|
||||
* K(v) = (2π - Σ_j θ_j) / A_v
|
||||
* 其中 θ_j 是顶点 v 处面角的总和,A_v 是 Voronoi 面积
|
||||
*
|
||||
* 平均曲率:
|
||||
* H(v) = || Σ_j (cot α_j + cot β_j) · e_j || / (2 · A_v)
|
||||
* 其中 α_j,β_j 是与边 e_j 相对的两个角
|
||||
*
|
||||
* 边界顶点使用角度缺损公式修正。
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @return CurvatureResult 每顶点曲率
|
||||
*
|
||||
* @note 要求网格为流形;边界顶点曲率使用近似公式
|
||||
* @code{.cpp}
|
||||
* auto crv = compute_curvature(mesh);
|
||||
* double max_curvature = *std::max_element(crv.gaussian.begin(), crv.gaussian.end());
|
||||
* // 可用于特征检测、网格分割等
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
CurvatureResult compute_curvature(const HalfedgeMesh& mesh);
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
@@ -8,12 +8,62 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/// Tutte embedding (harmonic parameterization) for a mesh with fixed boundary
|
||||
/// Boundary vertices are mapped to a circle; interior vertices are solved via Laplacian
|
||||
/// Returns per-vertex UV coordinates as Point2D
|
||||
/**
|
||||
* @brief Tutte 参数化(调和参数化)
|
||||
*
|
||||
* 将边界顶点固定映射到单位圆上,内部顶点通过求解 Laplace 方程
|
||||
* Δu = 0, Δv = 0 得到 UV 坐标。这是最经典、最稳定的网格参数化方法。
|
||||
*
|
||||
* 算法:
|
||||
* 1. 检测网格边界环
|
||||
* 2. 将边界顶点按弧长比例映射到单位圆上
|
||||
* 3. 对每个内部顶点求解均匀 Laplace(权重 w_ij = 1):
|
||||
* v_i = Σ_j v_j / deg(i)
|
||||
* 4. 构造稀疏线性系统 LU 求解
|
||||
*
|
||||
* 优点:极稳定,保证无翻转(对凸边界网格)
|
||||
* 缺点:边界固定,内部可能扭曲(非共形)
|
||||
*
|
||||
* @param mesh 输入三角网格(需含边界)
|
||||
* @return 每顶点 UV 坐标(Point2D),顺序与 mesh 顶点一致
|
||||
*
|
||||
* @note 网格必须有边界(开网格);闭曲面需先切割为拓扑圆盘
|
||||
* @code{.cpp}
|
||||
* auto uv = tutte_parameterization(mesh);
|
||||
* // uv[i] 为顶点 i 的 (u,v) 坐标,范围大致在 [-1,1]²
|
||||
* @endcode
|
||||
* @see lscm_parameterization
|
||||
* @ingroup mesh
|
||||
*/
|
||||
[[nodiscard]] std::vector<Point2D> tutte_parameterization(const HalfedgeMesh& mesh);
|
||||
|
||||
/// LSCM (Least Squares Conformal Maps) — simplified version
|
||||
/**
|
||||
* @brief LSCM(Least Squares Conformal Maps)参数化
|
||||
*
|
||||
* 基于 Lévy et al. 2002 的最小二乘共形映射。通过最小化共形能量
|
||||
* 使参数化尽可能保角(角度畸变小),同时允许边界自由。
|
||||
*
|
||||
* 与 Tutte 的区别:
|
||||
* - 无需固定边界 → 边界自然展开,畸变更小
|
||||
* - 需要固定至少 2 个顶点消除刚体自由度(内部处理)
|
||||
* - 共形性更好,适合纹理映射
|
||||
* - 不保证无翻转(局部极小可能翻转)
|
||||
*
|
||||
* 能量函数:
|
||||
* E(u,v) = Σ_T || ∇(u+iv) ||² · A_T
|
||||
* 对每个三角形 T 最小化梯度扰动平方和
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @return 每顶点 UV 坐标(Point2D)
|
||||
*
|
||||
* @note 简化实现,不包含完整 Lévy 论文中所有的退化处理
|
||||
* @code{.cpp}
|
||||
* auto uv = lscm_parameterization(mesh);
|
||||
* // 适合纹理 mapping
|
||||
* @endcode
|
||||
* @see tutte_parameterization
|
||||
* @ingroup mesh
|
||||
*/
|
||||
[[nodiscard]] std::vector<Point2D> lscm_parameterization(const HalfedgeMesh& mesh);
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
+167
-47
@@ -3,93 +3,213 @@
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
namespace vde::mesh {
|
||||
using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
// ── Triangle quality ────────────────────────────────
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Triangle quality
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 三角网格质量指标
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct MeshQuality {
|
||||
double min_angle_deg = 0;
|
||||
double max_angle_deg = 0;
|
||||
double avg_aspect_ratio = 0;
|
||||
double max_aspect_ratio = 0;
|
||||
size_t degenerate_faces = 0;
|
||||
double min_angle_deg = 0; ///< 最小三角形内角(度),≥ 0
|
||||
double max_angle_deg = 0; ///< 最大三角形内角(度),≤ 180
|
||||
double avg_aspect_ratio = 0; ///< 平均长宽比(外接圆半径 / 最短边)
|
||||
double max_aspect_ratio = 0; ///< 最大长宽比
|
||||
size_t degenerate_faces = 0; ///< 退化面数(面积 ≈ 0)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 评估三角网格质量
|
||||
*
|
||||
* 对网格中每个三角面计算内角、长宽比,统计全局最值及平均值。
|
||||
* 质量好的三角网格应有:
|
||||
* - min_angle > 20°(CFD 模拟要求 > 25°)
|
||||
* - max_angle < 120°(避免狭长退化)
|
||||
* - avg_aspect_ratio 接近 1
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @return MeshQuality 质量指标汇总
|
||||
*
|
||||
* @code{.cpp}
|
||||
* auto q = evaluate_mesh_quality(mesh);
|
||||
* if (q.min_angle_deg < 15.0) {
|
||||
* // 需要 remesh 或优化
|
||||
* }
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
MeshQuality evaluate_mesh_quality(const HalfedgeMesh& mesh);
|
||||
|
||||
// ── Tetrahedron quality ─────────────────────────────
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Tetrahedron quality
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 四面体网格质量指标
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct TetQuality {
|
||||
double min_jacobian = 0.0; // scaled Jacobian (normalised, 0=b−ad … 1=regular)
|
||||
double max_jacobian = 0.0;
|
||||
double avg_jacobian = 0.0;
|
||||
double min_dihedral_deg = 0.0; // minimum dihedral angle (degrees)
|
||||
double max_dihedral_deg = 0.0; // maximum dihedral angle
|
||||
double max_aspect_ratio = 0.0; // circumsphere-radius / shortest-edge
|
||||
size_t degenerate_tets = 0;
|
||||
size_t total_tets = 0;
|
||||
double min_jacobian = 0.0; ///< 最小归一化 Jacobian [0, 1],0=退化 1=正四面体
|
||||
double max_jacobian = 0.0; ///< 最大归一化 Jacobian
|
||||
double avg_jacobian = 0.0; ///< 平均归一化 Jacobian
|
||||
double min_dihedral_deg = 0.0; ///< 最小二面角(度),太小会导致刚度矩阵病态
|
||||
double max_dihedral_deg = 0.0; ///< 最大二面角(度),接近 180° 表示退化
|
||||
double max_aspect_ratio = 0.0; ///< 最大长宽比(外接球半径 / 最短边)
|
||||
size_t degenerate_tets = 0; ///< 退化四面体数(体积 ≈ 0 或 Jacobian ≈ 0)
|
||||
size_t total_tets = 0; ///< 四面体总数
|
||||
};
|
||||
|
||||
/// Evaluate tetrahedron quality from a triangle mesh.
|
||||
/// This function interprets the mesh as a tetrahedral mesh where
|
||||
/// each face belongs to a tet defined by the face vertices + face centroid
|
||||
/// (suitable for closed triangle surfaces interpreted as volume boundaries).
|
||||
/**
|
||||
* @brief 评估三角网格作为四面体边界的质量
|
||||
*
|
||||
* 将每个三角面与其面重心构造虚拟四面体,评估这些四面体的质量。
|
||||
* 适用于将封闭三角表面解释为体积边界时的质量检测。
|
||||
*
|
||||
* @param mesh 封闭三角网格(volume boundary)
|
||||
* @return TetQuality 四面体质量指标
|
||||
*
|
||||
* @note 这不是真正的四面体网格质量评估——仅用于表面网格的代理指标
|
||||
* @ingroup mesh
|
||||
*/
|
||||
TetQuality evaluate_tet_quality(const HalfedgeMesh& mesh);
|
||||
|
||||
// ── Generic element quality ─────────────────────────
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Generic element quality
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
enum class ElementType { Tri, Quad, Tet, Hex };
|
||||
|
||||
struct ElemQuality {
|
||||
double min_jacobian = 0.0;
|
||||
double max_jacobian = 0.0;
|
||||
double avg_jacobian = 0.0;
|
||||
size_t total_elements = 0;
|
||||
size_t degenerate = 0;
|
||||
/**
|
||||
* @brief 单元类型枚举
|
||||
* @ingroup mesh
|
||||
*/
|
||||
enum class ElementType {
|
||||
Tri, ///< 三角形
|
||||
Quad, ///< 四边形(Stub)
|
||||
Tet, ///< 四面体
|
||||
Hex ///< 六面体(Stub)
|
||||
};
|
||||
|
||||
/// Dispatch by element type; implemented for Tri and Tet (Quad/Hex return stub).
|
||||
/**
|
||||
* @brief 通用单元质量指标
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct ElemQuality {
|
||||
double min_jacobian = 0.0; ///< 最小归一化 Jacobian
|
||||
double max_jacobian = 0.0; ///< 最大归一化 Jacobian
|
||||
double avg_jacobian = 0.0; ///< 平均归一化 Jacobian
|
||||
size_t total_elements = 0; ///< 单元总数
|
||||
size_t degenerate = 0; ///< 退化单元数
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 按单元类型分派的网格质量评估
|
||||
*
|
||||
* @param mesh 输入网格
|
||||
* @param type 单元类型
|
||||
* @return ElemQuality 质量指标
|
||||
*
|
||||
* @note 目前仅 Tri 和 Tet 完整实现;Quad / Hex 返回 stub(全零)
|
||||
* @ingroup mesh
|
||||
*/
|
||||
ElemQuality evaluate_element_quality(const HalfedgeMesh& mesh, ElementType type);
|
||||
|
||||
// ── Quality report ──────────────────────────────────
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Quality report
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* @brief 详细质量报告
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct QualityReport {
|
||||
std::string element_type; // "triangle" / "tetrahedron"
|
||||
size_t total = 0;
|
||||
size_t degenerate = 0;
|
||||
std::string element_type; ///< "triangle" 或 "tetrahedron"
|
||||
|
||||
// Binned histogram (quality ∈ [0,1])
|
||||
size_t total = 0; ///< 单元总数
|
||||
size_t degenerate = 0; ///< 退化单元数
|
||||
|
||||
/// 质量直方图(k=10 个区间,[0,0.1), [0.1,0.2), ..., [0.9,1.0])
|
||||
static constexpr int kBins = 10;
|
||||
std::array<size_t, kBins> histogram{};
|
||||
|
||||
// Grade counts (A=excellent B=good C=acceptable D=poor F=fail)
|
||||
/// 分级计数(A=优秀 B=良好 C=合格 D=差 F=不合格)
|
||||
size_t grade_A = 0, grade_B = 0, grade_C = 0, grade_D = 0, grade_F = 0;
|
||||
|
||||
double min_quality = 0.0;
|
||||
double max_quality = 0.0;
|
||||
double avg_quality = 0.0;
|
||||
double stddev_quality = 0.0;
|
||||
double min_quality = 0.0; ///< 最低质量值
|
||||
double max_quality = 0.0; ///< 最高质量值
|
||||
double avg_quality = 0.0; ///< 平均质量值
|
||||
double stddev_quality = 0.0; ///< 质量标准差
|
||||
};
|
||||
|
||||
/// Generate a full quality report from a triangle surface mesh.
|
||||
/**
|
||||
* @brief 生成三角网格的完整质量报告
|
||||
*
|
||||
* 包含直方图、分级统计及基本统计量。
|
||||
* 分级标准(按归一化 Jacobian):
|
||||
* - A: [0.9, 1.0] — 优秀
|
||||
* - B: [0.7, 0.9) — 良好
|
||||
* - C: [0.5, 0.7) — 合格
|
||||
* - D: [0.3, 0.5) — 差
|
||||
* - F: [0.0, 0.3) — 不合格
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @return QualityReport 完整质量报告
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
QualityReport mesh_quality_report(const HalfedgeMesh& mesh);
|
||||
|
||||
/// Generate a full quality report from a tetrahedral mesh.
|
||||
/// The mesh is treated as having 4-vertex faces (tets) in the
|
||||
/// same index order as the surface-mesh face loop.
|
||||
/**
|
||||
* @brief 生成四面体网格的完整质量报告
|
||||
*
|
||||
* 将表面网格的面解释为四面体单元的边界(面 + 面心 = 虚拟四面体)。
|
||||
*
|
||||
* @param mesh 封闭三角网格
|
||||
* @return QualityReport 完整质量报告
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
QualityReport mesh_quality_report_tet(const HalfedgeMesh& mesh);
|
||||
|
||||
// ── Utility ─────────────────────────────────────────
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Utility
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
/// Compute the scaled Jacobian (determinant / (product of edge-lengths))
|
||||
/// for a triangle; returns [−1,1], 1 = equilateral.
|
||||
/**
|
||||
* @brief 三角形归一化 Jacobian
|
||||
*
|
||||
* 计算三角形 (a,b,c) 的带符号面积,归一化到 [-1, 1]。
|
||||
* 值 1 = 等边三角形(最优),0 = 退化(共线),-1 = 翻转(法向反了)。
|
||||
*
|
||||
* 公式:J = det([b-a, c-a, n]) / (|b-a|·|c-a|·|b-c|? or area-based?)
|
||||
* 实际实现使用面积与理想等边三角形面积之比。
|
||||
*
|
||||
* @param a,b,c 三角形三个顶点
|
||||
* @return 归一化 Jacobian [-1, 1]
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
double tri_scaled_jacobian(const Point3D& a, const Point3D& b, const Point3D& c);
|
||||
|
||||
/// Compute the scaled Jacobian for a tetrahedron; returns [0,1].
|
||||
/**
|
||||
* @brief 四面体归一化 Jacobian
|
||||
*
|
||||
* 计算四面体 (a,b,c,d) 的体积 Jacobian,归一化到 [0, 1]。
|
||||
* 值 1 = 正四面体(最优),0 = 退化(共面)。
|
||||
*
|
||||
* 公式:J = det([b-a, c-a, d-a]) / (归一化因子)
|
||||
*
|
||||
* @param a,b,c,d 四面体四个顶点
|
||||
* @return 归一化 Jacobian [0, 1]
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
double tet_scaled_jacobian(const Point3D& a, const Point3D& b,
|
||||
const Point3D& c, const Point3D& d);
|
||||
|
||||
|
||||
@@ -6,13 +6,40 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief 网格修复选项
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct RepairOptions {
|
||||
bool fill_holes = true;
|
||||
bool remove_duplicates = true;
|
||||
bool fix_orientation = true;
|
||||
bool remove_degenerate = true;
|
||||
bool fill_holes = true; ///< 是否填充孔洞(补面)
|
||||
bool remove_duplicates = true; ///< 是否合并重合顶点(距离 < ε)
|
||||
bool fix_orientation = true; ///< 是否统一面法向(外翻)
|
||||
bool remove_degenerate = true; ///< 是否删除退化面(面积为 0 的三角形)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 自动网格修复
|
||||
*
|
||||
* 对输入网格执行一系列修复操作,输出水密的流形网格:
|
||||
* - fill_holes: 检测边界环并用 fan 三角化填充
|
||||
* - remove_duplicates: 空间哈希合并间距 < 1e-8 的重复顶点
|
||||
* - fix_orientation: 从最大连通分量开始遍历传播一致的半边朝向
|
||||
* - remove_degenerate: 剔除面积 < ε 的退化三角形
|
||||
*
|
||||
* @param mesh 输入网格(可能有缺陷)
|
||||
* @param opts 修复选项(默认全部启用)
|
||||
* @return 修复后的网格
|
||||
*
|
||||
* @note 修复为启发式算法,不保证 100% 成功;孔洞过大或非流形边可能仍有残留问题
|
||||
* @code{.cpp}
|
||||
* auto fixed = repair_mesh(broken_mesh); // 默认全修复
|
||||
*
|
||||
* RepairOptions opts;
|
||||
* opts.fill_holes = false; // 仅做去重 + 定向
|
||||
* auto cleaned = repair_mesh(mesh, opts);
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh repair_mesh(const HalfedgeMesh& mesh, const RepairOptions& opts = {});
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
@@ -6,14 +6,41 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
/**
|
||||
* @brief 网格简化选项
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct SimplifyOptions {
|
||||
double target_ratio = 0.5; // Target face ratio (0, 1)
|
||||
int max_iterations = 100;
|
||||
bool preserve_boundary = true;
|
||||
double target_ratio = 0.5; ///< 目标面数比例 (0, 1],0.5 表示保留 50% 的面
|
||||
int max_iterations = 100; ///< 最大迭代次数(边塌缩上限)
|
||||
bool preserve_boundary = true; ///< 是否保护边界边不被塌缩
|
||||
};
|
||||
|
||||
/// QEM (Quadric Error Metrics) mesh simplification
|
||||
/// Returns simplified mesh with ~target_ratio * original face count
|
||||
/**
|
||||
* @brief QEM(Quadric Error Metrics)网格简化
|
||||
*
|
||||
* 基于 Garland & Heckbert 1997 的 QEM 算法,通过迭代边塌缩减少三角面数量。
|
||||
* 每条边维护一个 4×4 误差二次型 Q = Σ Q_f(面片的顶点-平面距离平方和),
|
||||
* 塌缩目标点为 argmin v^T Q v,塌缩代价为该最小值。
|
||||
*
|
||||
* 算法流程:
|
||||
* 1. 为每个顶点计算初始 Q(关联所有邻面的平面二次型之和)
|
||||
* 2. 为每条边计算最优塌缩点及代价,插入最小堆
|
||||
* 3. 循环弹出最小代价边,执行塌缩,更新受影响边的代价
|
||||
* 4. 直到面数达到 target_ratio 或堆为空
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @param opts 简化选项
|
||||
* @return 简化后的网格
|
||||
*
|
||||
* @note preserve_boundary 模式下,边界边通过惩罚项(大代价)被保护
|
||||
* @code{.cpp}
|
||||
* SimplifyOptions opts;
|
||||
* opts.target_ratio = 0.1; // 保留 10% 面
|
||||
* auto lod = simplify_mesh(high_res_mesh, opts);
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh simplify_mesh(const HalfedgeMesh& mesh, const SimplifyOptions& opts = {});
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
+118
-22
@@ -6,38 +6,134 @@ using core::Point2D;
|
||||
using core::Point3D;
|
||||
using core::Vector3D;
|
||||
|
||||
enum class SmoothMethod { Laplacian, Taubin, HCLaplacian, Bilateral };
|
||||
|
||||
struct SmoothOptions {
|
||||
int iterations = 10;
|
||||
double lambda = 0.5; // Laplacian weight
|
||||
double mu = -0.53; // Taubin shrink (mu < -lambda for Taubin)
|
||||
|
||||
// HC Laplacian parameters
|
||||
double hc_alpha = 0.0; // push-back strength (0 = default auto)
|
||||
double hc_beta = 0.5; // push-forward blend
|
||||
|
||||
// Bilateral parameters
|
||||
double bilateral_sigma_c = 0.0; // spatial sigma (0 = auto from avg edge length)
|
||||
double bilateral_sigma_n = 0.3; // normal sigma (radians)
|
||||
int bilateral_iters = 5;
|
||||
|
||||
SmoothMethod method = SmoothMethod::Laplacian;
|
||||
/**
|
||||
* @brief 网格光顺方法
|
||||
* @ingroup mesh
|
||||
*/
|
||||
enum class SmoothMethod {
|
||||
Laplacian, ///< 标准拉普拉斯光顺:v ← v + λ·L(v),快速但会收缩
|
||||
Taubin, ///< Taubin λ|μ 光顺:先正后负步交替,体积保持
|
||||
HCLaplacian, ///< HC 拉普拉斯(Humphrey's Classes):推拉两步保持形状/体积
|
||||
Bilateral ///< 双边滤波:法向加权去噪,保持尖锐特征
|
||||
};
|
||||
|
||||
/// Generic dispatcher — delegates to specific smooth functions
|
||||
/**
|
||||
* @brief 网格光顺参数
|
||||
* @ingroup mesh
|
||||
*/
|
||||
struct SmoothOptions {
|
||||
int iterations = 10; ///< 迭代次数
|
||||
double lambda = 0.5; ///< 拉普拉斯步长(Standard / Taubin 正步)
|
||||
|
||||
/// Taubin 负步系数,典型值 mu = -(lambda + ε),默认 -0.53
|
||||
/// 必须满足 mu < -lambda 以实现体积保持
|
||||
double mu = -0.53;
|
||||
|
||||
/// HC 拉普拉斯参数
|
||||
double hc_alpha = 0.0; ///< 回推强度,0 = 自动计算(推荐)
|
||||
double hc_beta = 0.5; ///< 前推混合系数
|
||||
|
||||
/// 双边滤波参数
|
||||
double bilateral_sigma_c = 0.0; ///< 空间 σ,0 = 自动按平均边长计算
|
||||
double bilateral_sigma_n = 0.3; ///< 法向 σ(弧度),控制特征保持的敏感度
|
||||
int bilateral_iters = 5; ///< 双边内部迭代次数
|
||||
|
||||
SmoothMethod method = SmoothMethod::Laplacian; ///< 光顺方法选择
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 网格光顺(通用分发器)
|
||||
*
|
||||
* 根据 opts.method 分派到具体的光顺实现:
|
||||
* - Laplacian → smooth_laplacian()
|
||||
* - Taubin → smooth_taubin()
|
||||
* - HCLaplacian → smooth_hc_laplacian()
|
||||
* - Bilateral → smooth_bilateral()
|
||||
*
|
||||
* @param mesh 输入三角网格
|
||||
* @param opts 光顺选项
|
||||
* @return 光顺后的网格(顶点位置更新,拓扑不变)
|
||||
*
|
||||
* @code{.cpp}
|
||||
* SmoothOptions opts;
|
||||
* opts.method = SmoothMethod::Taubin;
|
||||
* opts.iterations = 20;
|
||||
* auto smooth = smooth_mesh(noisy_mesh, opts);
|
||||
* @endcode
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh smooth_mesh(const HalfedgeMesh& mesh, const SmoothOptions& opts = {});
|
||||
|
||||
/// Standard Laplacian smoothing
|
||||
/**
|
||||
* @brief 标准拉普拉斯光顺
|
||||
*
|
||||
* 均匀拉普拉斯:v ← v + λ·(avg(neighbor_positions) - v)
|
||||
* 最简单但会引入体积收缩,适用于轻度平滑。
|
||||
*
|
||||
* @param mesh 输入网格
|
||||
* @param iterations 迭代次数
|
||||
* @param lambda 步长系数 (0, 1]
|
||||
* @return 光顺后的网格
|
||||
*
|
||||
* @note 迭代过多会导致网格坍塌;对于体积关键的应用优先选 Taubin 或 HC
|
||||
* @see smooth_taubin smooth_hc_laplacian
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh smooth_laplacian(const HalfedgeMesh& mesh, int iterations, double lambda);
|
||||
|
||||
/// Taubin λ|μ smoothing (volume-preserving)
|
||||
/**
|
||||
* @brief Taubin λ|μ 光顺(体积保持)
|
||||
*
|
||||
* 两阶段交替:
|
||||
* 1. 正步(收缩滤波):v ← v + λ·L(v)
|
||||
* 2. 负步(膨胀滤波):v ← v + μ·L(v),其中 μ < 0 且 |μ| > |λ|
|
||||
*
|
||||
* 传递函数在低频通带增益 ≈ 1,有效去噪同时保持体积。
|
||||
*
|
||||
* @param mesh 输入网格
|
||||
* @param iterations 完整 λ/μ 循环次数
|
||||
* @param lambda 正步系数 (0, 1]
|
||||
* @param mu 负步系数,需满足 mu < 0 且 |mu| > lambda
|
||||
* @return 光顺后的网格
|
||||
*
|
||||
* @note 典型值:λ = 0.5, μ = -0.53;|mu| 过大可能导致不稳定振荡
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh smooth_taubin(const HalfedgeMesh& mesh, int iterations, double lambda, double mu);
|
||||
|
||||
/// HC Laplacian (Humphrey's Classes) — two-pass with push-back to preserve volume
|
||||
/**
|
||||
* @brief HC 拉普拉斯光顺(Humphrey's Classes)
|
||||
*
|
||||
* 两遍处理以保持原始形状特征:
|
||||
* 1. 前推:标准拉普拉斯光顺,记录位移 b_i = v_i' - v_i
|
||||
* 2. 回推:v_i'' = v_i' - (α·b_i + β·avg(neighbor_b))
|
||||
*
|
||||
* 相比标准拉普拉斯显著减轻体积收缩,同时比 Taubin 更好地保持尖锐特征。
|
||||
*
|
||||
* @param mesh 输入网格
|
||||
* @param opts 光顺选项(使用 hc_alpha 和 hc_beta 字段)
|
||||
* @return 光顺后的网格
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh smooth_hc_laplacian(const HalfedgeMesh& mesh, const SmoothOptions& opts = {});
|
||||
|
||||
/// Bilateral mesh filtering — normal-weighted, preserves sharp features
|
||||
/**
|
||||
* @brief 双边网格滤波
|
||||
*
|
||||
* 基于法向加权的各向异性去噪,保持尖锐边缘和特征:
|
||||
* - 空间权重:w_c = exp(-d²/(2·σ_c²)),基于顶点间距
|
||||
* - 法向权重:w_n = exp(-θ²/(2·σ_n²)),基于法向夹角
|
||||
* - 更新:v ← v + Σ_j w_c(j)·w_n(j)·(v_j - v) / Σ_j w_c(j)·w_n(j)
|
||||
*
|
||||
* 相比于各向同性的拉普拉斯类方法,双边滤波在 CAD/机械零件网格上效果最优。
|
||||
*
|
||||
* @param mesh 输入网格
|
||||
* @param opts 光顺选项(使用 bilateral_ 前缀字段)
|
||||
* @return 光顺后的网格
|
||||
*
|
||||
* @ingroup mesh
|
||||
*/
|
||||
HalfedgeMesh smooth_bilateral(const HalfedgeMesh& mesh, const SmoothOptions& opts = {});
|
||||
|
||||
} // namespace vde::mesh
|
||||
|
||||
Reference in New Issue
Block a user