使用标准属性
翻译,原文链接
本文包括
- 如何添加和删除一个标准属性
- 如何获取和设置标准属性的值
之前就已经知道,我们可以通过增加自定义属性的方式将额外的数据与网格进行绑定,OpenMesh 中也有不少内置的属性,姑且将之称为标准属性 (standard properties). 与自定义属性不同的是,标准属性具有一些特殊性质和不同的接口,本文将着重说明这些特别之处。
下表列出了不同网格要素 (entity) 能够使用的标准属性
Vertex | Face | Edge | Halfedge | |
---|---|---|---|---|
Color | X | X | X | |
Normal | X | X | X | |
Position (*) | X | |||
Status | X | X | X | X |
TexCoord | X | X |
我们简单地用像request_face_normals()
这样的函数来给某一网格要素添加属性,表中唯一的例外是 Position ,因为顶点坐标是必须一直保存的,所有它不能够人为的添加或删除。
接下来的例子,我们将展示:
- 给网格添加顶点法向量
- 读取网格文件
- 检查文件中是否包含顶点法向量以便确定是否需要重新计算法向量
- 将顶点沿着法向量方向移动一个单位长度
- 用
std::cout
打印结果坐标
首先来添加顶点法向量
mesh.request_vertex_normals();
同样也可以请求添加面法向量
mesh.request_face_normals();
如果读入的网格文件中没有顶点法向量,我们就需要用 update_normals
来计算法向量,我们也可以检查当前网格中有没有顶点法向量
if (!mesh.has_vertex_normals())
{
std::cerr << "ERROR: Standard vertex property 'Normals' not available!\n";
return 1;
}
如果不再需要法向量,可以用下面的函数删除它们
mesh.release_vertex_normals();
但是有一个问题,如果我们对顶点的某一状态属性做了两次请求,那么在调用相关 release
函数时又会发生什么呢?结果是第一次调用 release
函数时,并不会删除该属性,第二次调用时才会真正删除该属性。原因是 OpenMesh 中标准属性都有各自的引用计数 (reference counter),每调用一次 request
函数,计数就加一;相反的每调用一次 release
函数,计数则减一;当计数为零时便删除该属性。
正如表格中所列,OpenMesh 中一共有9中可调用的标准动态属性, 它们 在OpenMesh::Concepts::KernelT 中都有各自的调用函数,具体如下:
- request_edge_status()
- request_edge_colors()
- request_face_colors()
- request_face_normals()
- request_face_status()
- request_face_texture_index()
- request_halfedge_status()
- request_halfedge_normals()
- request_halfedge_texcoords1D()
- request_halfedge_texcoords2D()
- request_halfedge_texcoords3D()
- request_vertex_colors()
- request_vertex_normals()
- request_vertex_status()
- request_vertex_texcoords1D()
- request_vertex_texcoords2D()
- request_vertex_texcoords3D()
相应的删除函数为
- release_edge_status()
- release_edge_colors()
- release_face_colors()
- release_face_normals()
- release_face_status()
- release_face_texture_index()
- release_halfedge_status()
- release_halfedge_normals()
- release_halfedge_texcoords1D()
- release_halfedge_texcoords2D()
- release_halfedge_texcoords3D()
- release_vertex_colors()
- release_vertex_normals()
- release_vertex_status()
- release_vertex_texcoords1D()
- release_vertex_texcoords2D()
- release_vertex_texcoords3D()
以及检查它们是否存在的函数
- has_edge_status()
- has_edge_colors()
- has_face_colors()
- has_face_normals()
- has_face_status()
- has_face_texture_index()
- has_halfedge_status()
- has_halfedge_normals()
- has_halfedge_texcoords1D()
- has_halfedge_texcoords2D()
- has_halfedge_texcoords3D()
- has_vertex_colors()
- has_vertex_normals()
- has_vertex_status()
- has_vertex_texcoords1D()
- has_vertex_texcoords2D()
- has_vertex_texcoords3D()
其中状态属性 (status property) 可以用来标记几何元素,例如被选中 (selected) 或者已删除 (deleted),具体用法参见 Deleting geometry elements
现在我们已经知道如何添加和删除标准属性,那么该怎么获取它们的值呢?与自定义属性一样,我们需要用到网格对象,但不同的是,每一个标准属性都提供各自的 set
和 get
函数,而不是像自定义属性一样用 property()
函数。这里我们看一下怎么将顶点沿着法线方向移动一个单位长度
for (MyMesh::VertexIter v_it = mesh.vertices_begin();
v_it != mesh.vertices_end(); ++v_it)
{
mesh.set_point( *v_it, mesh.point(*v_it)+mesh.normal(*v_it) );
}
get 函数需要输入一个要素 (entity) handle,set 函数则还需要额外输入一个待设定的值。从表中可以看到,并不是所有的要素有能够分配全部的标准属性,比如面通常是没有纹理坐标的,所以如果调用函数 mesh.texcoord2D(_face_handle)
,编译就会报错。
既然我们已经知道怎么添加、删除和获取标准属性,那么这些属性到底保存的是什么类型的数据呢?其中是否还有玄机?且看下回分解。
附上本文完整代码
#include <iostream>
// --------------------
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
typedef OpenMesh::TriMesh_ArrayKernelT<> MyMesh;
int main(int argc, char **argv)
{
MyMesh mesh;
if (argc!=2)
{
std::cerr << "Usage: " << argv[0] << " <input>\n";
return 1;
}
// request vertex normals, so the mesh reader can use normal information
// if available
mesh.request_vertex_normals();
// assure we have vertex normals
if (!mesh.has_vertex_normals())
{
std::cerr << "ERROR: Standard vertex property 'Normals' not available!\n";
return 1;
}
OpenMesh::IO::Options opt;
if ( ! OpenMesh::IO::read_mesh(mesh,argv[1], opt))
{
std::cerr << "Error loading mesh from file " << argv[1] << std::endl;
return 1;
}
// If the file did not provide vertex normals, then calculate them
if ( !opt.check( OpenMesh::IO::Options::VertexNormal ) )
{
// we need face normals to update the vertex normals
mesh.request_face_normals();
// let the mesh update the normals
mesh.update_normals();
// dispose the face normals, as we don't need them anymore
mesh.release_face_normals();
}
// move all vertices one unit length along it's normal direction
for (MyMesh::VertexIter v_it = mesh.vertices_begin();
v_it != mesh.vertices_end(); ++v_it)
{
std::cout << "Vertex #" << *v_it << ": " << mesh.point( *v_it );
mesh.set_point( *v_it, mesh.point(*v_it)+mesh.normal(*v_it) );
std::cout << " moved to " << mesh.point( *v_it ) << std::endl;
}
// don't need the normals anymore? Remove them!
mesh.release_vertex_normals();
// just check if it really works
if (mesh.has_vertex_normals())
{
std::cerr << "Ouch! ERROR! Shouldn't have any vertex normals anymore!\n";
return 1;
}
return 0;
}