2010年07月29日

modoからFLASHへ書き出してみた その17 modo 401 SP4 ActionScript 3.0

急に涼しくなったら何か朦朧として眠い・・・zzz

さて、PaperVision3Dもペアレントまで調べたのでそろそろアニメーションにも手をつけようと思ってソースを眺めていたら、animationのパッケージがすぐ見つかって、探って行ったらAnimationController(org.papervision3d.core.controller.AnimationController )を組み込めば簡単にアニメーション出来そうな感じだ。そこで探り探り前回作ったクレーンのモデルにアニメーションの仕組みを組み込んでみることにした。

まず、このモデルで稼動するのはアームの上下方向の回転と本体の旋回方向の回転だけだ。これらの各フレームでの角度をアニメーションのデータとして何かの形でどこかに格納しなくちゃならない。
AnimationControllerのソースを読んで行くと、どうやら動きのデータはクリップ(org.papervision3d.core.animation.clip.AnimationClip3D)というモーションデータのかたまりか、チャンネル(org.papervision3d.core.animation.channel.Channel3D)というアニメーションさせるプロパティの個別のデータかのどっちかで扱うようになっているようだ。まあいきなりクリップはハードルが高いので、今回はチャンネルを定義することでクレーンを動かしてみることにした。そこで設定したのがアームを上下させるLIFTチャンネルと全体を旋回させるTURNチャンネルだ。チャンネルは複数のカーブデータ(org.papervision3d.core.animation.curve.Curve3D)が格納出来て、時間経過に対して値がどう変化するかを表現出来る。そのカーブデータは複数のキーデータ(org.papervision3d.core.animation.key.CurveKey3D)で出来ていて、org.papervision3d.core.animation.keyパッケージを見ると、ベジェ、線形、ステップなどのキー形式が使える。このあたりはCGのグラフ編集ではおなじみの話だね。

以上の道具を使って0秒の時点で角度0度、1秒の時点で角度120度、2秒のところで角度0度になるベジェカーブデータを持ったチャンネルを作成するには、

   var curve:Curve3D=new Curve3D( );
   curve.addKey(new BezierCurveKey3D(0,0));
   curve.addKey(new BezierCurveKey3D(1,120));
   curve.addKey(new BezierCurveKey3D(2,0));
   var channel:Channel3D=new Channel3D( );
   channel.addCurve(curve);

とやればいいみたいだ。addCurve( )でわかる通り、チャンネルには複数のカーブが格納出来、追加した順に配列に格納される。このチャンネルをAnimationControllerに格納するには、

var _animation:AnimationController=new AnimationController();

        :

_animation.addChannel(channel);

でOKだ。このaddChannel( )メソッドには現在登録されている全チャンネルのキーの時間範囲を調べてアニメーションのスタート時刻と終了時刻を自動的に設定する機能も付いている。

こうやって作ったカーブとそれを追加するために作ってアニメーションコントローラに追加したチャンネルはどちらも追加順に配列に格納されるだけなので、追加した順番を覚えておかないと、わけのわからない事になるから注意が必要だよ。名前くらい付けられるといいんだけどね。

チャンネルとカーブデータがAnimationControllerに設定されて、AnimationControllerのplay( )メソッドを実行すると、経過時間を見ながらイベント(org.papervision3d.events.AnimationEvent)を発生する事が出来るようになる。ただ、その仕組みを動かすには、AnimationControllerのupdate( )メソッドを定期的に実行する必要がある。このへんの仕組みはどうなっているのかまだ把握出来ていないので、とりあえずMainクラスのEvent.ENTER_FRAMEイベントで、フレームが更新されるたびに呼び出すようにしてみた。

あとはAnimationControllerから発生するイベントを捉えてモデルのプロパティーを変更するイベントハンドラを定義して、それをリスナ登録してやればOKだ。例えばNEXT_FRAMEイベントに以下のようなハンドラを作って

  private function next_frame(e:AnimationEvent){
   if(_animation.numChannels!=0){
    var ch:Channel3D=_animation.channels[0];
    ch.update(e.time);
    body.rotationY=ch.output[0];
    ch =_animation.channels[1];
    ch.update(e.time);
    arm.rotationZ=ch.output[0];
   }
  }

登録するには、

_animation.addEventListener(AnimationEvent.NEXT_FRAME,next_frame);

としてやればいい。ハンドラに渡されるイベントオブジェクトにはアニメーションさせるための時刻timeプロパティが入っているのでこれを使ってチャンネルに対して

ch.update(e.time);

としてやると、その時刻でのチャンネルの値がカーブを登録した順にoutputプロパティに出力され、その値をアニメーションさせたいモデルのプロパティに設定してやれば(カーブは1つしか登録していないのでoutput配列の最初にそのカーブの値が格納されるからそれを取り出すのにoutput[0]としているよ)、

