本章深入探讨 OpenMesh 的高级功能,包括使用标准和自定义属性、自定义网格特性、网格扩展以及删除几何元素。这些功能使开发者能够实现复杂的网格处理任务,例如动态数据管理、自定义网格行为和拓扑修改。OpenMesh 是一个基于半边数据结构的 C++ 库,广泛应用于计算机图形学和几何处理。本章通过详细的说明和代码示例,帮助开发者掌握这些高级技术。
4.1 使用标准属性
OpenMesh 提供了多种标准属性(standard properties),这些属性可以附加到网格元素(如顶点、面、边和半边)上,用于存储常见数据,如颜色、法线、位置、状态和纹理坐标。除了位置属性(始终可用)外,其他属性需要通过特定的请求函数启用,并在不再需要时释放,以优化内存使用。
4.1.1 标准属性的类型和函数
以下表格总结了 OpenMesh 的标准属性及其相关函数:
属性 | 顶点 | 面 | 边 | 半边 | 请求函数 | 释放函数 | 检查函数 |
---|---|---|---|---|---|---|---|
颜色 | X | X | X | request_vertex_colors() , request_face_colors() , request_edge_colors() | release_vertex_colors() , release_face_colors() , release_edge_colors() | has_vertex_colors() , has_face_colors() , has_edge_colors() | |
法线 | X | X | X | request_vertex_normals() , request_face_normals() , request_halfedge_normals() | release_vertex_normals() , release_face_normals() , release_halfedge_normals() | has_vertex_normals() , has_face_normals() , has_halfedge_normals() | |
位置 | X | (始终可用,无需请求) | (不可移除) | - | |||
状态 | X | X | X | X | request_vertex_status() , request_face_status() , request_edge_status() , request_halfedge_status() | release_vertex_status() , release_face_status() , release_edge_status() , release_halfedge_status() | has_vertex_status() , has_face_status() , has_edge_status() , has_halfedge_status() |
纹理坐标 | X | X | request_vertex_texcoords1D() , request_vertex_texcoords2D() , request_vertex_texcoords3D() , request_halfedge_texcoords1D() , request_halfedge_texcoords2D() , request_halfedge_texcoords3D() | release_vertex_texcoords1D() , release_vertex_texcoords2D() , release_vertex_texcoords3D() , release_halfedge_texcoords1D() , release_halfedge_texcoords2D() , release_halfedge_texcoords3D() | has_vertex_texcoords1D() , has_vertex_texcoords2D() , has_vertex_texcoords3D() , has_halfedge_texcoords1D() , has_halfedge_texcoords2D() , has_halfedge_texcoords3D() | ||
纹理索引 | X | request_face_texture_index() | release_face_texture_index() | has_face_texture_index() |
- 动态请求:共 9 种属性可动态请求。
- 参考计数器:请求属性时计数器增加,释放时减少,计数为 0 时属性移除。
- 位置属性:始终可用,无法移除。
- 状态属性:用于标记删除状态,详见 4.5 删除几何元素。
4.1.2 使用标准属性的示例
以下示例展示如何请求和使用顶点法线和面颜色:
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <iostream>
typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
int main() {
MyMesh mesh;
// 请求顶点法线和面颜色
mesh.request_vertex_normals();
mesh.request_face_colors();
// 添加顶点和面(示例)
MyMesh::VertexHandle vh = mesh.add_vertex(MyMesh::Point(0, 0, 0));
std::vector<MyMesh::VertexHandle> face_vhandles;
face_vhandles.push_back(mesh.add_vertex(MyMesh::Point(1, 0, 0)));
face_vhandles.push_back(mesh.add_vertex(MyMesh::Point(0, 1, 0)));
face_vhandles.push_back(mesh.add_vertex(MyMesh::Point(0, 0, 1)));
MyMesh::FaceHandle fh = mesh.add_face(face_vhandles);
// 设置顶点法线
mesh.set_normal(vh, MyMesh::Normal(0, 0, 1));
// 设置面颜色
mesh.set_color(fh, MyMesh::Color(1.0f, 0.0f, 0.0f)); // 红色
// 获取并打印
MyMesh::Normal n = mesh.normal(vh);
MyMesh::Color c = mesh.color(fh);
std::cout << "顶点法线: " << n << std::endl;
std::cout << "面颜色: " << c << std::endl;
// 释放属性
mesh.release_vertex_normals();
mesh.release_face_colors();
return 0;
}
注意事项:
- 确保在使用属性前调用请求函数,否则会导致未定义行为。
- 使用完毕后释放属性以减少内存占用。
- 状态属性在删除几何元素时特别重要。
4.2 使用自定义属性
自定义属性(custom properties)允许开发者为网格元素动态附加任意数据,例如顶点的温度或面的权重。OpenMesh 提供了 OpenMesh::PropertyManager
类来简化属性的创建和管理。
4.2.1 自定义属性的类型
自定义属性可以附加到以下网格元素:
- VProp:顶点属性
- HProp:半边属性
- EProp:边属性
- FProp:面属性
- MProp:网格属性
4.2.2 创建和使用自定义属性
自定义属性可以是临时的(作用域结束时移除)或命名的(持久存在)。以下是示例:
-
临时顶点属性(存储温度):
auto temperature = OpenMesh::VProp<double>(mesh); MyMesh::VertexHandle vh = mesh.add_vertex(MyMesh::Point(0, 0, 0)); temperature[vh] = 1.0; // 设置温度 std::cout << "顶点温度: " << temperature[vh] << std::endl;
-
命名网格属性(存储描述):
auto desc = OpenMesh::MProp<std::string>(mesh, "description"); *desc = "这是一个非常棒的网格!"; std::cout << "网格描述: " << *desc << std::endl;
4.2.3 自定义属性的生命周期
- 临时属性:在
PropertyManager
对象超出作用域时自动移除。 - 命名属性:通过名称持久存储,可通过
mesh.hasProperty("name")
检查是否存在。
4.2.4 低级别 API
开发者也可以使用低级别 API 管理属性,例如:
add_property()
:添加属性get_property()
:获取属性remove_property()
:移除属性property()
:访问属性值
示例(低级别 API):
OpenMesh::VPropHandleT<double> temp_handle;
mesh.add_property(temp_handle, "temperature");
mesh.property(temp_handle, vh) = 1.0;
注意事项:
- 命名属性适合长期存储,临时属性适合短期计算。
- 使用
PropertyManager
比低级别 API 更简洁,推荐优先使用。
4.3 网格特性和属性
网格特性(traits)允许开发者自定义网格元素的数据类型和附加属性。默认情况下,OpenMesh 使用 OpenMesh::DefaultTraits
,定义了点(Point
)、法线(Normal
)等的数据类型。
4.3.1 自定义数据类型
可以通过派生 OpenMesh::DefaultTraits
来更改默认数据类型。例如,将点和法线改为双精度(double
):
struct MyTraits : public OpenMesh::DefaultTraits {
typedef OpenMesh::Vec3d Point; // 双精度点
typedef OpenMesh::Vec3d Normal; // 双精度法线
};
typedef OpenMesh::TriMesh_ArrayKernelT<MyTraits> MyMesh;
4.3.2 禁用 PrevHalfedge 属性
PrevHalfedge
属性加速半边遍历,但增加内存消耗。可以通过禁用它来节省内存:
struct MyTraits : public OpenMesh::DefaultTraits {
HalfedgeAttributes(OpenMesh::Attributes::None); // 禁用 PrevHalfedge
};
内存节省:对于三角形网格(genus $g=0$),边数 $E \approx 3 \cdot V$,禁用 PrevHalfedge
可为每条边节省约 8 字节内存。
4.4 扩展网格特性
通过扩展网格特性,开发者可以为网格元素添加自定义数据成员和方法。这是一种静态配置方式,适合需要固定附加数据的场景。
4.4.1 示例:为顶点添加重心
以下示例为顶点添加一个重心(cog_
)成员,用于平滑操作:
struct MyVertexTraits {
OpenMesh::Vec3f cog_;
void set_cog(const OpenMesh::Vec3f& _cog) { cog_ = _cog; }
OpenMesh::Vec3f cog() const { return cog_; }
};
struct MyTraits : public OpenMesh::DefaultTraits {
typedef MyVertexTraits VertexTraits;
};
typedef OpenMesh::TriMesh_ArrayKernelT<MyTraits> MyMesh;
使用示例:
MyMesh mesh;
MyMesh::VertexHandle vh = mesh.add_vertex(MyMesh::Point(0, 0, 0));
mesh.data(vh).set_cog(OpenMesh::Vec3f(1, 1, 1));
std::cout << "顶点重心: " << mesh.data(vh).cog() << std::endl;
注意事项:
- 特性是静态的,无法在运行时更改。
- 适合需要固定附加数据的场景,如算法特定的数据结构。
4.5 删除几何元素
删除几何元素(如顶点、边、面)需要先请求状态属性,标记元素为已删除,然后执行垃圾回收以移除它们。
4.5.1 请求状态属性
在删除操作前,必须请求状态属性:
- 面:
mesh.request_face_status()
- 边:
mesh.request_edge_status()
- 顶点:
mesh.request_vertex_status()
4.5.2 标记和删除
使用 delete_*
方法标记元素为已删除:
- 删除面:
mesh.delete_face(fh, false)
- 删除顶点:
mesh.delete_vertex(vh, false)
第二个参数决定是否自动删除孤立顶点(true
表示自动删除)。
4.5.3 垃圾回收
调用 mesh.garbage_collection()
移除标记为删除的元素:
mesh.garbage_collection();
4.5.4 示例:删除立方体的部分面
以下示例从立方体中删除除一个面外的所有面:
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include <vector>
typedef OpenMesh::PolyMesh_ArrayKernelT<> MyMesh;
int main() {
MyMesh mesh;
// 添加立方体顶点和面(示例)
MyMesh::VertexHandle vhandle[8];
vhandle[0] = mesh.add_vertex(MyMesh::Point(-1, -1, 1));
vhandle[1] = mesh.add_vertex(MyMesh::Point(1, -1, 1));
vhandle[2] = mesh.add_vertex(MyMesh::Point(1, 1, 1));
vhandle[3] = mesh.add_vertex(MyMesh::Point(-1, 1, 1));
vhandle[4] = mesh.add_vertex(MyMesh::Point(-1, -1, -1));
vhandle[5] = mesh.add_vertex(MyMesh::Point(1, -1, -1));
vhandle[6] = mesh.add_vertex(MyMesh::Point(1, 1, -1));
vhandle[7] = mesh.add_vertex(MyMesh::Point(-1, 1, -1));
std::vector<MyMesh::VertexHandle> face_vhandles;
std::vector<MyMesh::FaceHandle> fhandle;
// 添加 6 个面
face_vhandles = {vhandle[0], vhandle[1], vhandle[2], vhandle[3]};
fhandle.push_back(mesh.add_face(face_vhandles));
face_vhandles = {vhandle[4], vhandle[7], vhandle[6], vhandle[5]};
fhandle.push_back(mesh.add_face(face_vhandles));
face_vhandles = {vhandle[0], vhandle[3], vhandle[7], vhandle[4]};
fhandle.push_back(mesh.add_face(face_vhandles));
face_vhandles = {vhandle[1], vhandle[5], vhandle[6], vhandle[2]};
fhandle.push_back(mesh.add_face(face_vhandles));
face_vhandles = {vhandle[3], vhandle[2], vhandle[6], vhandle[7]};
fhandle.push_back(mesh.add_face(face_vhandles));
face_vhandles = {vhandle[0], vhandle[4], vhandle[5], vhandle[1]};
fhandle.push_back(mesh.add_face(face_vhandles));
// 请求状态属性
mesh.request_face_status();
mesh.request_vertex_status();
mesh.request_edge_status();
// 删除除 fhandle[1] 外的所有面
for (size_t i = 0; i < fhandle.size(); ++i) {
if (i != 1) {
mesh.delete_face(fhandle[i], false);
}
}
// 执行垃圾回收
mesh.garbage_collection();
return 0;
}
注意事项:
- 确保请求状态属性,否则删除操作会失败。
- 垃圾回收是必需的,否则标记为删除的元素仍占用内存。
- 半边无法直接删除,随父边删除。
4.6 总结
本章详细介绍了 OpenMesh 的高级功能,包括标准和自定义属性的使用、网格特性的自定义、网格扩展以及几何元素的删除。这些功能为开发者提供了灵活的工具,用于实现复杂的网格处理任务。通过提供的代码示例,开发者可以快速掌握这些技术,并将其应用于实际项目中。后续章节将进一步探讨 OpenMesh 的应用场景和优化技巧。