OpenGL glu ライブラリのテセレータ(gluTess*)の使い方

アウトラインフォントの描画など、輪郭形状のポリゴン化にはOpenGL の glu ライブラリに含まれている gluTess* の関数群を使うのが便利です。ところが、コールバックを多用するなど仕組みがちょっとややこしいい上に、日本語の解説はほぼ皆無という状況なので、以前使った時はそこそこ苦労した覚えがありました。

一歩、二歩、散歩。様のサイトで C# からの gluTess* を呼び出す方法を模索してはりましたので、せっかくなので当時を思い出しつつ、まずは C/C++ での普通の使い方について書いてみることにしました。

公式の解説

OpenGL Programming Guide の "Chapter 11 Tessellators and Quadrics" に掲載されています。古い OpenGL 1.1 対応のものでよければ pdf が公開されています。*1
アルゴリズムの詳細を知るにはOpenGL Sample Implementation のソースがSGI Free Software License Bというライセンスの下で公開されています。
特に、libtess ディレクトリにあるREADME ファイルには、gluTess* の関数を呼び出す際に指定する、各種設定値の詳しい説明もあり、使う側にとってとても参考にないます。

使い方の流れ

typedef void (__stdcall *tessCallbackFunc)();

// テセレータオブジェクトの生成
GLUtesselator* pTess = ::gluNewTess();

// コールバック関数の設定
::gluTessCallback(pTess, GLU_TESS_BEGIN_DATA, (tessCallbackFunc)callbackTessBeginPrimitive);
::gluTessCallback(pTess, GLU_TESS_VERTEX_DATA, (tessCallbackFunc)callbackTessVertex);
// ::gluTessCallback(pTess, GLU_TESS_EDGE_FLAG_DATA, (tessCallbackFunc)callbackTessEdgeFlag); // 今回は未使用
::gluTessCallback(pTess, GLU_TESS_END_DATA, (tessCallbackFunc)callbackTessEndPrimitive);
::gluTessCallback(pTess, GLU_TESS_ERROR_DATA, (tessCallbackFunc)callbackTessError);
::gluTessCallback(pTess, GLU_TESS_COMBINE_DATA, (tessCallbackFunc)callbackTessCombine);

// ワインディングルール(輪郭の内側を定めるルール)の設定。
// (OpenGL Programming Guide の図を見れば一目瞭然)
::gluTessProperty(pTess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);

// 近い位置にある複数の頂点を一つにマージする範囲。
// 入力される頂点において、最大の距離にこの値をかけた距離がマージの範囲となる。
::gluTessProperty(pTess, GLU_TESS_TOLERANCE, 0.01f);

// 輪郭を含む平面の法線ベクトルをセット。
// 頂点座標は3次元のため、これをセットしておけばテセレータが自前で行う法線計算をはしょることができる。
::gluTessNormal(pTess, 0.0f, 0.0f, 1.0f);

// ここから全輪郭のセット開始。
// 2 つ目の引数は、コールバック関数の pDataUser にそのまま渡されるので、自由に使ってよい。
// (ポリゴンメッシュを表す自作オブジェクトへのポインタ等を渡すとか)
::gluTessBeginPolygon(pTess, pBuilderPrimitive);
// テセレータに対し、全輪郭の全頂点をセット
for (unsigned int i = 0; i < numContours; i ++) {
	// 1 つの輪郭内
	::gluTessBeginContour(pTess);
	for (unsigned int j = 0; j < numPoints; j ++) {
		// 配列に格納した頂点へのポインタをテセレータに渡す
		// 最後の引数は、コールバック関数 callbackTessVertex の第1引数に渡されるので、自由に使ってよい。
		// (頂点を表す自作オブジェクトへのポインタ等を渡すとか)
		::gluTessVertex(pTess, pVertexOutline, pVertexOutline);
	}
	::gluTessEndContour(pTess);
}

// 全輪郭のセット終了。
// これを呼ぶと分割開始、コールバック関数が怒涛のごとく呼ばれる
::gluTessEndPolygon(pTess); 

// テセレータオブジェクトの削除
::gluDeleteTess(pTess);

コールバック関数

gluTessEndPolygon() が呼ばれると、テセレーションが始まりコールバック関数が怒涛のごとく呼ばれます。呼び出しの流れは OpenGL の描画で見慣れた glBegin()〜glVertex3f()〜glEnd() に沿った形となっており、コールバック関数の中でそのまま上記 API を呼び出し、描画やディスプレイリストを作成を行うこともできます。(OpenGL Programming Guide のサンプルではコールバック関数の中で実際に描画する例が書かれています)
ただ、これでは最初に一度だけで済むテセレーションを描画のたびに毎回行うことになって無駄です。gluTessBeginPolygon() や gluTessVertex() は自作オブジェクトへのポインタをユーザーデータとして渡すことができますので、これを使うと分割結果を自作オブジェクトの中に保持する、なんていうこともできます。

コールバック関数の宣言は以下のようになっています。

// 描画プリミティブが始まる時に呼ばれる。(glBegin() に相当)
static void __stdcall callbackTessBeginPrimitive(GLenum type, void* pDataUser);

// 描画プリミティブ内で頂点をセットする時に呼ばれる。(glVertex() に相当)
// pVertex には gluTessVertex() の最後の引数に渡したポインタがそのまま渡される
static void __stdcall callbackTessVertex(void* pVertex, void* pDataUser);

// 描画プリミティブの終わりで呼ばれる。(glEnd() に相当)
static void __stdcall callbackTessEndPrimitive(void* pDataUser);

// この後に続く頂点がエッジ上かどうかのフラグを教えてくれる
// このコールバックをセットしていると、STRIP, FAN は呼ばれないので、今回は使用せず
static void __stdcall callbackTessEdgeFlag(GLboolean flag, void* pDataUser);

// テセレーション処理内でエラーが発生した時に呼ばれる。
static void __stdcall callbackTessError(GLenum type, void *pDataUser);

// 輪郭が互いに交差するような形状だった時、交点を新たな頂点として追加する必要が起きた時に呼ばれる。
static void __stdcall callbackTessCombine(GLdouble coords[3], void *data[4], GLfloat weight[4], void** pDataOut, void* pDataUser);

*1:google:OpenGL redbook.pdfgoogle 検索してみて下さい。(11/2 18:50追記)ちょっと補足しますが、この PDF ファイルは以前 www.opengl.org のサイトからダウンロードできていたように記憶していますが、今現在 www.opengl.org に接続できないので確認できません…。orz