OpenMeshの使い方
インストール方法
OpenMeshを使っているのでここから最新バージョンをダウンロードしてインストールしたときのメモです。
OSはUbuntu 16.04LTSです。
ダウンロードからインストールまで
cd ~/Downloads
wget https://www.openmesh.org/media/Releases/6.3/OpenMesh-6.3.tar.gz
tar zxf OpenMesh-6.3.tar.gz
cd OpenMesh-6.3
mkdir build
cd build
cmake .. && sudo make all install
管理者権限がなくローカルディレクトリにインストールする場合は
cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/local/dir .. && make all install
となります。
共有・静的ライブラリのみ使用する場合、-DBUILD_APPS:BOOL=OFF
を追加するとアプリケーションをビルドしなくなるので早く終わります。
補足
もしビルド後に○○が見つからないというエラーメッセージが出ていたらインストールしたうえで再度ビルドしましょう。私の場合は下記が見つからないと出たので関連ライブラリ(括弧内)をインストールしました。
- glut (freeglut3, freeglut3-dev)
- latex compiler (texlive)
- doxygen
- boost python (libboost-dev, libboost-python-dev, libboost-system-dev)
自作プログラムのコンパイル方法
自作プログラムでOpenMeshを使用した場合、コンパイル時にインクルードファイルと共有ライブラリへのパスを指定してあげる必要があります。前節にしたがってOpenMeshを~/.local
にインストールしたとすると、次のようなオプション指定が必要となります。
- インクルードファイルへのパス:
-isystem $HOME/.local/include
- 共有ライブラリへのパス:
-Xlinker -rpath -Xlinker $HOME/.local/lib
- 共有ライブラリ:
-lOpenMeshCore
および-lOpenMeshTools
使い方
OpenMesh Documentationを参照しながら手探りで使い方を学びました。このDocumentationはかなりわかりにくく、検索してクラス定義を追おうとしてもたどり着かないことが多いです。
とにかく小さなコードを書いてコンパイルし、エラーメッセージから実際に何が行われているか確認するのが一番です。
メッシュデータ構造
OpenMeshではhalfedge(半辺?)データ構造を使ってメッシュを定義しているのが特徴です。
OpenMesh Documentaion: The Halfedge Data Structure
halfedgeデータ構造とは、一つの辺を異なる向きの2つの有向辺(directed edges)からなると考え、メッシュ構造をこれらの有向辺の集合として表すデータ構造のことです。
メッシュデータの基本構成要素
メッシュの基本構成要素である頂点(Vertex)・半辺(Halfedge)・辺(Edge)・面(Face)はOpenMesh/Core/Mesh/ArrayItems.hh
で定義されています。これらの基本要素を指定する操作は、インデックスを使うのではなく、Handleクラス(後述)を使って抽象化されています。
各構成要素を表すクラスは次のようなデータを持っています。
- Vertexクラスはその頂点から出て行く半辺のHandleをデータとして持っています。
- FaceクラスはそのFaceを構成する半辺のうちの一つのHandleをデータとして持っています。
- HalfEdgeクラスは次の要素のHandleをデータとして持っています。
- その半辺が指している頂点
- その半辺が属する面
- その半辺が属する面の次の半辺(半時計回りに定義されている)
- その半辺と同じ辺を構成する逆向きの半辺
- Edgeクラスはその辺を構成する2つのHalfedgeをデータとして持っています。
ただしユーザーはこれらクラスが持つデータには直接アクセスできず(privateデータとして定義され隠蔽されている)、必ずメッシュのメンバー関数へ次節で説明するHandleクラスオブジェクトを渡して必要なデータを得ることになります。これらのメッシュ要素へアクセスする関数はOpenMesh/Core/Mesh/ArrayKernel.hh
で定義されています。
メッシュデータへのアクセス方法
Handles
メッシュを通して頂点(Vertex)・半辺(Halfedge)・辺(Edge)・面(Face)にアクセスするために必要なクラスです。それぞれVertexHandle
、HalfedgeHandle
、EdgeHandle
、FaceHandle
、と名付けられており、これをメッシュに渡すことで欲しい頂点・半辺・辺・面のオブジェクトへの参照を得ることができます。例えば
// メッシュの別名定義
using MyMesh = OpenMesh::PolyMesh_ArrayKernelT<>;
// メッシュ作成
MyMesh mesh;
// 頂点を扱うハンドルクラスのオブジェクトを作成
MyMesh::VertexHandle vhandle;
// 頂点をメッシュへ登録し、そのハンドルを得る
vhandle = mesh.add_vertex(MyMesh::Point(0, 0, 0));
// メッシュに登録された頂点へハンドルを使ってアクセスする
const MyMesh::Point& pt = mesh.point(vhandle);
各HandleクラスはOpenMesh/Core/Mesh/Handles.hh
で定義されているBaseHandle
を継承して実装されており、次のメンバー関数が利用可能です。
-
idx()
: インデックスを返す -
is_valid()
: インデックスが有効かどうかを判定する
Iterators
OpenMeshでは、メッシュの基本構成要素である頂点(Vertex)・半辺(Halfedge)・辺(Edge)・面(Face)をループするためのイテレータ、VertexIter
・HalfedgeIter
・EdgeIter
・FaceIter
が定義されており、メッシュオブジェクトを通して得ることができます。(これらのイテレータはOpenMesh/Core/Mesh/Iterators.hhを継承して実装されています。)
基本的な使い方はOpenMesh Documentation: Mesh Iterators and Circulators: Iteratorsで説明されています。例えば次のように使うことができます。
// メッシュの別名定義
using MyMesh = OpenMesh::PolyMesh_ArrayKernelT<>;
// メッシュ作成
MyMesh mesh;
/* ... ここでメッシュデータを読み込んだものとする ... */
for (auto itr = mesh.vertices_begin(); itr != mesh.vertices_end(); ++itr) {
// この場合itrはMyMesh::VertexIter型となる
// *演算子を使ってハンドルへの参照が得られる
const MyMesh::VertexHandle& vhandle = *itr;
/* ... 何らかの処理 ... */
}
となります。半辺・辺・面に対してはhalfedges_begin()
、halfedges_end()
、edges_begin()
、edges_end()
、faces_begin()
、faces_end()
が利用可能です。
これらのイテレータを得る関数はOpenMesh/Core/Mesh/PolyConnectivity.hhで定義されています。
Range-based for loop用の関数も用意されており、次のような使い方も可能です。
for (auto&& vhandle : mesh.vertices()) {
// vhandleはMyMesh::VertexHandleの右辺値参照
const MyMesh::Point& pt = mesh.point(vhandle);
/* ... 何らかの処理 ...*/
}
Circulator
Circulatorとは、あるメッシュ構成要素に関連する要素をループする時に使うイテレータです。例えば、
-
VertexVertexIter
: ある頂点に(辺を通して)接続されている全ての頂点についてループするイテレータ -
FaceVertexIter
: ある面を構成する全てのの頂点についてループするイテレータ
などです。定義されているCirculatorについては、OpenMesh Documentation: Mesh Iterators and Circulators: Circulatorsを参照してください。
例えば全ての頂点について、その頂点に接続されている頂点の情報を使った処理をしたい場合、次のようにループすることが可能です。
// メッシュの別名定義
using MyMesh = OpenMesh::PolyMesh_ArrayKernelT<>;
// メッシュ作成
MyMesh mesh;
/* ... ここでメッシュデータを読み込んだものとする ... */
for (auto itr = mesh.vertices_begin(); itr != mesh.vertices_end(); ++itr) {
for (auto ctr = mesh.vv_iter(*itr); ctr.is_valid(); ++ctr) {
/* ... 何らかの処理 ... */
}
}
CirculatorについてもRange-based for loop用の関数が定義されており、上のループを次のように書き換えることが可能です。
for (auto&& vhandle : mesh.vertices()) {
for (auto&& cvhandle : mesh.vv_range(vhandle)) {
/* ... 何らかの処理 ... */
}
}