1. 程式人生 > 實用技巧 >DOC - Using and understanding OpenMesh

DOC - Using and understanding OpenMesh

DOC - Using and understanding OpenMesh

目錄

本文是對OpenMesh文件中《Using and understanding OpenMesh》一節的學習摘錄。

Features and Goals of OpenMesh

資料結構主要特徵是:

  • 並不受限於三角形網格,可處理一般的多邊形網格;
  • 頂點,半邊,邊,面的明確表示;
  • 高效的訪問一個頂點的鄰域;
  • 能夠處理非流形頂點(如兩個面,共享一個頂點)。

The Halfedge Data Structure

半邊資料結構中是將一條邊,劃分成了兩條方向相對的半邊,不同元素之間的連線關係,如下圖所示:

  • 每個頂點儲存了從它出發的半邊,圖中為1;
  • 每個面儲存了周圍的一條半邊,圖中為2;
  • 每個半邊儲存了以下handle:
    • 指向的頂點,圖中為3;
    • 屬於哪個面,圖中為4;
    • 共享面的下一個半邊(逆時針方向),圖中為5;
    • 相對的半邊,圖中為6;
    • 可選的,共享面的上一個半邊,圖中為7。

通過上面構建的關係,可以很方便的,對一個面周圍的半邊,頂點,相鄰面進行遍歷。

Mesh Iterators and Circulators

網格提供了迭代器,用來訪問vertices,halfedges,edges,faces。所有的迭代器定義在OpenMesh::Iterators名稱空間中,在具體使用的時候,可以用mesh內部的迭代器MyMesh::VertexIter取代OpenMesh::Iterators::VertexIterT<MyMesh>的形式。

迭代器的使用示例如下:

MyMesh mesh;

// iterate over all vertices
for (MyMesh::VertexIter v_it=mesh.vertices_begin(); v_it!=mesh.vertices_end(); ++v_it) 
   ...; // do something with *v_it, v_it->, or *v_it
// iterate over all halfedges
for (MyMesh::HalfedgeIter h_it=mesh.halfedges_begin(); h_it!=mesh.halfedges_end(); ++h_it) 
   ...; // do something with *h_it, h_it->, or *h_it
// iterate over all edges
for (MyMesh::EdgeIter e_it=mesh.edges_begin(); e_it!=mesh.edges_end(); ++e_it) 
   ...; // do something with *e_it, e_it->, or *e_it
// iterator over all faces
for (MyMesh::FaceIter f_it=mesh.faces_begin(); f_it!=mesh.faces_end(); ++f_it) 
   ...; // do something with *f_it, f_it->, or *f_it

刪除的元素

如果mesh中沒有元素被標記為刪除,idx()返回的值依次從0到number of elements - 1

但是如果有元素被標記為刪除,同時沒有執行垃圾回收(OpenMesh::ArrayKernel::garbage_collection() ),有效的idx並不是按順序依次排列的。執行完垃圾回收之後,順序會被重新調整。

OpenMesh使用惰性刪除方案,以避免不必要的資料結構更新。 半邊資料結構將始終直接進行更新,以確保以下演算法具有正確的迭代器設定。

如果你刪除一個面,這個面本身是存在的,但是位於hole的半邊會更新,這意味著相鄰頂點上的迴圈器將不再碰到該面。

如果刪除了一條邊,相鄰的面也將被刪除(標記它們已刪除並更新周圍的半邊)。 邊本身也將標記為已刪除。 同樣,迴圈器將不再看到刪除的元素。

對於頂點,將使用上述方案刪除所有相鄰的面和邊,並將頂點標記為已刪除。

此時,迭代器仍然能夠訪問到所有的元素(包括,標記為刪除的)。如果你使用skipping iterators,將會跳過刪除的元素。

skipping iterators如下:

  • vertices_sbegin();
  • edges_sbegin();
  • halfedges_sbegin();
  • faces_sbegin();

Circulators

用來快速訪問鄰域的迭代器。如,VertexVertexIter用來訪問頂點的1-ring頂點;FaceHalfedgeIter用來訪問屬於這個面的半邊;CenterItem_AuxiliaryInformation_TargetItem_Iter用來訪問中心元素周圍的所有相鄰元素。主要有:

  • VertexVertexIter: 頂點的所有1-ring頂點;
  • VertexIHalfedgeIter: 頂點的所有入半邊;
  • VertexOHalfedgeIter: 頂點的所有出半邊;
  • VertexEdgeIter: 頂點的所有相鄰邊;
  • VertexFaceIter: 頂點的所有相鄰面;
  • FaceVertexIter:面上的頂點;
  • FaceHalfedgeIter:面上的半邊;
  • FaceEdgeIter:面上的邊;
  • FaceFaceIter: 面的相鄰面;
  • HalfedgeLoopIter: 順序連結的半邊;

