Imaginary Code

from kougaku-navi.net

Processingでキー入力によるカメラ操作をする

3Dのプログラムを作っていると視点操作が欲しくなります。すぐにパッと使えて改造もしやすいカメラ操作用のクラスを作りました。キー操作で上下左右の首振りと、前後上下左右の移動ができます。
 
www.youtube.com
 
以下をProcessingにそのままコピペすれば動きます。

CameraControl control; // カメラ操作

void setup() {
  size(800, 600, P3D);
  control = new CameraControl(this); // setup()の中でnewするだけ
}

void draw() {
  background(100);

  // 箱と床
  perspective();
  translate( width/2, height/2, 0 );
  box(100);
  for (int x=-500; x<=500; x+=50) {
    line( x, 100, -500, x, 100, 500 );
  }
  for (int z=-500; z<=500; z+=50) {
    line( -500, 100, z, 500, 100, z );
  }

  // 操作説明の表示
  pushMatrix();
  ortho();
  resetMatrix();
  translate(-width/2.0, -height/2.0);
  hint(DISABLE_DEPTH_TEST);
  text("[UP],[DOWN] : Tilt up/down", 10, 20);
  text("[LEFT],[RIGHT] : Pan left/right", 10, 35);
  text("[w],[s] : Move forward/backward", 10, 50);
  text("[a],[d] : Move left/right", 10, 65 );
  text("[e],[c] : Move up/down", 10, 80 );
  hint(ENABLE_DEPTH_TEST);
  popMatrix();
}

// --------------------------------------------------------------
// ここからカメラ操作用のクラス(別なpdeファイルに分けると良いです)
public class CameraControl {
  final float MOVE_SPEED = 8; // 移動スピード
  final float ROTATION_SPEED = 0.02; // 首振りのスピード
  
  PApplet parent;

  CameraControl(PApplet parent) {
    this.parent = parent;
    try {
      parent.registerMethod("dispose", this);
      parent.registerMethod("pre", this);
    } 
    catch (Exception e) {
    }
  }

  public void dispose() {
    parent.unregisterMethod("dispose", this);
    parent.unregisterMethod("pre", this);
  }

  public void pre() {
    // もしキーイベントの自動実行がお気に召さない場合は、ここをコメントアウトしてkeyControl()メソッドを直接メインプログラムから呼んでください。
    keyControl();
  }

  public void keyControl() {
    if ( !parent.keyPressed ) return;

    // ビュー行列(camera)を入力に応じて修正するための行列
    PMatrix3D M = new PMatrix3D();

    if ( parent.key == 'w' ) {
      M.translate( 0, 0, MOVE_SPEED );
    } else if ( parent.key == 's' ) {
      M.translate( 0, 0, -MOVE_SPEED );
    } else if ( parent.key == 'a' ) {
      M.translate( MOVE_SPEED, 0, 0 );
    } else if ( parent.key == 'd' ) { 
      M.translate( -MOVE_SPEED, 0, 0 );
    } else if ( parent.key == 'e' ) { 
      M.translate( 0, MOVE_SPEED, 0 );
    } else if ( parent.key == 'c' ) { 
      M.translate( 0, -MOVE_SPEED, 0 );
    } else if ( parent.key == PConstants.CODED ) {
      if ( parent.keyCode == PConstants.UP ) {     
        M.rotateX(ROTATION_SPEED);
      } else if ( parent.keyCode == PConstants.DOWN ) {  
        M.rotateX(-ROTATION_SPEED);
      } else if ( parent.keyCode == PConstants.RIGHT ) { 
        M.rotateY(ROTATION_SPEED);
      } else if ( parent.keyCode == PConstants.LEFT ) {  
        M.rotateY(-ROTATION_SPEED);
      }
    }

    // ビュー行列の修正
    PMatrix3D C = ((PGraphicsOpenGL)(this.parent.g)).camera.get(); // コピー
    C.preApply(M);

    // 上を向くように修正
    C.invert();
    float ex = C.m03;
    float ey = C.m13;
    float ez = C.m23;
    float cx = -C.m02 + ex;
    float cy = -C.m12 + ey;
    float cz = -C.m22 + ez;
    parent.camera( ex, ey, ez, cx, cy, cz, 0, 1, 0 );
  }
}

キー操作は以下の通りです。
← →:左右の首振り
↑ ↓:上下の首振り
W:前進
S:後進
A:左移動
D:右移動
E:上移動
C:下移動
 
実装上の工夫として、カメラの現在位置や回転角度などの情報を個別に変数で持たせることをせずに、移動量や回転量に基づいてビュー行列を直接編集するようなやりかたにしました。これにより、どこかのタイミングでcamera()関数によって視点の位置を任意に変更されても、そこからの移動・回転ができます。回転はパン・チルトに限定し、常に天井方向が上になるようにしました。ロール軸回転もあると移動操作がややこしくなるからです。
 
あと、キー操作にまつわる処理は自動的に行われる仕様にしました。これにより、CameraControlクラスの変数を定義してsetup()関数内でnewするだけでキー入力によるカメラ操作ができます。もし自動実行にしたくない場合は、CameraControlクラス内のpre()メソッドの中身をコメントアウトし、メインプログラムの中からkeyControl()メソッドを直接呼ぶようにしてください。
 
ちなみにgithubでも公開しています。
github.com