papervision 3D を始めてみたでござるの巻

何となく三次元にうつつを抜かしてみたり。ActionScript で動かせる 3D 系のライブラリを探していたのですが、最初にいじった away3D というライブラリはドキュメントが少なく理解がいまいち進みませんでした。
そこでちゅう君に教わったのが Papervision3D という、今回使うライブラリ。ダウンロードは下のリンクから。

http://code.google.com/p/papervision3d

困ったときの API はこちら。

http://papervision3d.googlecode.com/svn/trunk/as3/trunk/docs/index.html

ついでに、一緒に使った Tweener というきれいな動きを演出してくれるライブラリはこちら。説明が必要なほど使っていないので、紹介だけ。

http://code.google.com/p/tweener/

では本題。下がとりあえず作ってみた、クリックすると反時計回りでくるくる回るメニューです。聖剣のリングコマンドを想像してもらうといいかもしれません。
複数アイテムを配置しての位置調整、イベントのリッスンと、応用に向けての個人的なポイントをクリアできたので今日はこの辺で。

package {
  import flash.events.Event;
  import flash.events.MouseEvent;
  import mx.core.BitmapAsset;
  import caurina.transitions.*;
  import caurina.transitions.properties.*;
  import org.papervision3d.core.proto.MaterialObject3D;
  import org.papervision3d.view.BasicView;
  import org.papervision3d.materials.BitmapMaterial;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.primitives.Plane;

  public class Main extends BasicView {
    private static const NUM_ITEMS:int = 12;
    private static const RADIAN:Number = Math.PI * 2 / NUM_ITEMS;
    private static const ADJUST_RADIAN:Number = 270 * Math.PI / 180;
    private static const RADIUS:Number = 600;
    [Embed(source="assets/Arisama.png")]
    private var bm:Class;
    private var bmAsset:BitmapAsset;
    private var bmMaterial:BitmapMaterial;
    private var planeArray:Array;
    // 回転のカウンター、右回転で加算、左で減産
    private var turnCounter:int = 0;

    public function Main() {
      super(0, 0, true);
      init3D();
    }

    // 3D 関連の初期化
    private function init3D():void {
      // アイテムの表示に使うビットマップデータの初期化
      bmAsset = new bm();
      bmMaterial = new BitmapMaterial(bmAsset.bitmapData);
      bmMaterial.interactive = true; // true にしないとクリックイベントが起こらない
      bmMaterial.doubleSided = true; // true にしないと裏返ったときに見えない
      // viewport の設定
      viewport.containerSprite.buttonMode = true;
      // アイテム作成
      createRing();
      // カメラ設定
      camera.focus = 600;
      camera.zoom = 1;
      camera.y = 0;

      addEventListener(Event.ENTER_FRAME, renderHandler);
      // 個々の要素にイベントを設定しても動かない
      addEventListener(MouseEvent.CLICK, onClickHandler);
    }

    // リング状にアイテムを生成
    private function createRing():void {
      planeArray = new Array();
      for(var i:int = 0; i < NUM_ITEMS; i ++) {
        var plane:Plane = new Plane(bmMaterial, 181.5, 100);
        plane.x = Math.cos(i * RADIAN + ADJUST_RADIAN) * RADIUS;
        plane.y = 0;
        plane.z = Math.sin(i * RADIAN + ADJUST_RADIAN) * RADIUS;
        plane.rotationY = i * (-360 / NUM_ITEMS);
        scene.addChild(plane);
        planeArray.push(plane);
      }
    }

    private function renderHandler(e:Event):void {
      renderer.renderScene(scene, camera, viewport);
    }

    private function onClickHandler(e:Event):void {
      turnRight();
    }

    private function turnRight():void {
      turnCounter ++;
      // 全要素を右隣の位置へ
      for(var i:int = 0; i < NUM_ITEMS; i ++) {
        Tweener.addTween(Plane(planeArray[i]),
          {time       : 2,
           delay      : 0,
           x          : Math.cos((i + turnCounter) * RADIAN + ADJUST_RADIAN) * RADIUS,
           z          : Math.sin((i + turnCounter) * RADIAN + ADJUST_RADIAN) * RADIUS,
           rotationY  : ((i + turnCounter) * (-360 / NUM_ITEMS)),
           transition : "easeOutElastic"});
      }
    }
  }
}

ソースだけだとよくわからないと思いますが BasicView というのはライブラリに用意されている簡単に 3D 描画を行うための基本クラスみたいなもので、自分の視点(の、座標)にあたる viewport と描画されたオブジェクトを写す camera(実際はこちらが自分の目に近い)、そして描画を行う renderer を初期のプロパティとして持っています。
もちろんこれらは自分で定義する事もできるのですが(調べたところ、複数の viewport や camera を用意したサンプルもありました)、今回は必要ないのでパス。
注意点はほとんどソースの方に書いてありますが、一つ。この部分に注目してください。

    private function turnRight():void {
      turnCounter ++;

ここをなぜ

    private function turnRight():void {
      turnCounter = (turnCounter + 1) % 12;

としないかというと、それをすると回転がおかしくなってしまう(逆回転する事がある)ためです。Tweenerpapervision3D のどちらかの動作によるものだと思いますが、その都合でカウンタはひたすら増えるか、減り続けます。

ちなみにここ、最初はカウンタを使わずにこんなコードでした。

    private function turnRight():void {
      planeArray = planeArray.unshift(planeArray.pop());

何となくスマートでいいかと思ったのですが、これでも上記と同じような問題が発生します。まあ int 型があふれたりするほどクリックされても困るので、これでほぼ実害はないかと。