はじめに †このページではFK Tipsについて掲載しています。新バージョンについて色々追記予定でいます。 現在掲載している他にも注意しておきたい点がありましたらどんどん編集していってください。 FKの便利な使い方 †FKの便利な活用方法を紹介します。 座標系あれこれ †fk_Modelの親子関係は便利で強力な機能だが、扱う座標や姿勢の座標系を勘違いしていると思いもよらない挙動を引き起こす。ここでは混乱しがちな座標系を整理し、ありがちなミスの傾向と対策を述べる。 一番の罠は、グローバル座標系とローカル座標系という2つの考え方で理解しようとしてしまうことにある。fk_Modelにおける親子関係とは「子モデルにとってのグローバル座標系を、親モデルのローカル座標系に置き換える」という処理によって実現していることに注意しよう。 これに気がつくと、子モデルでglFocus()などのgl系関数を使う際に渡す引数は「その子モデルにとってのグローバル座標系上の値」でなければならないことが分かる。つまり「親モデルのローカル座標系上の値」を渡さないと意図通りの動作をしないわけだ。 このまま説明を続けると混乱するだろうから、ここでFKにおける座標系を以下の3種に分類することを提案したい。これらの区別ができれば理解は進むはずである。
いかがだろうか。ワールドとグローバルという概念を分離することで、fk_Modelで扱っているグローバル座標系は「1つ上の階層の座標系」という相対的なものを表していると認識できるだろう。親を持たないモデルは全てワールド座標系を親として持っている、という解釈もできる。 さて、そうなると親を持つモデルでgl系関数を使う場合、ワールド座標系の値をそのまま持ってくることは出来ないため、以下のようなコーディングが必要になる。glFocus()で任意のワールド座標の地点にモデルを向けたい場合の例を挙げる。 fk_Vector worldPos(x, y, z); fk_Vector parentPos; fk_Model *parent = childModel.getParent(); if(parent != NULL) { parentPos = parent->getInhInvMatrix()*worldPos; } else { parentPos = worldPos; } childModel.glFocus(parentPos); getParent()で親モデルのポインタを取得し、getInhInvMatrix()でワールド座標から親モデルのローカル座標に変換する行列を得てワールド座標と乗算すれば、意図通りの結果が得られる。親子関係が外れている場合はポインタとしてNULLが帰ってくるため、その場合はワールド座標をそのまま渡してしまえばよい。 また、もっとシンプルな解決手段として「いったん親子関係を切り、glFocus()などの操作をしてから親子関係を再構築する」というアプローチもある。開発者のearth氏としてはこちらを推奨している(らしい)。 fk_Vector worldPos(x, y, z); fk_Model *parent = childModel.getParent(); childModel.deleteParent(true); childModel.glFocus(worldPos); if(parent != NULL) { childModel.setParent(parent, true); } setParent()とdeleteParent()は引数にtrueを渡すことで、親子関係のON/OFF後でもワールド座標系上での位置と姿勢が変化しないように補正する処理を行ってくれる。内部的にはgetInhInvMatrix()などを用いているので、同様の結果が得られる。 この一連の処理はFKの機能として取り込んでしまってもいいように思うので、今後検討していきたい。 テクスチャ関連 †テクスチャのピクセル色をそのまま表示する方法 †光源やマテリアルの影響を受けずにテクスチャのピクセル色をそのまま表示するには以下のように記述する。 fk_Texture texture;//テクスチャを表すクラス texture.setTextureMode(FK_TEX_REPLACE);//画像モード ただし、この場合はマテリアルの透明度も反映されなくなるので半透明表示もできなくなる。光源には影響されたくないが透明度だけ有効にしたい場合は上記の設定は行わず、以下のようなマテリアルを作成して用いるとよい。 fk_Material pixMat; pixMat.setAmbDiff(0.0, 0.0, 0.0); pixMat.setSpecular(0.0, 0.0, 0.0); pixMat.setEmission(1.0, 1.0, 1.0); マテリアル関連 †色などの材質に関するTipsです。 マテリアルの初期化 †FKでマテリアルを扱うためには、マテリアルの初期化を行う必要がある。 初期化は1度だけで十分なので、Fl_Windowのend関数の直後あたりで行えばよい。 // マテリアルの初期化 fk_Material::initDefault() 初期化を行わないと、色を指定しても正しい色にならないので注意すること。 FKにおける材質要素 †fk_Materialが持つ要素は以下の6つである。 ごく簡単な説明と共に紹介する。
一時的な材質変更 †既にsetMaterial()を行ったモデルに対して一時的に色や透明度を変更したい場合は、わざわざfk_Materialのオブジェクトを用意する必要はない。例えば透明度を変更したい場合は、以下のようなコードで実現できる。 hogeModel.getMaterial()->setAlpha(0.5); 一度もsetMaterial()を行っていない場合は正しく動作しないので注意すること。同じようなコードで、現在設定されている色の値も得ることができる。 逆に、setMaterial()で指定したマテリアルの色設定を後から変更するだけでは、既にセット済みのモデルには反映されない。何故ならマテリアルの設定は個々のモデルに値渡しされているためである。FKは基本的には参照渡しの関係が多いが、マテリアルに関しては例外であるため、気を付けて欲しい。 線と点の色 †物質の色はfk_ModelのsetMaterial関数によって設定するが、線と点の色はそれぞれ別の関数によって設定する。 // 物質の材質設定 fk_Model model; fk_Block block; model.setShape(&block); model.setMaterial(White); // 線の色設定 fk_Line line; model.setShape(&line); model.setLineColor(0.0f, 0.0f, 1.0f); // 点の色設定 fk_Point point; model.setShape(&point); model.setPointColor(1.0f, 0.0f, 0.0f); 線や点の色設定を行わなかった場合に線や点を描画する場合は、設定されているマテリアルの環境反射係数の色が使われる。 fk_Solid関連 †半稜線構造や、fk_Solid関係の使い方に関するTipsです。 位相要素のID †fk_Solidの位相要素(fk_Vertex, fk_Half, fk_Edge, fk_Loop)のIDは1から始まる。 fk_Loopは5角形以上の多角形に対応 †fk_Loopは5角形以上の多角形を生成できます。 生成方法に違いはありません。 3DCG一般 †デプスバッファの値から3次元座標系上での距離を逆算 †一般に,デプスバッファの値は,下記の一般式 d = (a Z + b) / Z ---(1) で計算されます(ただし Z は光軸方向の距離(Near, Far を測る向き), a, b は定数,d はデプスバッファ値). #値 Z を直接デプスバッファに格納せず,このような非線型な演算を #行うのは,ポリゴン描画の高速化のためです.つまり,ポリゴンの #頂点 (X, Y, Z) を投影し,スクリーン座標 (xs, ys) とそのデプス #値 d を計算すれば,ポリゴンの内部は xs, ys の座標の上で d を #線形補間することにより塗りつぶせます. #グーローシェーディングと同じようなハードを使うわけですね. 上の式 (1) で,Z = Far の時に d = 1, Z = Near の時に d = 0 と なるようにすると, a = Far / (Far - Near) b = -(Far * Near) / (Far - Near) となります.また,この a, b を 65536 倍すれば, 16bit の デプスバッファ値が直接計算できることになります. この式を逆算して, Z = b / (d - a) とすれば,デプスバッファ値からZ値が直接計算できるはずです. FLTK関連 †GUIを作るためのFLTK関連のTipsです。 FLTKで日本語を扱う †FLTKはVer.1.3.0以降でUTF-8に対応し、日本語文字列を正式に扱えるようになりました。 しかし、Visual Studioの場合は文字列定数のエンコードが強制的にシフトJISになるため、エンコードを変換する必要があります。 この問題への対処として、fk_Code::utf8()関数を用意しました。 FLTK で作成した GUI に日本語文字列を渡す際には、次のように記述してください。 // 外側のウィンドウのタイトルバーに日本語を使う場合 Fl_Window mainWindow(512, 512, fk_Code::utf8("日本語", FK_STR_SJIS)); // 第 2 引数で変換前の文字コードを指定しますが、 // fk_Code::setInputCoding()で事前に設定すれば省略できます。 fk_Code::setInputCoding(FK_STR_SJIS); // ダイアログに日本語文字列を出力したい場合 fl_alert(fk_Code::utf8("日本語文字列")); ソースファイルの冒頭で using namespace fk_Code; と宣言すれば、 fk_Code:: も省略できます。適宜利用すると良いでしょう。 GUI上でのフォント変更 †FLTKで作成したGUI上で表示する文字列は、次のような処理で変更することができます。 Fl::set_font(FL_HELVETICA, "フォント名"); // 例 Fl::set_font(FL_HELVETICA, "メイリオ"); // Visual Studio環境の場合は文字コードの変換が必要 Fl::set_font(FL_HELVETICA, utf8("メイリオ")); 指定する場所はmain()関数内のなるべく早い位置(マテリアルを初期化するあたり)がお勧めです。 FL_HELVETICAとは、FLTKがデフォルトで利用するフォントを指す定数値です。 set_font()は指定した定数値が実際に利用するフォントを置き換える働きをします。 Windowsの場合、フォント名はコントロールパネルのフォント一覧で表示されるものを指定します。 Vista以降なら"メイリオ"、XP以前なら"MS ゴシック"(MSは全角で、その後ろは半角スペース)を指定すれば、フォントが見つからずに異常が発生することはないでしょう。 以下にデバッグ出力やアプリ構築に便利なダイアログを紹介します。これらは全てVisual Studio環境では変換処理を噛ませないと悲しいこと(文字化け祭り)になるので、注意しましょう。 何かしらのメッセージを表示してプログラムの進行を一時停止させたい †fl_alert()かfl_message()を使うのがおすすめです。メッセージ文字列の引数はprintf()と同じ形式で指定するので、変数の内容を確認するのにも便利です。alertとmessageの違いは、表示時に鳴る音とアイコンが変わるだけで、動作的にはほぼ一緒です。 fl_alert("やばい値になっちまったぞ!:%d", value); fl_message("ちゃんと処理できたよ、お兄ちゃん!"); 選択肢を選ばせたい †YesかNoか、でいいならfl_ask()を使いましょう。引数にはalertやmessageと同じ形式でメッセージ文字列を指定します。呼び出されるとダイアログを表示して処理を停止し、Yesが選ばれたら1を、Noが選ばれたら0を返します。EnterがYes、ESCがNoのショートカットになります。 int selection = fl_ask("Are you ready?"); if(selection == 1) { fl_message("Good!"); } else { fl_alert("Hurry!Hurry!Hurry!!!!!1"); } 3択にしたいならfl_choice()を使います。第1引数にメッセージ、第2〜4引数に選択肢のボタンキャプションを右のボタンから順番に指定します。返値も右から順に0,1,2となります。FLTK的には真ん中のボタン(返値1)がOK的な選択肢を想定しているようで、ENTERがショートカットになります。ESCは一番右(返値0)です。 int selection = fl_ask("YesかNoか半分か?", "半分", "Yes", "No"); if(selection == 0) { fl_message("そりゃ残念"); } else if(selection == 1) { fl_message("ふーん"); } else { fl_message("半端ものめ"); } キーボードから何か入力させたい †fl_input()が便利です。第1引数にメッセージ、第2引数にダイアログに最初から入力済みにしておきたい文字列を指定します。返値はconst char *で、入力をキャンセルした場合はNULLが返ってきます。 string recvStr; const char *inputStr = fl_input("ギブミーメッセージ"); if(inputStr != NULL) { recvStr = inputStr; } else { fl_message("メッセージ、くれなかったね。。。ううん、いいの。ごめんね。"); } 返値の文字列ポインタは、次にfl_input()を呼ばれるときまでしか有効でないので、受け取ったらすぐにstring型の変数にコピーしたり、数値入力処理だったらatoi()やatof()で変換するなどして、長い期間保持しないようにしてください。 Mac関連 †fk_Window::getKeyStatus()について †Mac環境では、getKeyStatus()で取得したいキーを大文字で指定する必要があります。 // NG(WindowsではOK) window.getKeyStatus('a'); // OK window.getKeyStatus('A'); 地味なトラップなので気をつけましょう。 ファイル参照時のカレントディレクトリについて †fk_System::setcwd()を呼ぶことで実行ファイルのあるパスにカレントを移動できますが、Mac環境でappファイルをダブルクリックして実行した場合は正常に動作しません(2.8.10.1現在)。ファイルを参照するプログラムを動作させたい場合は、次のコードに示す関数をmain()冒頭でコールすると良いでしょう。 #include <CoreFoundation/CoreFoundation.h> bool setcwd_bundle() { CFURLRef appUrlRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); CFStringRef macPath = CFURLCopyFileSystemPath(appUrlRef, kCFURLPOSIXPathStyle); char appPath[256]; CFStringGetCString(macPath, appPath, 256, kCFStringEncodingMacJapanese); if(appPath == NULL) return false; chdir(appPath); chdir(".."); return true; } 次回のバージョンアップでこのコードが反映される予定ですが、Mac上での開発者には有益な情報かと思いますので、記録しておきます。 |