Processing で 3D を扱っていて、地味に不便だなぁと感じるのが、カメラ行列(射影行列ではない)に直接アクセスできない事。
printCamera() メソッドでカメラ行列をコンソールに表示する事は可能だけど、これを眺めるだけでカメラの並進量や回転量を把握するのは、残念ながら普通の人には難しい。
加えて、そのカメラ行列をプログラム内で使う事は(現時点では)できないようである。
これができないと、たとえばデバッグカメラ(カメラ配置を第三者視点から確認する事)が多少やりづらい(工夫次第でなんとかなりそうだが)。
そこでひとまず、Processing 標準の camera() メソッドをエミュレートするものを作ってしまう事にした。
以下に当該メソッドのソースコードを示す。使い方は camera() メソッドと基本的に一緒。
// 世界座標を、カメラに合わせて動かす // 引数の意味は、camera()メソッドと同じ void worldToCamera() { worldToCamera( width/2.0, height/2.0, (height/2.0) / tan(PI*60.0 / 360.0), width/2.0, height/2.0, 0, 0, 1, 0); } void worldToCamera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { // カメラ座標空間の3軸 PVector r3 = new PVector(eyeX-centerX, eyeY-centerY, eyeZ-centerZ); PVector r2 = new PVector(upX, upY, upZ); PVector r1 = r2.cross(r3); r2 = r3.cross(r1); // 正規化 r1.normalize(); r2.normalize(); r3.normalize(); /* ================================================== */ /* 各軸まわりの回転量、および並進量を個別に求める例 */ /* オイラー角表現はヘディング・ピッチ・バンクに準ずる */ /* ================================================== */ float h, p, b; p = asin(-r2.z); if (cos(p) != 0) { h = atan2(r1.z / cos(p), r3.z / cos(p)); b = atan2(r2.x, r2.y); } else { b = 0; h = atan2(-r3.x, r1.x); } resetMatrix(); rotateY(h); // ヘディング角 rotateX(p); // ピッチ角 rotateZ(b); // バンク角 translate(-eyeX, -eyeY, -eyeZ); /* ================================================== */ // 別解:applyMatrix による一括変換の例 /* ================================================== // 並進成分の計算 PVector t = new PVector( -(eyeX * r1.x + eyeY * r1.y + eyeZ * r1.z), -(eyeX * r2.x + eyeY * r2.y + eyeZ * r2.z), -(eyeX * r3.x + eyeY * r3.y + eyeZ * r3.z) ); // 変換行列の作成・適用 resetMatrix(); applyMatrix(r1.x, r1.y, r1.z, t.x, r2.x, r2.y, r2.z, t.y, r3.x, r3.y, r3.z, t.z, 0.0, 0.0, 0.0, 1 ); ================================================== */ }
このままでは camera() と一緒だけど、たとえば MyCamera クラスとかで包んでやって、カメラの並進量・回転量(あるいは行列そのもの)に対するアクセサを定義してやれば、もうちょっと高度なカメラ制御が可能になると思う。
【テスト】
適当な視点から箱を俯瞰するサンプルプログラムを作る。ただし、カメラの設定には、既存の camera() メソッドと新たに作った worldToCamera() メソッドを用意し、どちらを使用するかをマウスで切り替えられるようにした(デフォルトでは worldToCamera() メソッドが、マウスを押しているときだけ camera() メソッドが使用される)。
ソースコードは以下の通り。
int boxSize = 90; // 箱の大きさ int nBox = 10; // 箱の数 int mergine = 50; // 余白 void setup() { size(800, 600, P3D); } void draw() { background(0); if(mousePressed) { // 既存の関数 camera(800, -500, 700, 0, 0, 0, 1, 1, 0); } else { // 新しく作った関数 worldToCamera(800, -500, 700, 0, 0, 0, 1, 1, 0); } // 色を設定 colorMode(HSB, nBox, 100, 100); // 適当に箱をならべる pushMatrix(); translate(-(nBox * (boxSize + mergine))/2, 0, 0); for(int i = 0; i < nBox; ++i) { pushMatrix(); stroke(i, 99, 99); fill(i, 99, 99, 100); translate(i * (boxSize + mergine), 0, 0); box(boxSize); popMatrix(); } popMatrix(); }
実際に走らせてみると両者に差はなく、きちんと camera() が再現されている事が判る。
これを応用すれば、既知のカメラ視点・注視点・鉛直軸より、並進・回転成分を抽出するメソッドを書く事も可能だろう。
で。
今日はちょっと忙しいのでここまで。中途半端でごめん。
【てきとう参考文献】
カメラの回転行列からオイラー角(ヘディング、ピッチ、バンク角)の回転量を取り出す箇所に関しては、Fletcher Dunn, Ian Parberry 著、松田 晃一訳『実例で学ぶ ゲーム 3D 数学』を参考にしたよ。
0 件のコメント:
コメントを投稿
ひとことどうぞφ(・ω・,,)