獲取以上迭代器的函式如下(defined in OpenMesh::PolyConnectivity):

/**************************************************
 * Vertex circulators
 **************************************************/
// Get the vertex-vertex circulator (1-ring) of vertex _vh
VertexVertexIter OpenMesh::PolyConnectivity::vv_iter (VertexHandle _vh);
// Get the vertex-incoming halfedges circulator of vertex _vh
VertexIHalfedgeIter OpenMesh::PolyConnectivity::vih_iter (VertexHandle _vh);
// Get the vertex-outgoing halfedges circulator of vertex _vh
VertexOHalfedgeIter OpenMesh::PolyConnectivity::voh_iter (VertexHandle _vh);
// Get the vertex-edge circulator of vertex _vh
VertexEdgeIter OpenMesh::PolyConnectivity::ve_iter (VertexHandle _vh);
// Get the vertex-face circulator of vertex _vh
VertexFaceIter OpenMesh::PolyConnectivity::vf_iter (VertexHandle _vh);
/**************************************************
 * Face circulators
 **************************************************/
// Get the face-vertex circulator of face _fh
FaceVertexIter OpenMesh::PolyConnectivity::fv_iter (FaceHandle _fh);
// Get the face-halfedge circulator of face _fh
FaceHalfedgeIter OpenMesh::PolyConnectivity::fh_iter (FaceHandle _fh);
// Get the face-edge circulator of face _fh
FaceEdgeIter OpenMesh::PolyConnectivity::fe_iter (FaceHandle _fh);
// Get the face-face circulator of face _fh
FaceFaceIter OpenMesh::PolyConnectivity::ff_iter (FaceHandle _fh);

使用示例:

MyMesh mesh;
// (linearly) iterate over all vertices
for (MyMesh::VertexIter v_it=mesh.vertices_sbegin(); v_it!=mesh.vertices_end(); ++v_it)
{
  // circulate around the current vertex
  for (MyMesh::VertexVertexIter vv_it=mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it)
  {
    // do something with e.g. mesh.point(*vv_it)
  }
}

How to navigate on mesh

對半邊進行順序訪問:

[...]
TriMesh::HalfedgeHandle heh, heh_init;
// Get the halfedge handle assigned to vertex[0]
heh = heh_init = mesh.halfedge_handle(vertex[0].handle());
// heh now holds the handle to the initial halfedge.
// We now get further on the boundary by requesting
// the next halfedge adjacent to the vertex heh
// points to...
heh = mesh.next_halfedge_handle(heh);
// We can do this as often as we want:
while(heh != heh_init) {
        heh = mesh.next_halfedge_handle(heh);
}
[...]

判斷邊界相關的函式(OpenMesh::PolyConnectivity::is_boundary().):

// Test if a halfedge lies at a boundary (is not adjacent to a face)
bool is_boundary (HalfedgeHandle _heh) const
// Test if an edge lies at a boundary
bool is_boundary (EdgeHandle _eh) const
// Test if a vertex is adjacent to a boundary edge
bool is_boundary (VertexHandle _vh) const
// Test if a face has at least one adjacent boundary edge.
// If _check_vertex=true, this function also tests if at least one
// of the adjacent vertices is a boundary vertex
bool is_boundary (FaceHandle _fh, bool _check_vertex=false) const

獲取邊的from,to頂點:

// Get the handle of the to vertex
OpenMesh::Concepts::KernelT::to_vertex_handle();
// Get the handle of the from vertex
OpenMesh::Concepts::KernelT::from_vertex_handle();

Read and write meshes from files

示例如下:

#include <OpenMesh/Core/IO/MeshIO.hh>
MyMesh mesh;
if (!OpenMesh::IO::read_mesh(mesh, "some input file")) 
{
  std::cerr << "read error\n";
  exit(1);
}
// do something with your mesh ...
if (!OpenMesh::IO::write_mesh(mesh, "some output file")) 
{
  std::cerr << "write error\n";
  exit(1);
}

