ActionScript3.0のPerspectiveProjectionクラス

Flash CS4から、ActionScript3.0に幾つかのクラスが追加されましたが、その中で何といっても魅力的なのは3D表現でしょう。私は、これまでFlashにおける3D表現にはPaperVision3Dを使用してきましたが、簡単な3D表現であればActionScript3.0のクラスだけで実現できることになります。今回は、ActionScript3.0のPerspectiveProjectionクラスに関する学習メモです。

PerspectiveProjectionクラスとは、DisplayObjectインスタンスに対して、遠近法に基づく投影を行うクラスです。PerspectiveProjectionクラスは、下記3つのプロパティを持っています。

fieldOfView
遠近法の視野角を0より大きく180より小さいNumber型の数値で指定します。値が小さいとオブジェクトの遠近感が弱くなり、大きいと遠近感が強くなります。値が180に近づくほど歪みが強くなり、魚眼レンズのような効果になります。この値は、下記focalLengthプロパティの値と連動しています。
focalLength
遠近法の焦点距離をNumber型の数値で指定します。この値は、上記fieldOfViewプロパティの値と連動して動的に計算され設定されます。逆にfocalLengthの値を指定するとfieldOfViewプロパティの値も動的に計算され設定されます。計算式は、
focalLength = stageWidth / 2 * (cos(fieldOfView / 2) / sin(fieldOfView / 2)
projectionCenter
遠近法の消失点(vanishing point)をステージ左上隅を基準とした座標(Pointインスタンス)で指定します。DisplayObjectインスタンスのz座標を大きくするほど、この座標に近づいていきます。

PerspectiveProjectionを使ったサンプルを作ってみました。NumericStepperでroot.transform.perspectiveProjectionのプロパティを変更できます。十字のマークの中心が消失点の座標で、ドラッグして変更することもできます。左の赤いSpriteはz座標を0に中央の緑と右の青のSpriteはz座標を100に指定しています。また、中央の緑のSpriteは、独自のPerspectiveProjectionインスタンスを生成しているので、rootのperspectiveProjectionの影響を受けないことが確認できます。

以下がソースになります。target_spは消失点を示すSprite、fov_nsはfieldOfView用のNumericStepper、fl_nsはfocalLength用のNumericStepper、pcx_nsはprojectionCenter.x用のNumericStepper、pcy_nsはprojectionCenter.y用のNumericStepperになります。

ActionScript3.0

package {
  import flash.display.Sprite;
  import flash.display.Graphics;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.geom.Rectangle;
  import flash.geom.Point;
  import flash.geom.PerspectiveProjection;
  public class PerspectiveProjectionTest extends Sprite {
    private var a_sp:Sprite;
    private var b_sp:Sprite;
    private var c_sp:Sprite;
    public function PerspectiveProjectionTest() {
      a_sp = new Sprite();
      var g:Graphics = a_sp.graphics;
      g.beginFill(0xFF0000, 1);
      g.drawRect(-50, -50, 100, 100);
      g.endFill();
      a_sp.x = stage.stageWidth * 0.2;
      a_sp.y = stage.stageHeight * 0.5;
      addChildAt(a_sp, 0);
      b_sp = new Sprite();
      g = b_sp.graphics;
      g.beginFill(0x00FF00, 1);
      g.drawRect(-50, -50, 100, 100);
      g.endFill();
      b_sp.x = stage.stageWidth * 0.5;
      b_sp.y = stage.stageHeight * 0.5;
      b_sp.z = 100;
      addChildAt(b_sp, 0);
      c_sp = new Sprite();
      g = c_sp.graphics;
      g.beginFill(0x0000FF, 1);
      g.drawRect(-50, -50, 100, 100);
      g.endFill();
      c_sp.x = stage.stageWidth * 0.8;
      c_sp.y = stage.stageHeight * 0.5;
      c_sp.z = 100;
      addChildAt(c_sp, 0);
      var _pp:PerspectiveProjection = root.transform.perspectiveProjection;
      fov_ns.value = _pp.fieldOfView;
      fl_ns.value = _pp.focalLength;
      pcx_ns.value = _pp.projectionCenter.x;
      pcy_ns.value = _pp.projectionCenter.y;
      addEventListener(Event.ENTER_FRAME, enterFrameHandler);
      fov_ns.addEventListener(Event.CHANGE, changeHandler);
      fl_ns.addEventListener(Event.CHANGE, changeHandler);
      pcx_ns.addEventListener(Event.CHANGE, changeHandler);
      pcy_ns.addEventListener(Event.CHANGE, changeHandler);
      target_sp.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      target_sp.mouseChildren = false;
      target_sp.buttonMode = true;
      b_sp.transform.perspectiveProjection = new PerspectiveProjection();
      b_sp.transform.perspectiveProjection.projectionCenter = new Point(b_sp.x, b_sp.y);
    }
    private function changeHandler(e:Event):void {
      var _ns:NumericStepper = NumericStepper(e.target);
      var _pp:PerspectiveProjection = root.transform.perspectiveProjection;
      var _point:Point;
      switch(_ns) {
        case fov_ns :
          _pp.fieldOfView = _ns.value;
          fl_ns.value = _pp.focalLength;
          break;
        case fl_ns :
          _pp.focalLength = _ns.value;
          fov_ns.value = _pp.fieldOfView;
          break;
        case pcx_ns :
          _point = new Point(_ns.value, _pp.projectionCenter.y);
          _pp.projectionCenter = _point;
          target_sp.x = _point.x;
          target_sp.y = _point.y;
          break;
        case pcy_ns :
          _point = new Point(_pp.projectionCenter.x, _ns.value);
          _pp.projectionCenter = _point;
          target_sp.x = _point.x;
          target_sp.y = _point.y;
          break;
      }
    }
    private function mouseDownHandler(e:MouseEvent):void {
      target_sp.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
      target_sp.startDrag(false, new Rectangle(0, 0, 600, 376));
      addEventListener(Event.ENTER_FRAME, mouseMoveHandler);
      stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    }
    private function mouseUpHandler(e:MouseEvent):void {
      removeEventListener(Event.ENTER_FRAME, mouseMoveHandler);
      stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
      target_sp.stopDrag();
      target_sp.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
    }
    private function mouseMoveHandler(e:Event):void {
      var _point:Point = new Point(target_sp.x, target_sp.y);
      pcx_ns.value = _point.x;
      pcy_ns.value = _point.y;
      var _pp:PerspectiveProjection = root.transform.perspectiveProjection;
      _pp.projectionCenter = _point;
    }
    private function enterFrameHandler(e:Event):void {
      a_sp.rotationY += 2;
      b_sp.rotationX += 2;
      c_sp.rotationX += 2;
    }
  }
}

感想としては、簡単な3D表現であれば十分にPerspectiveProjectionクラスで表現できると感じました。より高度な表現には、Matrix3Dクラスなどの理解が必須だと思うので、少しづつ勉強していきたいと思います。