10.3 Alpha Wrap 3D
本章介绍 Alpha Shapes 的扩展——Alpha Wrap 3D,它保证输出封闭、流形、无自交的三角网格表面。
1. 理论基础
1.1 包装问题背景
Alpha Wrap 3D 是 CGAL 中用于计算输入几何数据”包装”(wrap)的算法。与 Alpha Shapes 不同,Alpha Wrap 保证输出是一个封闭、流形、无自交的三角网格表面。
核心思想:
- 从输入数据(点云、三角网格、三角汤)出发
- 使用 Delaunay 细化算法构造一个”紧密”包围输入的网格
- 通过 alpha 参数控制网格的精细程度
- 通过 offset 参数控制与输入数据的距离
1.2 算法概述
Alpha Wrap 算法基于以下核心概念:
- Delaunay 三角剖分:维护输入空间的四面体化
- 内外标记:将四面体标记为”内部”或”外部”
- 门(Gate):内外边界上的三角面片
- Steiner 点插入:细化 Delaunay 网格以逼近目标表面
1.3 参数说明
| 参数 | 含义 | 影响 |
|---|---|---|
| alpha | 特征尺寸 | 控制保留的细节程度,较小的 alpha 保留更多特征 |
| offset | 偏移距离 | 控制输出与输入的距离,必须为正 |
参数关系:
- offset 默认值为 alpha / 30
- alpha 越大,网格越粗糙
- offset 越大,输出离输入越远
2. CGAL Alpha Wrap 3D 架构
2.1 模块组织
Alpha_wrap_3/
include/CGAL/
alpha_wrap_3.h # 主接口函数
Alpha_wrap_3/internal/
Alpha_wrap_3.h # 核心算法类
Alpha_wrap_triangulation_cell_base_3.h # 单元基类
Alpha_wrap_triangulation_vertex_base_3.h # 顶点基类
gate_priority_queue.h # 优先队列
oracles.h # Oracle 接口
Oracle_base.h # Oracle 基类
Point_set_oracle.h # 点云 Oracle
Triangle_soup_oracle.h # 三角汤 Oracle
Triangle_mesh_oracle.h # 三角网格 Oracle
Segment_soup_oracle.h # 线段汤 Oracle
geometry_utils.h # 几何工具
offset_intersection.h # 偏移交点计算
2.2 核心类层次
Alpha_wrapper_3<Oracle>
├── Oracle (模板参数)
│ ├── Point_set_oracle
│ ├── Triangle_soup_oracle
│ ├── Triangle_mesh_oracle
│ └── Segment_soup_oracle
├── Triangulation (Delaunay_triangulation_3)
└── Alpha_PQ (优先队列)
3. 核心实现详解
3.1 Alpha_wrapper_3 类
// Alpha_wrap_3/internal/Alpha_wrap_3.h
template <typename Oracle_, typename Triangulation_ = CGAL::Default>
class Alpha_wrapper_3 {
using Oracle = Oracle_;
using Base_GT = typename Oracle::Geom_traits;
using Default_Gt = CGAL::Robust_circumcenter_filtered_traits_3<Base_GT>;
// 顶点基类包含类型信息
using Default_Vb = Alpha_wrap_triangulation_vertex_base_3<Default_Gt>;
// 单元基类包含内外标记
using Default_Cb = Alpha_wrap_triangulation_cell_base_3<Default_Gt>;
using Default_Cbt = Cell_base_with_timestamp<Default_Cb>;
using Default_Tds = CGAL::Triangulation_data_structure_3<Default_Vb, Default_Cbt>;
using Default_Triangulation = CGAL::Delaunay_triangulation_3<
Default_Gt, Default_Tds, Fast_location>;
public:
using Triangulation = typename Default::Get<Triangulation_, Default_Triangulation>::type;
using Geom_traits = typename Triangulation::Geom_traits;
private:
using Cell_handle = typename Triangulation::Cell_handle;
using Facet = typename Triangulation::Facet;
using Vertex_handle = typename Triangulation::Vertex_handle;
using Gate = internal::Gate<Triangulation>;
using Alpha_PQ = std::stack<Gate>; // 或使用 Modifiable_priority_queue
// 核心成员
Oracle m_oracle; // 输入数据访问接口
Triangulation m_tr; // Delaunay 三角剖分
Alpha_PQ m_queue; // 待处理的面片队列
// 参数
FT m_alpha = FT(-1), m_sq_alpha = FT(-1);
FT m_offset = FT(-1), m_sq_offset = FT(-1);
// 种子点(用于创建初始腔体)
Seeds m_seeds;
};3.2 单元标签系统
// Alpha_wrap_triangulation_cell_base_3.h
enum class Cell_label {
INSIDE = 0, // 内部单元
OUTSIDE = 1, // 外部单元
MANIFOLD = 2 // 用于保证流形性的临时标记
};
template <class GT>
class Alpha_wrap_triangulation_cell_base_3 {
Cell_label m_label = Cell_label::INSIDE;
public:
bool is_inside() const { return m_label == Cell_label::INSIDE; }
bool is_outside() const { return m_label == Cell_label::OUTSIDE; }
bool is_manifold() const { return m_label == Cell_label::MANIFOLD; }
void set_label(Cell_label label) { m_label = label; }
};3.3 顶点类型
// Alpha_wrap_triangulation_vertex_base_3.h
enum class Vertex_type {
DEFAULT = 0, // 普通顶点(Steiner 点)
BBOX_VERTEX = 1, // 包围盒顶点
SEED_VERTEX = 2 // 种子顶点(用于创建初始腔体)
};
template <class GT>
class Alpha_wrap_triangulation_vertex_base_3 {
Vertex_type m_type = Vertex_type::DEFAULT;
public:
Vertex_type& type() { return m_type; }
};3.4 核心算法流程
// 主算法流程
template <typename Visitor>
void operator()(const double alpha, const double offset,
OutputMesh& output_mesh, ...) {
// 1. 初始化
if (\!initialize(alpha, offset, refining))
return;
// 2. Alpha 洪水填充(核心细化循环)
alpha_flood_fill(visitor);
// 3. 保证流形性
if (do_enforce_manifoldness)
make_manifold();
// 4. 清除内部连通分量
if (\!keep_inner_ccs)
purge_inner_connected_components();
// 5. 提取表面
extract_surface(output_mesh, ovpm, \!do_enforce_manifoldness);
}3.5 初始化过程
// 初始化 Delaunay 三角剖分
bool initialize(const double alpha, const double offset, const bool refining) {
m_alpha = FT(alpha);
m_sq_alpha = square(m_alpha);
m_offset = FT(offset);
m_sq_offset = square(m_offset);
if (refining) {
// 从现有三角剖分继续
return initialize_from_existing_triangulation();
}
// 清空并重新初始化
m_tr.clear();
insert_bbox_corners(); // 插入包围盒角点
if (m_seeds.empty())
return initialize_from_infinity(); // 从无限远开始
else
return initialize_with_cavities(); // 从种子点开始
}
// 从无限远初始化(标准模式)
bool initialize_from_infinity() {
for (Cell_handle ch : m_tr.all_cell_handles()) {
if (m_tr.is_infinite(ch)) {
ch->set_label(Cell_label::OUTSIDE);
const int inf_index = ch->index(m_tr.infinite_vertex());
push_facet(std::make_pair(ch, inf_index));
} else {
ch->set_label(Cell_label::INSIDE);
}
}
return true;
}
// 使用种子点创建初始腔体
bool initialize_with_cavities() {
// 在每个种子点周围插入正二十面体顶点
// 标记这些单元为内部,其余为外部
// 将边界 facet 加入队列
for (const Point_3& seed_p : m_seeds) {
Vertex_handle seed_v = m_tr.insert(seed_p);
seed_v->type() = AW3i::Vertex_type::SEED_VERTEX;
// 插入正二十面体顶点
std::array<Point_3, 12> ico_ps = construct_icosahedron(seed_p, 1.65 * m_alpha);
for (const Point_3& ico_p : ico_ps) {
Vertex_handle ico_v = m_tr.insert(ico_p, seed_v);
ico_v->type() = AW3i::Vertex_type::SEED_VERTEX;
}
}
// 标记 incident 单元并填充队列
// ...
}3.6 Alpha 洪水填充算法
// 核心细化循环
template <typename Visitor>
bool alpha_flood_fill(Visitor& visitor) {
while (\!m_queue.empty()) {
if (\!visitor.go_further(*this))
return false; // 用户中断
const Gate& gate = m_queue.top();
const Facet& f = gate.facet();
Cell_handle ch = f.first; // 外部单元
int s = f.second;
Cell_handle nh = ch->neighbor(s); // 内部单元
m_queue.pop();
// 计算 Steiner 点
Point_3 steiner_point;
if (compute_steiner_point(ch, nh, steiner_point)) {
// 需要细化:插入 Steiner 点
insert_steiner_point(steiner_point, nh, visitor);
} else {
// 不需要细化:直接标记为外部
nh->set_label(Cell_label::OUTSIDE);
// 将新边界 facet 加入队列
const int mi = m_tr.mirror_index(ch, s);
for (int i = 1; i < 4; ++i) {
push_facet(std::make_pair(nh, (mi + i) & 3));
}
}
}
return true;
}
// 计算 Steiner 点
bool compute_steiner_point(const Cell_handle ch, const Cell_handle neighbor,
Point_3& steiner_point) const {
const Point_3& neighbor_cc = circumcenter(neighbor);
// 检查邻居的外心是否在偏移体积内
Ball_3 neighbor_cc_offset_ball(neighbor_cc, m_sq_offset);
bool is_neighbor_cc_in_offset = m_oracle.do_intersect(neighbor_cc_offset_ball);
if (is_neighbor_cc_in_offset) {
const Point_3& ch_cc = circumcenter(ch);
// Voronoi 边与偏移表面相交
if (m_oracle.first_intersection(ch_cc, neighbor_cc, steiner_point, m_offset)) {
return true;
}
}
// 检查邻居四面体是否与输入相交
Tetrahedron_with_outside_info<Geom_traits> tet(neighbor, geom_traits());
if (m_oracle.do_intersect(tet)) {
// Steiner 点是输入上最近点沿法向偏移
const Point_3 closest_pt = m_oracle.closest_point(neighbor_cc);
Vector_3 unit = closest_pt - neighbor_cc;
unit = unit * (m_offset / sqrt(unit.squared_length()));
steiner_point = closest_pt + unit;
return true;
}
return false; // 不需要细化
}
// 插入 Steiner 点
void insert_steiner_point(const Point_3& steiner_point, Cell_handle neighbor,
Visitor& visitor) {
// 定位冲突区域
Locate_type lt;
int li, lj;
Cell_handle conflict_cell = m_tr.locate(steiner_point, lt, li, lj, neighbor);
// 查找冲突区域边界
std::vector<Facet> boundary_facets;
std::vector<Cell_handle> conflict_zone;
m_tr.find_conflicts(steiner_point, conflict_cell,
std::back_inserter(boundary_facets),
std::back_inserter(conflict_zone));
visitor.before_Steiner_point_insertion(*this, steiner_point);
// 插入点
Vertex_handle vh = m_tr.insert(steiner_point, lt, conflict_cell, li, lj);
vh->type() = AW3i::Vertex_type::DEFAULT;
visitor.after_Steiner_point_insertion(*this, vh);
// 标记新单元并更新队列
std::vector<Cell_handle> new_cells;
m_tr.incident_cells(vh, std::back_inserter(new_cells));
for (const Cell_handle& new_ch : new_cells) {
new_ch->set_label(m_tr.is_infinite(new_ch) ?
Cell_label::OUTSIDE : Cell_label::INSIDE);
}
// 将新边界 facet 加入队列
for (Cell_handle new_ch : new_cells) {
for (int i = 0; i < 4; ++i) {
if (m_tr.is_infinite(new_ch, i)) continue;
Cell_handle new_nh = new_ch->neighbor(i);
if (new_nh->label() == new_ch->label()) continue;
Facet boundary_f = std::make_pair(new_ch, i);
if (new_ch->is_outside())
push_facet(boundary_f);
else
push_facet(m_tr.mirror_facet(boundary_f));
}
}
}3.7 Facet 状态判断
// 判断 facet 是否可以遍历
enum class Facet_status {
IRRELEVANT = 0, // 不相关(内部 facet)
HAS_INFINITE_NEIGHBOR = 1, // 邻接无限单元(宽松)
SCAFFOLDING = 2, // 邻接 BBOX 或 SEED 顶点(宽松)
TRAVERSABLE = 3 // 可遍历(满足 alpha 条件)
};
Facet_status facet_status(const Facet& f) const {
CGAL_precondition(\!m_tr.is_infinite(f));
Cell_handle ch = f.first;
int id = f.second;
CGAL_precondition(ch->is_outside());
Cell_handle nh = ch->neighbor(id);
if (m_tr.is_infinite(nh))
return Facet_status::HAS_INFINITE_NEIGHBOR;
if (nh->is_outside())
return Facet_status::IRRELEVANT;
// 检查是否邻接脚手架顶点
for (int i = 0; i < 3; ++i) {
Vertex_handle vh = ch->vertex(Triangulation::vertex_triple_index(id, i));
if (vh->type() == AW3i::Vertex_type::BBOX_VERTEX ||
vh->type() == AW3i::Vertex_type::SEED_VERTEX) {
return Facet_status::SCAFFOLDING;
}
}
// 检查 alpha 条件
if (is_traversable(f))
return Facet_status::TRAVERSABLE;
return Facet_status::IRRELEVANT;
}
// 判断 facet 是否满足 alpha 条件
bool is_traversable(const Facet& f) const {
return less_squared_radius_of_min_empty_sphere(m_sq_alpha, f, m_tr);
}3.8 流形性保证
// 检测并修复非流形顶点
void make_manifold() {
std::stack<Vertex_handle> non_manifold_vertices;
// 收集所有非流形顶点
for (Vertex_handle v : m_tr.finite_vertex_handles()) {
if (is_non_manifold(v))
non_manifold_vertices.push(v);
}
// 逐个修复
while (\!non_manifold_vertices.empty()) {
Vertex_handle v = non_manifold_vertices.top();
non_manifold_vertices.pop();
if (\!is_non_manifold(v))
continue;
// 找到与 v 关联的外部单元,按大小排序
std::vector<Cell_handle> inc_cells;
m_tr.finite_incident_cells(v, std::back_inserter(inc_cells));
std::vector<Cell_handle> outside_cells;
for (Cell_handle c : inc_cells) {
if (c->is_outside())
outside_cells.push_back(c);
}
// 按最长边排序(优先添加小单元)
std::stable_sort(outside_cells.begin(), outside_cells.end(),
[&](Cell_handle l, Cell_handle r) {
return sq_longest_edge(l) < sq_longest_edge(r);
});
// 逐个标记为 MANIFOLD 直到修复
for (Cell_handle c : outside_cells) {
c->set_label(Cell_label::MANIFOLD);
if (\!is_non_manifold(v))
break;
}
// 检查邻接顶点是否变为非流形
std::vector<Vertex_handle> adj_vertices;
m_tr.finite_adjacent_vertices(v, std::back_inserter(adj_vertices));
for (Vertex_handle nv : adj_vertices) {
if (is_non_manifold(nv))
non_manifold_vertices.push(nv);
}
}
}
// 检测顶点是否非流形
bool is_non_manifold(Vertex_handle v) const {
// 收集所有与 v 关联的单元
std::vector<Cell_handle> inc_cells;
m_tr.incident_cells(v, std::back_inserter(inc_cells));
// 在关联单元中 flood fill 内外连通分量
// 如果有多个未访问的连通分量,则为非流形
// ...
}3.9 Oracle 接口
Oracle 是 Alpha Wrap 与输入数据交互的抽象接口:
// Oracle_base.h
class Oracle_base {
public:
// 距离查询
virtual FT squared_distance(const Point_3& p) const = 0;
virtual Point_3 closest_point(const Point_3& p) const = 0;
// 相交测试
virtual bool do_intersect(const Tetrahedron_3& tet) const = 0;
virtual bool do_intersect(const Ball_3& ball) const = 0;
// 射线求交
virtual bool first_intersection(const Ray_3& ray, Point_3& out) const = 0;
// 包围盒
virtual Bbox_3 bbox() const = 0;
};
// Triangle_mesh_oracle.h
class Triangle_mesh_oracle : public Oracle_base {
// 使用 AABB 树加速查询
AABB_tree tree;
public:
template <typename TriangleMesh, typename NamedParameters>
void add_triangle_mesh(const TriangleMesh& mesh, const NamedParameters& np) {
// 构建 AABB 树
tree.insert(faces(mesh).first, faces(mesh).second, mesh, ...);
tree.build();
}
Point_3 closest_point(const Point_3& p) const override {
return tree.closest_point(p);
}
bool do_intersect(const Tetrahedron_3& tet) const override {
return tree.do_intersect(tet);
}
// ...
};4. 使用示例
4.1 包装三角网格
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/alpha_wrap_3.h>
#include <CGAL/Polygon_mesh_processing/bbox.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <CGAL/Real_timer.h>
namespace PMP = CGAL::Polygon_mesh_processing;
using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Point_3 = K::Point_3;
using Mesh = CGAL::Surface_mesh<Point_3>;
int main(int argc, char** argv) {
// 读取输入
const std::string filename = argv[1];
Mesh mesh;
if (\!PMP::IO::read_polygon_mesh(filename, mesh) ||
is_empty(mesh) || \!is_triangle_mesh(mesh)) {
std::cerr <> "Invalid input" <> std::endl;
return EXIT_FAILURE;
}
std::cout <> "Input: " <> num_vertices(mesh) <> " vertices, "
<> num_faces(mesh) <> " faces" <> std::endl;
// 计算 alpha 和 offset
CGAL::Bbox_3 bbox = PMP::bbox(mesh);
const double diag_length = std::sqrt(
CGAL::square(bbox.xmax() - bbox.xmin()) +
CGAL::square(bbox.ymax() - bbox.ymin()) +
CGAL::square(bbox.zmax() - bbox.zmin()));
const double relative_alpha = 20.0; // 相对 alpha 值
const double relative_offset = 600.0; // 相对 offset 值
const double alpha = diag_length / relative_alpha;
const double offset = diag_length / relative_offset;
std::cout <> "alpha: " <> alpha <> ", offset: " <> offset <> std::endl;
// 构造包装
CGAL::Real_timer t;
t.start();
Mesh wrap;
CGAL::alpha_wrap_3(mesh, alpha, offset, wrap);
t.stop();
std::cout <> "Result: " <> num_vertices(wrap) <> " vertices, "
<> num_faces(wrap) <> " faces" <> std::endl;
std::cout <> "Took " <> t.time() <> " s." <> std::endl;
// 保存结果
CGAL::IO::write_polygon_mesh("wrap.off", wrap,
CGAL::parameters::stream_precision(17));
return EXIT_SUCCESS;
}4.2 包装点云
#include <CGAL/alpha_wrap_3.h>
using Point_3 = K::Point_3;
using Mesh = CGAL::Surface_mesh<Point_3>;
int main() {
std::vector<Point_3> points;
// ... 填充点云数据 ...
const double alpha = 0.5;
const double offset = alpha / 30.0;
Mesh wrap;
CGAL::alpha_wrap_3(points, alpha, offset, wrap);
std::cout <> "Wrapped point set with " <> points.size() <> " points" <> std::endl;
std::cout <> "Result: " <> num_vertices(wrap) <> " vertices" <> std::endl;
return 0;
}4.3 使用种子点
#include <CGAL/alpha_wrap_3.h>
int main() {
Mesh mesh;
// ... 加载网格 ...
// 定义种子点(用于创建初始腔体)
std::vector<Point_3> seeds = {
Point_3(0, 0, 0),
Point_3(10, 0, 0),
Point_3(5, 10, 0)
};
Mesh wrap;
CGAL::alpha_wrap_3(mesh, alpha, offset, wrap,
CGAL::parameters::seed_points(seeds));
return 0;
}4.4 渐进式包装(多分辨率)
#include <CGAL/alpha_wrap_3.h>
int main() {
Mesh mesh;
// ... 加载网格 ...
Mesh wrap;
// 第一次包装(粗粒度)
double alpha1 = 1.0;
CGAL::alpha_wrap_3(mesh, alpha1, alpha1/30.0, wrap);
CGAL::IO::write_polygon_mesh("wrap_coarse.off", wrap);
// 第二次包装(细粒度,复用三角剖分)
double alpha2 = 0.5;
CGAL::alpha_wrap_3(mesh, alpha2, alpha2/30.0, wrap,
CGAL::parameters::refine_triangulation(true));
CGAL::IO::write_polygon_mesh("wrap_fine.off", wrap);
return 0;
}4.5 使用访问者监控进度
#include <CGAL/alpha_wrap_3.h>
struct MyVisitor {
void on_alpha_wrapping_begin(const auto& wrapper) {
std::cout <> "Alpha wrapping started" <> std::endl;
}
void on_flood_fill_begin(const auto& wrapper) {
std::cout <> "Flood fill started" <> std::endl;
}
bool go_further(const auto& wrapper) {
// 可以添加超时检查或用户中断
return true;
}
void before_facet_treatment(const auto& wrapper, const auto& gate) {
// 处理每个 facet 前的回调
}
void before_Steiner_point_insertion(const auto& wrapper, const Point_3& p) {
std::cout <> "Inserting Steiner point at " <> p <> std::endl;
}
void on_alpha_wrapping_end(const auto& wrapper) {
std::cout <> "Alpha wrapping completed" <> std::endl;
}
};
int main() {
Mesh mesh;
// ... 加载网格 ...
MyVisitor visitor;
Mesh wrap;
CGAL::alpha_wrap_3(mesh, alpha, offset, wrap,
CGAL::parameters::visitor(visitor));
return 0;
}5. 复杂度分析
5.1 时间复杂度
| 阶段 | 复杂度 | 说明 |
|---|---|---|
| 初始化 | O(n log n) | 构建初始 Delaunay 三角剖分 |
| Oracle 查询 | O(log n) ~ O(n) | 取决于输入类型和 AABB 树效率 |
| 洪水填充 | O(m log m) | m 是最终顶点数 |
| Steiner 点插入 | O(log m) 均摊 | Delaunay 插入 |
| 流形性保证 | O(m) | 通常很快 |
| 表面提取 | O(m) | 遍历所有面 |
| 总计 | O((n+m) log(n+m)) | n 输入规模,m 输出规模 |
5.2 空间复杂度
| 数据结构 | 空间 | 说明 |
|---|---|---|
| Delaunay 三角剖分 | O(m) | m 是顶点数 |
| Oracle (AABB 树) | O(n) | n 是输入规模 |
| 优先队列 | O(m^{2/3}) | 边界 facet 数量 |
| 输出网格 | O(m) | 三角网格 |
| 总计 | O(n + m) | 线性空间 |
6. 关键文件位置
| 文件路径 | 说明 |
|---|---|
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/alpha_wrap_3.h | 主接口函数 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_3.h | 核心算法类 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_triangulation_cell_base_3.h | 单元基类 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Alpha_wrap_triangulation_vertex_base_3.h | 顶点基类 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/gate_priority_queue.h | 优先队列实现 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/oracles.h | Oracle 接口定义 |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Triangle_mesh_oracle.h | 三角网格 Oracle |
/home/chunibyo/workspace/osc/cgal/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/Point_set_oracle.h | 点云 Oracle |
7. 注意事项
-
数值类型:
- 必须使用浮点型内核(如
Epick) - 精确内核不被支持,因为算法涉及二分查找
- 必须使用浮点型内核(如
-
参数选择:
- alpha 应该根据输入数据的特征尺寸选择
- offset 默认 alpha/30 通常效果良好
- 过大的 alpha 可能导致丢失细小特征
-
输入要求:
- 三角网格必须是封闭的(用于 Oracle 查询)
- 点云应该足够稠密
- 输入可以有噪声
-
输出特性:
- 保证是封闭流形
- 保证无自交
- 严格包含输入数据
- 可能包含额外的”脚手架”几何
-
性能优化:
- 使用
refine_triangulation参数进行渐进式细化 - 可以使用种子点控制包装的起始位置
- 可以通过访问者实现进度监控和中断
- 使用