石像備忘録

モアイ像がいろんなメモを書き残す場

Qhullを用いて三次元凸包を取得する

こんにちは、しもむ~です。
初記事です。

ある作業中に3Dモデルの三次元凸包モデル出力をする必要があったので
Qhullを用いて三次元凸包を取得する方法をメモします

今回使用した言語はC++
開発環境はVisual Studio 2017になります。

そもそも凸包とは

wikipediaによると

数学における凸包(とつほう、英: convex hull)または凸包絡(とつほうらく、英: convex envelope)は、与えられた集合を含む最小の凸集合である。例えば X がユークリッド平面内の有界な点集合のとき、その凸包は直観的には X をゴム膜で包んだときにゴム膜が作る図形として視認することができる。

とのことで、2次元の凸包だと

f:id:simomuuu:20171026072955p:plain:w300

こんな感じになります。 で、三次元の凸包はどういう状態かというと

f:id:simomuuu:20171026073043j:plain:w300

これが

f:id:simomuuu:20171026073045j:plain:w300

こんな感じになります。
UnityにおけるMesh ColliderのConvex hullみたいなところで使われたりします。

Qhullについて

Qhullとは凸包やドローネ三角形分割、ボロノイ図を作成するための
ソフトウェアです。ソースコードが公開されており、
他のライブラリやソフトでもライブラリとして使用されています。
(使用例:PCL,scipy,MatLab,MeshLabなど)

Qhullの導入

Qhullのソリューションファイルを生成

まず
http://www.qhull.org/download/
からソースコードを落としてきて、解凍します。

解凍後はqhull-2015.2/build以下の物を削除します。

Cmakeを起動し、

  • souce code欄にqhull-2015.2
  • build the binaries欄にqhull-2015.2/build

を指定しContifireを押してターゲットのVisual Studioを選択し実行します。

実行後はAdd Entryで以下の物を追加します。

項目 内容
Name: CMAKE_DEBUG_POSTFIX
Value: STRING
column column
Value: _d
Description

DEBUGビルドのときに出力ファイルに_dを追加する

追加後にGenerateを押してソリューションファイルを生成します。

Qhullをビルド&インストール

Visual Studioを起動し生成したソリューションファイル(qhull2015.2/build/qhull.sin)を開きます。

  • ALL_BUILD->ビルド

でQhullをビルドします。

  • INSTALL->プロジェクトのみ->INSTALLのみをビルド

でQhullをインストールします。

ここまででqhullのインストールは完了です

QhullをVisual Studioで使用可能にする

Qhullを使用したいプロジェクトのプロパティを開きます。

構成プロパティ->C/C++->全般->追加のインクルードディレクトリ に
C:\Program Files\qhull\include
を追加

構成プロパティ->リンカー->全般->追加のライブラリディレクトリ に
C:\Program Files\qhull\lib
を追加

構成プロパティ->リンカー->入力->追加の依存ファイル に
qhullcpp.lib
qhullstatic_r.lib
を追加


qhullstatic.libを使用した場合実行時に
QH6248 qh_lib_check: Incorrect qhull library called. Caller uses reentrant Qhull while library is non-reentrant
QH6249 qh_lib_check: Incorrect qhull library called. Size of qhT for caller is 6632, but for library is 2432.
QH6256 qh_lib_check: Cannot continue. Library 'qhull 7.2.0 (2015.2 2016/01/18)' uses a static qhT (e.g., libqhull.so)

とエラーが生じることがあります。
qhullstatic_r.libを使用することでこれが回避できます。


ここまででQhullの導入は完了です。

実際にQhullを使ってみる

実際にC++からQhullを使用するサンプルが以下のようになります。
なお、Qhullの実行結果から面の法線方向も取得できるようですが、
今回は頂点情報のみを取得します。

//一部抜粋 全ソースコードは記事の最後に

#include <libqhullcpp\Qhull.h>
#include <libqhullcpp\PointCoordinates.h>
#include <libqhullcpp\QhullFacet.h>
#include <libqhullcpp\QhullFacetList.h>
#include <libqhullcpp\QhullVertexSet.h>

void ConvexHull::execute() {

    std::cout << "Start Convex hull" << std::endl;

    orgQhull::Qhull qhull;
    orgQhull::PointCoordinates points;
    //3次元を扱う
    points.setDimension(3);
    
    //qhullに渡す頂点情報を作成
    //配列の中身は{x0, y0, z0, x1, y1, z1, x2, y2, ...}となる
    //QhullではcoorT型を使うが、実質double
    std::vector<coordT> allpoint;
    for (auto v : m_input_vertexes) {
        allpoint.push_back(v);
    }
    points.append( allpoint);
    //qhullの実行
    qhull.runQhull(points.comment().c_str(), points.dimension(), points.count(), points.coordinates(), "Qt");

    //qhullの結果から面を取得
    orgQhull::QhullFacetList faceList = qhull.facetList();
    for (auto itr = faceList.begin(); itr != faceList.end(); itr++) {

        //面から頂点情報を取得
        orgQhull::QhullVertexSet vset = (*itr).vertices();
        for (auto vitr = vset.begin(); vitr != vset.end(); vitr++) {
            orgQhull::QhullPoint p = (*vitr).point();
            double * coords = p.coordinates();
            m_convex_vertexes.push_back(coords[0]);
            m_convex_vertexes.push_back(coords[1]);
            m_convex_vertexes.push_back(coords[2]);
        }

    }

    std::cout << "End convex hull" << std::endl;
}

また、Qhullの処理時に発生する例外はQhullErrorで拾うことができます。

ConvexHull convex;
convex.setVertexes(stl.vertexes());
try {
    convex.execute();
}
catch(orgQhull::QhullError &e){
    std::cerr << e.errorCode() << std::endl;
    std::cerr << e.what() << std::endl;
}

Qhullに渡した頂点情報に不備がある等の処理失敗時に例外が発生します。

今回のサンプルの全ソースコード
stl(バイナリ)形式の3dモデルを読み込み、三次元凸包化して出力します。