body.rotationY=ch.output[0];

カーブで定義されたアニメーションの現在時刻での状態が再現できるわけだ。

そんな仕組みで作ったのが下のコード(Crane.as)だ。

package  {
 import org.papervision3d.objects.DisplayObject3D;
 import org.papervision3d.core.animation.IAnimationProvider;
 import org.papervision3d.core.controller.AnimationController;
 import org.papervision3d.core.geom.TriangleMesh3D;
 import org.papervision3d.core.proto.MaterialObject3D;
 import org.papervision3d.events.AnimationEvent;
 import org.papervision3d.core.animation.channel.Channel3D;
 import org.papervision3d.core.animation.curve.Curve3D;
 import org.papervision3d.core.animation.key.BezierCurveKey3D; 

 public class Crane extends DisplayObject3D{
  public static const TURN:int = 0;
  public static const LIFT:int = 1;
  var base:TriangleMesh3D;
  var body:TriangleMesh3D;
  var arm:TriangleMesh3D;
  var _animation:AnimationController;
  public function Crane(material:MaterialObject3D=null) {
   base=new Base(material);
   body=new Body(material);
   arm=new Arm(material);
   body.addChild(arm);
   base.addChild(body);
   this.addChild(base);
   arm.x=0.095;
   arm.y=0.564;
   body.y=0.182;
   _animation=new AnimationController();
   //Body Turn channel
   var ch:Channel3D=new Channel3D();
   var curve:Curve3D=new Curve3D();
   curve.addKey(new BezierCurveKey3D(0,0));
   curve.addKey(new BezierCurveKey3D(1,120));
   curve.addKey(new BezierCurveKey3D(2,0));
   ch.addCurve(curve);
   _animation.addChannel(ch);

   //Arm Lift channel
   ch=new Channel3D();
   curve=new Curve3D();
   curve.addKey(new BezierCurveKey3D(0,0));
   curve.addKey(new BezierCurveKey3D(0.5,30));
   curve.addKey(new BezierCurveKey3D(1,0));
   ch.addCurve(curve);
   _animation.addChannel(ch);

   _animation.addEventListener(AnimationEvent.START,start_anim);
   _animation.addEventListener(AnimationEvent.NEXT_FRAME,next_frame);
   _animation.addEventListener(AnimationEvent.COMPLETE,complete_anim);
  }
  
  public function update(){
   _animation.update();
  }
  
  public function play(){
   _animation.play();
  }
  private function start_anim(e:AnimationEvent){

  }
  private function next_frame(e:AnimationEvent){
   if(_animation.numChannels!=0){
    var ch:Channel3D=_animation.channels[TURN];
    ch.update(e.time);
    body.rotationY=ch.output[0];
    ch =_animation.channels[LIFT];
    ch.update(e.time);
    arm.rotationZ=ch.output[0];
   }
  }
  private function complete_anim(e:AnimationEvent){

  }
 }
}

そしてこれをシーンに登録して動かすMainコードがこれ。基本的にクレーンのインスタンスを作ってplayメソッドを実行し、定期的にupdateメソッドを実行しているだけだ。

package  {
 import org.papervision3d.view.BasicView;
 import org.papervision3d.objects.primitives.Sphere;
 import flash.events.Event;
 import org.papervision3d.objects.primitives.Plane;
 import org.papervision3d.core.geom.renderables.Vertex3D;
 import flash.display.DisplayObject;
 import org.papervision3d.objects.DisplayObject3D;
 import org.papervision3d.core.geom.TriangleMesh3D;
 import org.papervision3d.materials.ColorMaterial;
 import org.papervision3d.lights.PointLight3D;
 import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
 import org.papervision3d.materials.shadematerials.PhongMaterial;
 


 public class Main extends BasicView{
  var crane:Crane;
  public function Main() {

   var light:PointLight3D=new PointLight3D(true,false);
   var material:FlatShadeMaterial=new FlatShadeMaterial(light,0xffffff,0x111111,50);
   light.x=2;
   light.z=-2;
   light.y=2;
   crane=new Crane(material);
   scene.addChild(crane);
   var ct:DisplayObject3D=new DisplayObject3D();
   camera.x=1.2;
   camera.y=1.5;
   camera.z=-7;
   ct.y=1.6;
   camera.target=ct;
   startRendering();
   crane.play();
   addEventListener(Event.ENTER_FRAME,loop);
  }
  
  private function loop(e:Event){
   crane.update();
  }
 }
}

なんとか動くようになったけど、チャンネル1つにカーブを2つ登録してもいいわけで、このあたりはどう扱うのが正解なのかな?

それではまた次回。

modoカテゴリー別ページ



take_z_ultima at 13:29│Comments(0)TrackBack(0)modo | FLASH

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 

Archives