詳細示例見:https://www.graphics.rwth-aachen.de/media/openmesh_static/Documentations/OpenMesh-Doc-Latest/a04344.html

Some basic operations: Flipping and collapsing edge

Flipping edges

TriMesh mesh;
// Add some vertices
TriMesh::VertexHandle vhandle[4];
vhandle[0] = mesh.add_vertex(MyMesh::Point(0, 0, 0));
vhandle[1] = mesh.add_vertex(MyMesh::Point(0, 1, 0));
vhandle[2] = mesh.add_vertex(MyMesh::Point(1, 1, 0));
vhandle[3] = mesh.add_vertex(MyMesh::Point(1, 0, 0));
// Add two faces
std::vector<TriMesh::VertexHandle> face_vhandles;
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[0]);
mesh.add_face(face_vhandles);
face_vhandles.clear();
face_vhandles.push_back(vhandle[2]);
face_vhandles.push_back(vhandle[0]);
face_vhandles.push_back(vhandle[3]);
mesh.add_face(face_vhandles);
// Now the edge adjacent to the two faces connects
// vertex vhandle[0] and vhandle[2].
// Find this edge and then flip it
for(TriMesh::EdgeIter it = mesh.edges_begin(); it != mesh.edges_end(); ++it) {
        if(!mesh.is_boundary(*it)) {
                // Flip edge
                mesh.flip(*it);
        }
}
// The edge now connects vertex vhandle[1] and vhandle[3].

Collapsing edges

將from_vertex塌陷成to_vertex。

PolyMesh mesh;
// Request required status flags
mesh.request_vertex_status();
mesh.request_edge_status();
mesh.request_face_status();
// Add some vertices as in the illustration above
PolyMesh::VertexHandle vhandle[7];
vhandle[0] = mesh.add_vertex(MyMesh::Point(-1, 1, 0));
vhandle[1] = mesh.add_vertex(MyMesh::Point(-1, 3, 0));
vhandle[2] = mesh.add_vertex(MyMesh::Point(0, 0, 0));
vhandle[3] = mesh.add_vertex(MyMesh::Point(0, 2, 0));
vhandle[4] = mesh.add_vertex(MyMesh::Point(0, 4, 0));
vhandle[5] = mesh.add_vertex(MyMesh::Point(1, 1, 0));
vhandle[6] = mesh.add_vertex(MyMesh::Point(1, 3, 0));
// Add three quad faces
std::vector<PolyMesh::VertexHandle> face_vhandles;
face_vhandles.push_back(vhandle[1]);
face_vhandles.push_back(vhandle[0]);
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[1]);
face_vhandles.push_back(vhandle[3]);
face_vhandles.push_back(vhandle[5]);
face_vhandles.push_back(vhandle[4]);
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[5]);
mesh.add_face(face_vhandles);
// Now find the edge between vertex vhandle[2]
// and vhandle[3]
for(PolyMesh::HalfedgeIter it = mesh.halfedges_begin(); it != mesh.halfedges_end(); ++it) {
  if( mesh.to_vertex_handle(*it) == vhandle[3] &&
      mesh.from_vertex_handle(*it) == vhandle[2])
  {
    // Collapse edge
    mesh.collapse(*it);
    break;
  }
}
// Our mesh now looks like in the illustration above after the collapsing.

Conceptual Class Hierarchy

概念相關類的繼承關係如下(大多通過模板引數的形式實現整合):

Specifying your MyMesh

