开源网格划分软件-OpenMesh 二次开发教程 (3)基本使用

本章详细介绍 OpenMesh 的基本使用方法,涵盖定义网格类型、添加顶点和面、读写网格文件以及执行基本的网格操作。这些内容是 OpenMesh 二次开发的基础,帮助开发者快速掌握网格处理的核心技能。OpenMesh 是一个功能强大的 C++ 库,基于半边数据结构,广泛应用于计算机图形学和几何处理。本章通过详细的代码示例和说明,确保开发者能够轻松上手并实现常见网格处理任务。

3.1 定义网格类型

OpenMesh 提供了几种预定义的网格类型,适用于不同的应用场景。最常用的两种类型是:

  • OpenMesh::PolyMesh_ArrayKernelT<>:用于表示任意多边形网格,面可以有任意数量的边,适合需要灵活拓扑的场景。
  • OpenMesh::TriMesh_ArrayKernelT<>:用于表示三角形网格,所有面均为三角形,适合需要高效算法的场景(如网格平滑或细分)。

对于大多数基本应用,预定义的网格类型已经足够。以下是如何定义一个多边形网格的示例:

#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>

typedef OpenMesh::PolyMesh_ArrayKernelT<> MyMesh;

此代码定义了一个名为 MyMesh 的多边形网格类型,使用数组内核(ArrayKernel),在性能和内存使用之间取得了平衡。如果需要自定义网格类型(例如添加特定属性),可以使用 traits 机制,这将在后续高级主题中讨论。

3.2 添加顶点和面

构建网格需要先添加顶点,然后通过顶点句柄定义面。这是 OpenMesh 中创建几何体的基本步骤。

3.2.1 添加顶点

使用 add_vertex 方法添加顶点,该方法接受一个 MyMesh::Point 对象,表示顶点的 3D 坐标。例如,创建一个立方体需要添加 8 个顶点:

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));

每个顶点通过 add_vertex 返回一个 VertexHandle,用于后续定义面。

3.2.2 添加面

面通过一组顶点句柄定义,使用 add_face 方法。顶点句柄需要按逆时针顺序排列,以确保正确的面法线方向。例如,立方体有 6 个四边形面:

// 添加面
std::vector<MyMesh::VertexHandle> face_vhandles;

// 前
face_vhandles.clear();
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[3]);
mesh.add_face(face_vhandles);

// 后
face_vhandles.clear();
face_vhandles.push_back(vhandle[4]);
face_vhandles.push_back(vhandle[7]);
face_vhandles.push_back(vhandle[6]);
face_vhandles.push_back(vhandle[5]);
mesh.add_face(face_vhandles);

// 左
face_vhandles.clear();
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[3]);
face_vhandles.push_back(vhandle[7]);
face_vhandles.push_back(vhandle[4]);
mesh.add_face(face_vhandles);

// 右
face_vhandles.clear();
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[5]);
face_vhandles.push_back(vhandle[6]);
face_vhandles.push_back(vhandle[2]);
mesh.add_face(face_vhandles);

// 上
face_vhandles.clear();
face_vhandles.push_back(vhandle[3]);
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[6]);
face_vhandles.push_back(vhandle[7]);
mesh.add_face(face_vhandles);

// 下
face_vhandles.clear();
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[4]);
face_vhandles.push_back(vhandle[5]);
face_vhandles.push_back(vhandle[1]);
mesh.add_face(face_vhandles);

此代码创建了一个由 8 个顶点和 6 个四边形面组成的立方体网格。

3.3 读写网格文件

OpenMesh 的 IO 模块支持多种网格文件格式(如 OBJ、OFF、PLY、STL 等)的读写操作。通过 OpenMesh::IO::read_meshOpenMesh::IO::write_mesh 函数,开发者可以轻松导入和导出网格数据。

3.3.1 读取网格文件

使用 read_mesh 函数从文件中加载网格:

#include <OpenMesh/Core/IO/MeshIO.hh>

MyMesh mesh;
if (!OpenMesh::IO::read_mesh(mesh, "input.obj")) {
    std::cerr << "错误:无法从文件读取网格。" << std::endl;
    return 1;
}

3.3.2 写入网格文件

使用 write_mesh 函数将网格保存到文件:

if (!OpenMesh::IO::write_mesh(mesh, "output.off")) {
    std::cerr << "错误:无法将网格写入文件。" << std::endl;
    return 1;
}

3.3.3 使用选项控制读写

通过 OpenMesh::IO::Options 可以控制读写行为,例如指定是否以二进制格式读写,或是否包含特定属性(如顶点法线、颜色等)。以下是一个读取包含顶点法线的网格的示例:

OpenMesh::IO::Options ropt;
ropt += OpenMesh::IO::Options::VertexNormal;
if (!OpenMesh::IO::read_mesh(mesh, "input.obj", ropt)) {
    std::cerr << "错误:无法读取包含顶点法线的网格。" << std::endl;
    return 1;
}

支持的格式和选项如下表所示:

