[AS3] focalLengthでスピード感が違うみたい

2009/07/2

こんにちは。きんくまです。
今日はあいにくの雨です。もうすぐ梅雨あけですかね?

今回こんなのを作りました。


さて、本題の前に余談です。

昨日、jsflEditを作る際に目標にしていたものがSparkにアップされまして、興奮しちゃいました。
>> PsycodeLive(ライブコーディング用AS3エディタ)
機能としては、コード入力したものを保存・再生したりするものです。
で、この機能もいいですが、個人的に気になっていたのがエディタ機能。
オートインデントやアンドゥ、シンタックスハイライトなどをflashで作っちゃってるわけです。すげえっす。
いろいろとどうやって実装しているのか気になります。ソースを読んで勉強しよう。

また、このエディターは分離して別ページにアップされてました。
>> Psycode(TextAreaの拡張。機能はインデント・自動インデント/アンインデント・アンドゥ・リドゥ)

ここから本題です。

focalLengthによるスピード感の違い

Player10から搭載された3D機能。
まだあんまり触っていなかったので、しばらく突っ込んで勉強しようと思いました。
で、デモを作ったのが上のものです。

しばらく見ていると酔ってくるので注意です。(後述)
焦点距離のfocalLengthというプロパティがあります。
マウスを画面の中央からの距離に応じて、遠ければ遠いほどfocalLengthを大きくなるようにしました。
で、中央に向かえば向かうほど、板が速くなるような感じがすると思いませんか?

ちょっと前にマリオカートWiiを買ったんですが、たぶんこの錯覚?を利用しているんじゃないかという場面がありました。
ミニターボです。
ターボを発動した瞬間、一瞬画面が遠ざかるような感じになるんです。
これはたぶんこの焦点距離を小さくしているんじゃないでしょうか。

あと、ずっと見ていると3D酔いをしてきて困ってしまいました。
たぶん原因はこの焦点距離をぐいぐい動かしているからだと思います。
たんにオブジェクトを回転させるだけだとここまで酔いません。
3Dコンテンツを作る際はこのあたりも気を使った方が良さそうですね。

今回3Dで初めて使ったクラスや関数がいくつかあります。
perspectiveProjection, focalLength, local3DToGlobalなど。
このあたりはもう少し理解したらまとめたいです。

以下ソース。
クラス名のあとに3とか2がついているのは実験でクラスファイルをごちゃごちゃ作ってたなごりです。

Drive3