自定義一個mesh,需要有如下步驟:

  1. 明確是三角網格,還是多邊形網格;(通常情況下選擇三角網格
  2. 選擇mesh kernel;mesh kernel特例化了mesh 屬性在內部的儲存形式。預設的kernel是ArrayKernelT
  3. 使用Traits類,引數化mesh。可以向網格項新增任意items,如標量,點,法線和顏色型別,並使用預定義屬性,例如Attributes :: Normal和Attributes :: Color。
  4. 使用自定義型別,動態繫結資料到mesh,或者是mesh的entities(vertex,(half-)edge,face)。

Mesh Traits

下面需要使用者自定義實現,需要提供的型別有:

  • point和scalar型別:MyMesh::PointMyMesh::Scalar
  • mesh items:MyMesh::Vertex,MyMesh::Halfedge,MyMesh::Edge,MyMesh::Face;
  • handle型別:MyMesh::VertexHandle,MyMesh::HalfedgeHandle,MyMesh::EdgeHandle,MyMesh::FaceHandle

預設的traits類似如下:

struct DefaultTraits
{
    typedef Vec3f Point;
    typedef Vec3f Normal;
    typedef Vec2f TexCoord;
    typedef Vec3uc Color;
    
    VertexTraits    {};
    HalfedgeTraits  {};
    EdgeTraits      {};
    FaceTraits      {};
    
    VertexAttributes(0);
    HalfedgeAttributes(Attributes::PreHalfedge);
    EdgeAttributes(0);
    FaceAttributes(0);
};

建立自定義traits的時候需要從上面繼承建立。如果需要改變point的型別,可以進行如下操作:

struct MyTraits : public OpenMesh::DefaultTraits
{
    typedef OpenMesh::Vec3d Point;
};

Adding Predefined Attributes

有一些預定義的attributes可以新增到mesh items中。這些全域性的屬性定義在OpenMesh::Attributes.命名看空間中。如果想要向頂點新增法向量和顏色,向面上新增法向量,那麼可以如下操作:

struct MyTraits : public OpenMesh::DefaultTraits
{
    VertexAttributes( OpenMesh::Attributes::Normal | OpenMesh::Attributes::Color);
    FaceAttribtues(OpenMesh::Attributes::Normal);
};

對於屬性提供執行時檢測,和編譯時檢測,執行時檢測如下:

if (OM_Check_Attrib(MyMesh::Vertex, Normal))
    do_something_with_normals();

編譯時檢測如下:

#include <OpenMesh/Core/Utils/GenProg.hh>

// draw a face normal if we have one
void drawFaceNormal(const MyMesh::Face& _f) { 
  drawFaceNormal(_f, GenProg::Bool2Type<OM_Check_Attrib(MyMesh::Face, Normal)>()); 
}
// normal exists -> use it
void drawFaceNormal(const MyMesh::Face& _f, GenProg::Bool2Type<true>) { 
  glNormal3fv(_f.normal());
}
// empty dummy (no normals)
void drawFaceNormal(const MyMesh::Face& _f, GenProg::Bool2Type<false>){}

Adding User-Defined Elements

struct MyTraits : public OpenMesh::DefaultTraits
{
  VertexTraits
  {
    int some_additional_index;
  };
};

// 巨集定義展開後得到如下形式:
struct MyTraits : public OpenMesh::DefaultTraits
{
  template <class Base, class Refs> struct VertexT : public Base
  {
    int some_additional_index;
  };
};

Final Implementation Example

#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
// define traits
struct MyTraits : public OpenMesh::DefaultTraits
{
  // use double valued coordinates
  typedef OpenMesh::Vec3d Point;
  // use vertex normals and vertex colors
  VertexAttributes( OpenMesh::DefaultAttributer::Normal |
                    OpenMesh::DefaultAttributer::Color );
  // store the previous halfedge
  HalfedgeAttributes( OpenMesh::DefaultAttributer::PrevHalfedge );
  // use face normals
  FaceAttributes( OpenMesh::DefaultAttributer::Normal );
  // store a face handle for each vertex
  VertexTraits
  {
    typename Base::Refs::FaceHandle my_face_handle;
  };
};
// Select mesh type (TriMesh) and kernel (ArrayKernel)
// and define my personal mesh type (MyMesh)
typedef OpenMesh::TriMesh_ArrayKernelT<MyTraits>  MyMesh;
int main(int argc, char **argv)
{
  MyMesh mesh;
  // -------------------- Add dynamic data
  // for each vertex an extra double value
  OpenMesh::VPropHandleT< double > vprop_double;
  mesh.add_property( vprop_double );
  // for the mesh an extra string
  OpenMesh::MPropHandleT< string > mprop_string;
  mesh.add_property( mprop_string );
  // -------------------- do something
  ...;
}

Specifying an OpenMesh using Eigen3 vectors

#include <OpenMesh/Core/Geometry/EigenVectorT.hh>

struct EigenTraits : OpenMesh::DefaultTraits {
    using Point = Eigen::Vector3d;
    using Normal = Eigen::Vector3d;
    using TexCoord2D = Eigen::Vector2d;
};
using EigenTriMesh = OpenMesh::TriMesh_ArrayKernelT<EigenTraits>;
EigenTriMesh mesh;