格式/选项ASCII二进制MSBLSBSwap顶点法线顶点颜色顶点纹理坐标边颜色面法线面颜色面纹理坐标颜色Alpha颜色浮点自定义
OBJxxx*xxxx
OFFxxxxxxxxx
PLYxxxxxxxxxxx**
OMxxxxxxxxxx
STLxxxx
VTKx

注释

  • OBJ 支持非标准顶点颜色(仅浮点数)。
  • PLY 支持顶点和面属性的基本类型,无需预请求自定义属性。

3.4 基本网格操作

OpenMesh 提供多种方法来操作网格,包括遍历网格元素、访问邻居顶点以及修改网格拓扑。

3.4.1 遍历网格元素

OpenMesh 使用迭代器遍历网格中的顶点、边和面。例如,遍历所有顶点并打印其坐标:

for (MyMesh::VertexIter v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); ++v_it) {
    MyMesh::Point p = mesh.point(*v_it);
    std::cout << "顶点 " << v_it->idx() << ": " << p << std::endl;
}

遍历所有面:

for (MyMesh::FaceIter f_it = mesh.faces_begin(); f_it != mesh.faces_end(); ++f_it) {
    std::cout << "面 " << f_it->idx() << std::endl;
}

3.4.2 访问邻居顶点

使用环形迭代器(circulators)可以访问一个顶点的一环邻域(直接相邻的顶点)。例如,计算一个顶点的一环邻域的重心:

MyMesh::VertexHandle vhandle = mesh.vertex_handle(0); // 示例顶点
MyMesh::Point cog(0, 0, 0);
int count = 0;
for (MyMesh::VertexVertexIter vv_it = mesh.vv_iter(vhandle); vv_it.is_valid(); ++vv_it) {
    cog += mesh.point(*vv_it);
    ++count;
}
if (count > 0) {
    cog /= count;
}
std::cout << "顶点 0 的重心: " << cog << std::endl;

3.4.3 修改网格拓扑

OpenMesh 支持删除顶点、边和面,但需要先请求状态属性以标记删除:

mesh.request_face_status();
mesh.request_vertex_status();
mesh.request_edge_status();

MyMesh::FaceHandle fhandle = mesh.face_handle(0); // 示例面
mesh.delete_face(fhandle, false);
mesh.garbage_collection();

类似地,删除顶点:

MyMesh::VertexHandle vhandle = mesh.vertex_handle(0); // 示例顶点
mesh.delete_vertex(vhandle, false);
mesh.garbage_collection();

调用 garbage_collection() 确保已标记为删除的元素从内存中移除。

3.5 代码示例

以下是一个完整的示例,展示如何读取一个三角形网格,执行拉普拉斯平滑(将每个顶点移动到其一环邻域的重心),然后将结果保存到文件。

3.5.1 示例代码

#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <iostream>
#include <vector>

typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;

void smooth(MyMesh& mesh, int iterations) {
    for (int i = 0; i < iterations; ++i) {
        // 计算每个顶点的一环邻域重心
        std::vector<MyMesh::Point> cogs(mesh.n_vertices(), MyMesh::Point(0, 0, 0));
        for (MyMesh::VertexIter v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); ++v_it) {
            MyMesh::Point cog(0, 0, 0);
            int count = 0;
            for (MyMesh::VertexVertexIter vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) {
                cog += mesh.point(*vv_it);
                ++count;
            }
            if (count > 0) {
                cog /= count;
            }
            cogs[v_it->idx()] = cog;
        }
        // 更新顶点位置
        for (MyMesh::VertexIter v_it = mesh.vertices_begin(); v_it != mesh.vertices_end(); ++v_it) {
            mesh.set_point(*v_it, cogs[v_it->idx()]);
        }
    }
}

int main(int argc, char** argv) {
    if (argc != 4) {
        std::cerr << "用法: " << argv[0] << " <迭代次数> <输入文件> <输出文件>" << std::endl;
        return 1;
    }

    int iterations = std::stoi(argv[1]);
    MyMesh mesh;

    if (!OpenMesh::IO::read_mesh(mesh, argv[2])) {
        std::cerr << "错误: 无法从 " << argv[2] << " 读取网格" << std::endl;
        return 1;
    }

    smooth(mesh, iterations);

    if (!OpenMesh::IO::write_mesh(mesh, argv[3])) {
        std::cerr << "错误: 无法将网格写入 " << argv[3] << std::endl;
        return 1;
    }

    return 0;
}

3.5.2 示例说明

  • 输入:程序接受三个命令行参数:平滑迭代次数、输入文件名和输出文件名。
  • 操作:读取输入文件中的三角形网格,执行指定次数的拉普拉斯平滑(将每个顶点移动到其一环邻域的重心)。
  • 输出:将平滑后的网格写入输出文件。
  • 用途:此示例展示了如何结合网格读写、遍历和修改操作,适用于网格优化等场景。

3.6 总结

本章介绍了 OpenMesh 的基本使用方法,包括定义网格类型、添加顶点和面、读写网格文件以及执行基本的网格操作。通过详细的代码示例,开发者可以快速掌握这些核心功能,为更复杂的几何处理任务奠定基础。后续章节将探讨高级主题,如自定义属性和网格扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值