使用自定义属性
翻译,原文链接
本文包括:
- 如何添加和删除自定义属性
- 如何获取和设置自定义属性的值
上一篇文章中我们计算所有顶点相邻点的重心并将之存储在数组中,这里我们介绍一种更加方便也更不容易出错的方式,也就是利用 OpenMesh 来管理这些数据。动态的管理网格的属性会更加方便。
通过创建 OpenMesh::PropertyManager
对象,我们可以很方便地创建自定义属性 (custom properties)并将其与网格关联起来。PropertyManager
可以管理这些属性的生命周期并提供读取操作。
我们可以使用预定义的 VProp, HProp,EProp, FProp, MProp
来创建 PropertyManager
将这些动态属性分别关联到顶点 (vertices)、半边 (halfedges)、边(edges) 、面 (faces) 和网格 (mesh)。创建这些属性的时候,需要给相应模板指定属性的数据类型。
值得注意的是,我们要明白具名属性 (Named properties) 和临时属性 (Temporary properties) 之间的差别。临时属性在创建的时候,其构造函数必须输入网格作为参数,相应的属性也立即创建,这些属性也会随着临时属性的 PropertyManager
脱离作用范围 (应该是被析构掉了),而立马被删除。如果一个属性在创建的时候,提供了属性名称,那么即使该具名属性的 PropertyManager
已经不存在了,该属性数据依旧存在。如果我们在创建一个具有相同名称的 PropertyManager
,就可以读取和改写之前已经存在的属性。(译注:属性 property 实际存储在于网格相关的数据结构中,PropertyManager
只是提供了一个方便管理属性的工具)
最后,PropertyManager
的第一个可选参数可以用来指定属性的初始值。下面的代码给出了创建网格属性的例子。
// Add a temporary mesh property that stores a double value for every vertex
auto temperature = OpenMesh::VProp<double>(mesh);
OpenMesh::VertexHandle vh = ...;
temperature[vh] = 1.0;
// The temperature property will be removed from the mesh when the handle reaches the end of the scope.
// Obtain an existing property that stores a 2D vector for every halfedge
// (or create that property if it does not exist already) and initilize it with the Vector(1,1))
auto uv = OpenMesh::HProp<OpenMesh::Vec2d>(mesh, "uv", OpenMesh::Vec2d(1,1));
OpenMesh::VertexHandle heh = ...;
std::cout << temperature[heh][0] << " " << temperature[heh][1] << std::endl;
// Obtain an existing mesh property (or create that property if it does not exist already)
// containing a description string
auto desc = OpenMesh::MProp<std::string>(mesh, "desc");
*desc = "This is a very nice mesh.";
下面我们将之前的重心平滑代码用顶点属性 (vertex property) 的方式重新写一遍。首先需要创建一个临时的顶点属性管理器,其属性数据类型为 MyMesh::Point
auto cog = OpenMesh::VProp<MyMesh::Point>(mesh);
这行代码会根据顶点数量分配足够的内存来存储这些顶点属性,接下来计算每个顶点相邻点的重心坐标
for (const auto& vh : mesh.vertices()) {
cog[vh] = {0,0,0};
int valence = 0;
// Iterate over all 1-ring vertices around vh
for (const auto& vvh : mesh.vv_range(vh)) {
cog[vh] += mesh.point(vvh);
++valence;
}
cog[vh] /= valence;
}
最后,将顶点坐标设置为重心坐标
for (const auto& vh : mesh.vertices()) {
mesh.point(vh) = cog[vh];
}
完整代码如下
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/DefaultTriMesh.hh>
#include <OpenMesh/Core/Utils/PropertyManager.hh>
#include <iostream>
#include <vector>
using MyMesh = OpenMesh::TriMesh;
int main(int argc, char** argv)
{
// Read command line options
MyMesh mesh;
if (argc != 4) {
std::cerr << "Usage: " << argv[0] << " #iterations infile outfile" << std::endl;
return 1;
}
const int iterations = argv[1];
const std::string infile = argv[2];
const std::string outfile = argv[3];
// Read mesh file
if (!OpenMesh::IO::read_mesh(mesh, infile)) {
std::cerr << "Error: Cannot read mesh from " << infile << std::endl;
return 1;
}
{
// Add a vertex property storing the computed centers of gravity
auto cog = OpenMesh::VProp<MyMesh::Point>(mesh);
// Smooth the mesh several times
for (int i = 0; i < iterations; ++i) {
// Iterate over all vertices to compute centers of gravity
for (const auto& vh : mesh.vertices()) {
cog[vh] = {0,0,0};
int valence = 0;
// Iterate over all 1-ring vertices around vh
for (const auto& vvh : mesh.vv_range(vh)) {
cog[vh] += mesh.point(vvh);
++valence;
}
cog[vh] /= valence;
}
// Move all vertices to the previously computed positions
for (const auto& vh : mesh.vertices()) {
mesh.point(vh) = cog[vh];
}
}
} // The cog vertex property is removed from the mesh at the end of this scope
// Write mesh file
if (!OpenMesh::IO::read_mesh(mesh, outfile)) {
std::cerr << "Error: Cannot write mesh to " << outfile << std::endl;
return 1;
}
}
属性的生命周期
在上面的代码中,我们创建 VProp 的时候并没赋予名称,这就导致该属性在 其 handle 变量cog
离开其作用范围时自动被删除了。如果我们想让一个属性在其 handle 变量作用范围之外仍然继续存在,就需要在创建它的时候为之赋名,就像下面的代码
auto face_area = OpenMesh::FProp<double>(mesh, "face_area");
在之后的代码中,我们可以用过名称来获取该属性。如果想确定我们想要的属性究竟是否存在,可以用函数 hasProperty()
来查询一下,就像下面的代码
if (OpenMesh::hasProperty<OpenMesh::FaceHandle, double>(mesh, "face_area")) {
// Property exists. Do something with it.
auto valley = OpenMesh::FProp<bool>(mesh, "face_area");
}
else {
// Property does not exist. Do something else.
}
Low-Level Property API
诸如 VProp, HProp, EProp, FProp, MProp
等皆为高级的属性管理接口,除此之外,OpenMesh也提供了一些低级的接口来手动管理属性的生命周期。这些接口函数在 mesh 中函数中, add_property, get_propery, remove_perperty, property
以及其他一些 property handle classes (OpenMesh::VPropHandleT, OpenMesh::HPropHandleT, OpenMesh::EPropHandleT, OpenMesh::FPropHandleT, OpenMesh::MPropHandleT))