package
{
  import flash.display.Graphics;
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.geom.PerspectiveProjection;
  import flash.geom.Point;
  import flash.geom.Vector3D;

  [SWF(width="600",height="400",frameRate="30")]
  public class Drive3 extends Sprite
  {
    public var canvas:Sprite;
    public var rects:Array;
    public var numOfRect:int = 30;
    public var pcenter:Point;
    public var frontRect:RectPoints2;
    public static const MAX_FL:Number = 800;
    public static const MIN_FL:Number = 200;
    public static var fl:Number = 400;
    public static var max_fl_len:Number; //中央からの最大の距離の2乗
    public var isMotioning:Boolean = false;
    public static var STAGE_WIDTH:Number;
    public static var STAGE_HEIGHT:Number;

    public function Drive3()
    {
      STAGE_WIDTH = this.stage.stageWidth;
      STAGE_HEIGHT = this.stage.stageHeight;

      var hw:Number = STAGE_WIDTH / 2;
      var hh:Number = STAGE_HEIGHT / 2;
      transform.perspectiveProjection.focalLength = fl;

      max_fl_len = hw * hw + hh * hh;

      canvas = new Sprite();
      canvas.z = 0;

      pcenter = new Point();
      pcenter.x = hw;
      pcenter.y = hh;
      addChild(canvas);

      frontRect = new RectPoints2( -fl + 50, new Point(hw, hh));

      var i:int;
      var r:RectPoints2;
      rects = new Array();
      for (i = 0; i < numOfRect; i++) {
        r = new RectPoints2(30000 / numOfRect * i, new Point(hw, hh));
        rects[i] = r;
      }

      this.stage.addEventListener(MouseEvent.CLICK, switchMotion);
      ef();
    }

    private function switchMotion(e:MouseEvent):void
    {
      isMotioning = !isMotioning;
      if (isMotioning) {
        this.addEventListener(Event.ENTER_FRAME, ef);
      }else {
        this.removeEventListener(Event.ENTER_FRAME, ef);
      }
    }

    private function ef(e:Event = null):void
    {
      pcenter.x += (this.stage.mouseX - pcenter.x) * 0.1;
      pcenter.y += (this.stage.mouseY - pcenter.y) * 0.1;
      transform.perspectiveProjection.projectionCenter = new Point(pcenter.x, pcenter.y);
      render();
    }

    public function render():void
    {
      setFL();
      zSort();

      var i:int;
      var r:RectPoints2;
      var g:Graphics = canvas.graphics;
      g.clear();
      for (i = 0; i < numOfRect; i++) {
        r = rects[i];
        r.move();
        drawRect(r);
      }
      drawLineToCenter();
    }

    //中央に向かって線をひく
    private function drawLineToCenter():void
    {
      var g:Graphics = canvas.graphics;
      var i:int;
      var v:Vector3D;
      var pt:Point;
      var p:PerspectiveProjection = transform.perspectiveProjection;
      var currentCenter:Point = new Point(p.projectionCenter.x, p.projectionCenter.y);
      var sub:Point;
      var a:Number;
      var b:Number;

      g.lineStyle(1, 0xd4dfe5, 0.4);

      for (i = 0; i < 4; i++) {
        g.moveTo(currentCenter.x, currentCenter.y);
        v = frontRect["v" + (i + 1)];
        pt = canvas.local3DToGlobal(v);

        //範囲外を戻す
        sub = pt.subtract(currentCenter);
        a = sub.y / sub.x;
        b = currentCenter.y - a * currentCenter.x;
        if (pt.x < 0) {
          pt.x = 0;
          pt.y = b;
        }else if (pt.x > STAGE_WIDTH) {
          pt.x = STAGE_WIDTH;
          pt.y = a * pt.x + b;
        }
        if (pt.y < 0) {
          pt.y = 0;
          pt.x = -1 * b / a;
        }else if(pt.y > STAGE_HEIGHT){
          pt.y = STAGE_HEIGHT;
          pt.x = (pt.y - b) / a;
        }
        g.lineTo(pt.x, pt.y);
      }
    }

    //中心からの距離に応じてfocalLengthを変える
    private function setFL():void
    {
      var w:Number = STAGE_WIDTH / 2 - this.stage.mouseX;
      var h:Number = STAGE_HEIGHT / 2 - this.stage.mouseY;
      var len:Number = w * w + h * h;
      var targetFL:Number = (MAX_FL - MIN_FL) * (len / max_fl_len) + MIN_FL;
      fl += (targetFL - fl) * 0.1;
      transform.perspectiveProjection.focalLength = fl;

      frontRect.z = -fl + 10;
    }

    public function drawRect(r:RectPoints2):void
    {
      var g:Graphics = canvas.graphics;
      var pt1:Point;
      var pt2:Point;
      var pt3:Point;
      var pt4:Point;
      g.beginFill(r.color, 0.3);
      pt1 = canvas.local3DToGlobal(r.v1);
      pt2 = canvas.local3DToGlobal(r.v2);
      pt3 = canvas.local3DToGlobal(r.v3);
      pt4 = canvas.local3DToGlobal(r.v4);

      //大きすぎるものは描画しない
      if (Point.distance(pt1, pt2) > 1000) {
        return;
      }else if (Point.distance(pt2, pt3) > 1000) {
        return;
      }
      g.moveTo(pt1.x, pt1.y);
      g.lineTo(pt2.x, pt2.y);
      g.lineTo(pt3.x, pt3.y);
      g.lineTo(pt4.x, pt4.y);
      g.lineTo(pt1.x, pt1.y);
    }

    public function zSort():void
    {
      rects.sortOn("z", Array.NUMERIC | Array.DESCENDING);
    }
  }

}

RectPoints2

package
{
  import flash.geom.Point;
  import flash.geom.Vector3D;

  public class RectPoints2
  {
    public var v1:Vector3D;
    public var v2:Vector3D;
    public var v3:Vector3D;
    public var v4:Vector3D;
    public var vectors:Vector.<Vector3D>;
    public var _z:Number;
    public var vz:Number = -30;
    public var color:uint;

    public function RectPoints2(setZ:Number, offset:Point)
    {
      this._z = setZ;
      var i:int;
      var w:int = 100;
      var h:int = 60;
      v1 = new Vector3D( -w + offset.x, -h + offset.y, _z);
      v2 = new Vector3D(  w + offset.x, -h + offset.y, _z);
      v3 = new Vector3D(  w + offset.x,  h + offset.y, _z);
      v4 = new Vector3D( -w + offset.x,  h + offset.y, _z);
      vectors = new Vector.<Vector3D>();
      vectors.push(v1);
      vectors.push(v2);
      vectors.push(v3);
      vectors.push(v4);
      color = Math.floor(0xffffff * Math.random());
    }

    public function move():void
    {
      this.z += vz;
      if (this._z < -1 * (Drive3.fl - 40)) {
        this.z = 30000;
        color = Math.random() * 0xffffff;
      }
    }

    public function set z(setZ:Number):void
    {
      var i:int;
      var v:Vector3D;
      _z = setZ;
      for (i = 0; i < 4; i++) {
        v = vectors[i];
        v.z = _z;
      }
    }

    public function get z():Number
    {
      return _z;
    }
  }

}

あと、wonderflにも投稿しました。
>> local3DToGlobalを使ったドライブっぽくしたやつ


ページトップへ戻る