2011/06/15

Processingのカメラ行列にアクセスしたい

【何の役に立つか分からない技術コーナー】

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 件のコメント:

コメントを投稿

ひとことどうぞφ(・ω・,,)