Papervision3D は左手座標系(left-handed coordinate sysytem)。
左手の親指がX軸、人差し指がY軸、中指がZ軸になる座標系。

Papervision3D の座標情報が3D空間ではどんなふうに見えるか確認するために Flash コンテンツを作ってみた。

[軸の色]
X: red
Y: green
Z: blue

[立方体の面の色]
front: red
back: green
right: blue
left: aqua
top: yellow
bottom: purple

[キー操作]
[z]キー: 面の裏側の描画をON/OFF切替
[a]キー: pitch (ピッチ: X軸まわりの回転)
[s]キー: yaw (ヨー: Y軸まわりの回転)
[d]キー: roll (ロール: Z軸まわりの回転)
[r]キー: 初期状態へ戻す

気づいたことを以下にメモ。

カメラの視線

camera rotate x = 0
camera rotate y = 0
camera rotate z = 0
のとき、カメラの視線はZ軸の負から正の方向へ向かっている。

オブジェクトの回転

pitch, yaw, roll に正の数を指定したときの回転方向
Papervision3D pitch-yaw-roll

それぞれの軸の負から正の方向へ向いたときに、反時計回り。

この Cube オブジェクトの ratationX, ratationY, ratationZ の値を増加したときの回転方向
Papervision3D rotation xyz

以下の2パターンは回転の感覚が違う。
-TARGET ROTATE Y が 0 のときに、TARGET ROTATE X を 0 から増加させていくとき。
-TARGET ROTATE Y が 180 にときに、TARGET ROTATE X を 0 から増加させていくとき。

3D空間のXYZ軸を中心に回転すると思っていたが、そうではなく、オブジェクト毎に持っている内部座標のXYZ軸を中心に回転しているっぽい。なかなか理解できない……

ちなみに、pitch, yaw, roll はフライトシミュレーション用語らしい。
参考: Amazon.co.jp: ゲーム開発のための数学・物理学入門 (Wendy Stahler 著)

ソースコード

開発環境: Flex 2.0.1 & Papervision 2.0 Alpha (Great White)

必要なのは4つのクラス。

  • FreeMoving.as: 実行エントリとなるメインクラス。
  • Axes.as: X軸Y軸Z軸
  • NiButton.as: SimpleButton - クジラ Flash ActionScript3 Tips の KuButton クラスを参考にして作ったボタンクラス。
  • PmBar.as: 値の増減を管理する Plus Minus Bar なクラス

FreeMoving.as


package {
 
  import flash.display.*;
  import flash.events.*;
  import flash.text.*;
 
  import org.papervision3d.cameras.*;
  import org.papervision3d.core.proto.*;
  import org.papervision3d.materials.*;
  import org.papervision3d.materials.utils.*;
  import org.papervision3d.objects.*;
  import org.papervision3d.objects.primitives.*;
  import org.papervision3d.render.*;
  import org.papervision3d.scenes.*;
  import org.papervision3d.view.*;
  
  [SWF(width="600", height="600", backgroundColor="#000000", frameRate="30")]
 
  // Papervision3D は左手系座標
  public class FreeMoving extends Sprite {
 
    private const _initialCameraX:Number = 100;
    private const _initialCameraY:Number = 100;
    private const _initialCameraZ:Number = -500;
    private const _initialCameraRotateX:Number = -15;
    private const _initialCameraRotateY:Number =  -15;
    private const _initialCameraRotateZ:Number = 0;
    private const _initialCameraFocus:Number = 500;
    private const _initialCameraZoom:Number = 3;
    
    private var _scene       : Scene3D;
    private var _rootNode    : DisplayObject3D;
    
    private var _viewport    : Viewport3D;
    private var _renderer    : BasicRenderEngine;
 
    private var _camera      : FreeCamera3D;
    
    private var _target      : Cube;
 
    private var _targetx     : PmBar;
    private var _targety     : PmBar;
    private var _targetz     : PmBar;
    private var _targetrx    : PmBar;
    private var _targetry    : PmBar;
    private var _targetrz    : PmBar;
    private var _camerax     : PmBar;
    private var _cameray     : PmBar;
    private var _cameraz     : PmBar;
    private var _camerarx    : PmBar;
    private var _camerary    : PmBar;
    private var _camerarz    : PmBar;
    private var _camerafocus : PmBar;
    private var _camerazoom  : PmBar;
 
    public function FreeMoving():void {
 
      stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
 
      // シーンオブジェクトを作る
      _scene = new Scene3D();
      
      // 3D空間表示領域オブジェクトを作って Sprite#addChild
      _viewport = new Viewport3D(this.stage.stageWidth, this.stage.stageHeight);
      addChild(_viewport);
      
      // 描画エンジンオブジェクトを作る
      _renderer = new BasicRenderEngine();
 
      // カメラオブジェクトを作る
      _camera = new FreeCamera3D();
      _camera.x = _initialCameraX;
      _camera.y = _initialCameraY;
      _camera.z = _initialCameraZ;
      _camera.rotationX = _initialCameraRotateX;
      _camera.rotationY = _initialCameraRotateY;
      _camera.rotationZ = _initialCameraRotateZ;
      _camera.focus = _initialCameraFocus;
      _camera.zoom  = _initialCameraZoom;
 
      // ルートノードを作る
      _rootNode = new DisplayObject3D();
      _scene.addChild(_rootNode);
      
      // X軸, Y軸, Z軸のオブジェクトを作成して3D空間に追加
      var axes:Axes = new Axes(
        createWireframeMaterial(0xFF0000),
        createWireframeMaterial(0x00FF00),
        createWireframeMaterial(0x0000FF));
      _rootNode.addChild(axes);
      
      // ターゲットオブジェクト(立方体Cube)を作成して3D空間に追加
      _target = createTarget();
      _rootNode.addChild(_target);
 
      // コントロールパネルを作成
      buildControlPanel();
      
      // 3D空間を描画
      updateView();
    }
    
    private function buildControlPanel():void{
      
      var offsetx:Number = 10;
      var offsety:Number = 10;
      
      var labelWidth:Number = 120;
      var valueWidth:Number = 40;
      
      var targetMoveUnit:Number = 20;
      var targetRotateUnit:Number = 15;
      var cameraMoveUnit:Number = 50;
      var cameraRotateUnit:Number = 15;
      
      _targetx = new PmBar("TARGET X",labelWidth, valueWidth, 20, 0, -1000, 1000, targetMoveUnit, changeTargetx);
      _targetx.x = offsetx;
      _targetx.y = offsety;
      addChild(_targetx);
      
      _targety = new PmBar("TARGET Y",labelWidth, valueWidth, 20, 0, -1000, 1000, targetMoveUnit, changeTargety);
      _targety.x = offsetx;
      _targety.y = offsety + _targetx.height;
      addChild(_targety);
      
      _targetz = new PmBar("TARGET Z",labelWidth, valueWidth, 20, 0, -1000, 1000, targetMoveUnit, changeTargetz);
      _targetz.x = offsetx;
      _targetz.y = offsety + _targetx.height * 2;
      addChild(_targetz);
      
      _targetrx = new PmBar("TARGET ROTATE X",labelWidth, valueWidth, 20, 0, -1000, 1000, targetRotateUnit, changeTargetrx);
      _targetrx.x = offsetx;
      _targetrx.y = offsety + _targetx.height * 3;
      addChild(_targetrx);
      
      _targetry = new PmBar("TARGET ROTATE Y",labelWidth, valueWidth, 20, 0, -1000, 1000, targetRotateUnit, changeTargetry);
      _targetry.x = offsetx;
      _targetry.y = offsety + _targetx.height * 4;
      addChild(_targetry);
      
      _targetrz = new PmBar("TARGET ROTATE Z",labelWidth, valueWidth, 20, 0, -1000, 1000, targetRotateUnit, changeTargetrz);
      _targetrz.x = offsetx;
      _targetrz.y = offsety + _targetx.height * 5;
      addChild(_targetrz);
      
      _camerax = new PmBar("CAMERA X",labelWidth, valueWidth, 20, _initialCameraX, -1000, 1000, cameraMoveUnit, changeCamerax);
      _camerax.x = offsetx;
      _camerax.y = offsety + _camerax.height * 6;
      addChild(_camerax);
      
      _cameray = new PmBar("CAMERA Y",labelWidth, valueWidth, 20, _initialCameraY, -1000, 1000, cameraMoveUnit, changeCameray);
      _cameray.x = offsetx;
      _cameray.y = offsety + _camerax.height * 7;
      addChild(_cameray);
      
      _cameraz = new PmBar("CAMERA Z",labelWidth, valueWidth, 20, _initialCameraZ, -1000, 1000, cameraMoveUnit, changeCameraz);
      _cameraz.x = offsetx;
      _cameraz.y = offsety + _camerax.height * 8;
      addChild(_cameraz);
      
      _camerarx = new PmBar("CAMERA ROTATE X",labelWidth, valueWidth, 20, _initialCameraRotateX, -1000, 1000, cameraRotateUnit, changeCamerarx);
      _camerarx.x = offsetx;
      _camerarx.y = offsety + _camerax.height * 9;
      addChild(_camerarx);
      
      _camerary = new PmBar("CAMERA ROTATE Y",labelWidth, valueWidth, 20, _initialCameraRotateY, -1000, 1000, cameraRotateUnit, changeCamerary);
      _camerary.x = offsetx;
      _camerary.y = offsety + _camerax.height * 10;
      addChild(_camerary);
      
      _camerarz = new PmBar("CAMERA ROTATE Z",labelWidth, valueWidth, 20, _initialCameraRotateZ, -1000, 1000, cameraRotateUnit, changeCamerarz);
      _camerarz.x = offsetx;
      _camerarz.y = offsety + _camerax.height * 11;
      addChild(_camerarz);
      
      // focus:
      // This value is a positive number representing the distance of the observer from the front clipping plane,
      // which is the closest any object can be to the camera.
      // Use it in conjunction with zoom.
      _camerafocus = new PmBar("CAMERA FOCUS",labelWidth, valueWidth, 20, _initialCameraFocus, -1000, 1000, 100, changeCamerafocus);
      _camerafocus.x = offsetx;
      _camerafocus.y = offsety + _camerax.height * 12;
      addChild(_camerafocus);
      
      // zoom:
      // This value specifies the scale at which the 3D objects are rendered.
      // Higher values magnify the scene, compressing distance.
      // Use it in conjunction with focus.
      _camerazoom = new PmBar("CAMERA ZOOM",labelWidth, valueWidth, 20, _initialCameraZoom, -1000, 1000, 1, changeCamerazoom);
      _camerazoom.x = offsetx;
      _camerazoom.y = offsety + _camerax.height * 13;
      addChild(_camerazoom);
    }
    
    private function changeTargetx(self:PmBar, value:Number):void{
      _target.x = value;
      updateView();
    }
 
    private function changeTargety(self:PmBar, value:Number):void{
      _target.y = value;
      updateView();
    }
 
    private function changeTargetz(self:PmBar, value:Number):void{
      _target.z = value;
      updateView();
    }
 
    private function changeTargetrx(self:PmBar, value:Number):void{
      _target.rotationX = value;
      updateView();
    }
 
    private function changeTargetry(self:PmBar, value:Number):void{
      _target.rotationY = value;
      updateView();
    }
 
    private function changeTargetrz(self:PmBar, value:Number):void{
      _target.rotationZ = value;
      updateView();
    }
 
    private function changeCamerax(self:PmBar, value:Number):void{
      _camera.x = value;
      updateView();
    }
 
    private function changeCameray(self:PmBar, value:Number):void{
      _camera.y = value;
      updateView();
    }
 
    private function changeCameraz(self:PmBar, value:Number):void{
      _camera.z = value;
      updateView();
    }
 
    private function changeCamerarx(self:PmBar, value:Number):void{
      _camera.rotationX = value;
      updateView();
    }
 
    private function changeCamerary(self:PmBar, value:Number):void{
      _camera.rotationY = value;
      updateView();
    }
 
    private function changeCamerarz(self:PmBar, value:Number):void{
      _camera.rotationZ = value;
      updateView();
    }
 
    private function changeCamerafocus(self:PmBar, value:Number):void{
      _camera.focus = value;
      updateView();
    }
 
    private function changeCamerazoom(self:PmBar, value:Number):void{
      _camera.zoom = value;
      updateView();
    }
 
    private function createTarget():Cube {
    
      var materials:MaterialsList = new MaterialsList({
        //all: createWireframeMaterial(0xFFFF00),
        front: createWireframeMaterial(0xFF0000),
        back: createWireframeMaterial(0x00FF00),
        right: createWireframeMaterial(0x0000FF),
        left: createWireframeMaterial(0x00FFFF),
        top: createWireframeMaterial(0xFFFF00),
        bottom: createWireframeMaterial(0xFF00FF)
      });
      var width:Number  = 200;
      var depth:Number  = 200;
      var height:Number = 200;
      var segmentsS:int = 2;
      var segmentsT:int = 3;
      var segmentsH:int = 4;
      var insideFaces:int = 0;
      var excludeFaces:int = 0;
      var initObject:Object = null;
      
      var obj:Cube = new Cube(materials, width, depth, height, segmentsS, segmentsT, segmentsH, insideFaces, excludeFaces, initObject);
      obj.x = 0;
      obj.y = 0;
      obj.z = 0;
      return obj;
    }
 
    private function createWireframeMaterial(color:Number):MaterialObject3D {
      var material:WireframeMaterial = new WireframeMaterial(color);
      material.lineAlpha = 1;
      //material.oneSide = !_doubleSided;
      material.doubleSided = false;
      return material;
    }
 
    private function reset():void{
      _targetx.reset();
      _targety.reset();
      _targetz.reset();
      _targetrx.reset();
      _targetry.reset();
      _targetrz.reset();
      _camerax.reset();
      _cameray.reset();
      _cameraz.reset();
      _camerarx.reset();
      _camerary.reset();
      _camerarz.reset();
      _camerafocus.reset();
      _camerazoom.reset();
    }
    
    private function change():void{
      _target.x = _targetx.value;
      _target.y = _targety.value;
      _target.z = _targetz.value;
      _target.rotationX = _targetrx.value;
      _target.rotationY = _targetry.value;
      _target.rotationZ = _targetrz.value;
      _camera.x = _camerax.value;
      _camera.y = _cameray.value;
      _camera.z = _cameraz.value;
      _camera.rotationX = _camerarx.value;
      _camera.rotationY = _camerary.value;
      _camera.rotationZ = _camerarz.value;
      _camera.focus = _camerafocus.value;
      _camera.zoom = _camerazoom.value;
    }
    
    private function updateView():void{
      _renderer.renderScene(_scene, _camera, _viewport);
    }
    
    private function updateControlPanel():void{
      _camerarx.value = _target.rotationX;
      _camerary.value = _target.rotationY;
      _camerarz.value = _target.rotationZ;
    }
    
    private function onKeyDown(event:KeyboardEvent):void {
 
      switch(event.keyCode) {
        case 65: // aキーでX軸方向の回転
          _target.pitch(15);
          updateControlPanel();
          updateView();
          break;
        case 83: // sキーでY軸方向の回転
          _target.yaw(15);
          updateControlPanel();
          updateView();
          break;
        case 68: // dキーでZ軸方向の回転
          _target.roll(15);
          updateControlPanel();
          updateView();
          break;
        case 90: // zキーでキューブの内側をON/OFF
          reverseMaterialListDoubleSided(_target.materials);
          updateView();
          break;
        case 82: // rキーでリセット
          reset();
          change();
          updateView();
          break;
      }
    }
    
    private static function reverseMaterialListDoubleSided(materials:MaterialsList):void{
      reverseDoubleSided(materials.getMaterialByName("front"));
      reverseDoubleSided(materials.getMaterialByName("back"));
      reverseDoubleSided(materials.getMaterialByName("right"));
      reverseDoubleSided(materials.getMaterialByName("left"));
      reverseDoubleSided(materials.getMaterialByName("top"));
      reverseDoubleSided(materials.getMaterialByName("bottom"));
    }
 
    private static function reverseDoubleSided(material:MaterialObject3D):void{
      material.doubleSided = !material.doubleSided;
    }
  }
}

Axes.as


package {
  import org.papervision3d.core.proto.*;
  import org.papervision3d.materials.utils.*;
  import org.papervision3d.objects.*;
  import org.papervision3d.objects.primitives.*;
 
  // 軸オブジェクト(X軸Y軸Z軸を描画)
  public class Axes extends DisplayObject3D {
 
    public function Axes(
      xMaterial:MaterialObject3D = null,
      yMaterial:MaterialObject3D = null,
      zMaterial:MaterialObject3D = null,
      length:Number = 1000,
      thickness:Number = 1,
      initObject:Object = null){
 
      var name:String = null;
      var geometry:GeometryObject3D = null;
      super(name, geometry, initObject);
 
      buildAxes(xMaterial, yMaterial, zMaterial, length, thickness);
    }
 
    private function buildAxes(
      xm:MaterialObject3D, ym:MaterialObject3D, zm:MaterialObject3D,
      length:Number, thickness:Number):void {
 
      // Cube(materialslist, width, depth, height);
      //x=width, z=depth, y=height だと思って書いてみたが
      //z=width, x=depth, y=height になっているような……
      var xml:MaterialsList = new MaterialsList();
      var yml:MaterialsList = new MaterialsList();
      var zml:MaterialsList = new MaterialsList();
      xml.addMaterial(xm, "all");
      yml.addMaterial(ym, "all");
      zml.addMaterial(zm, "all");
      var segmentsS:int = 8;
      var segmentsT:int = 8;
      var segmentsH:int = 8;
      var axisx:Cube = new Cube(xml, length, thickness, thickness, segmentsS, segmentsT, segmentsH);
      var axisy:Cube = new Cube(yml, thickness, thickness, length, segmentsS, segmentsT, segmentsH);
      var axisz:Cube = new Cube(zml, thickness, length, thickness, segmentsS, segmentsT, segmentsH);
      addChild(axisx);
      addChild(axisy);
      addChild(axisz);
    }
 
  }
}

NiButton.as


package
{
  import flash.display.Sprite;
  import flash.display.SimpleButton;
  import flash.text.*;
 
  public class NiButton extends SimpleButton
  {
    private var aLabel:String;
    private var aWidth:Number  = 48;
    private var aHeight:Number = 20;
    
    public function NiButton(
      label:String = "Button",
      width:Number = 48,
      height:Number = 20,
      alpha:Number = 1.0,
      upStateTextColor:uint    = 0xFFFFFF,
      upStateFaceColor:uint    = 0x000000,
      upStateBorderColor:uint    = 0xFFFFFF,
      overStateTextColor:uint    = 0x000000,
      overStateFaceColor:uint    = 0xC0C0C0,
      overStateBorderColor:uint  = 0x8080FF,
      downStateTextColor:uint    = 0x000000,
      downStateFaceColor:uint    = 0xFFFFFF,
      downStateBorderColor:uint  = 0xC0C0C0,
      hittestStateTextColor:uint   = 0xFFFFFF,
      hittestStateFaceColor:uint   = 0x000000,
      hittestStateBorderColor:uint = 0xFFFFFF)
    {
      aLabel = label;
      aWidth = width;
      aHeight = height;
      this.upState   = drawButton(upStateTextColor, upStateFaceColor, upStateBorderColor, alpha);
      this.overState = drawButton(overStateTextColor, overStateFaceColor, overStateBorderColor, alpha);
      this.downState = drawButton(downStateTextColor, downStateFaceColor, downStateBorderColor, alpha);
      this.hitTestState = drawButton(hittestStateTextColor, hittestStateFaceColor, hittestStateBorderColor, alpha);
    }
    
    private function drawButton(textColor:uint, faceColor:uint, borderColor:uint, alpha:Number):Sprite
    {
      // Button
      var p:Sprite = new Sprite();
      p.graphics.lineStyle(1, borderColor);
      p.graphics.beginFill(faceColor, alpha);
      p.graphics.drawRect(0, 0, aWidth, aHeight);
      p.graphics.endFill();
      // Text
      var t:TextField = new TextField();
      t.text = aLabel;
      t.width = aWidth;
      t.textColor = textColor;
      t.autoSize = TextFieldAutoSize.CENTER;
      p.addChild(t);
      return p;
    }      
  }
}

PmBar.as


package {
  
  import flash.display.*;
  import flash.events.*;
  import flash.text.*;
 
  public class PmBar extends Sprite{
    
    private var _plusButton:SimpleButton;
    private var _minusButton:SimpleButton;
    private var _label:TextField;
    private var _number:TextField;
    private var _value:Number;
    private var _initialValue:Number;
    private var _minValue:Number;
    private var _maxValue:Number;
    private var _unitValue:Number;
    private var _listener:Function;
    
    public function PmBar(label:String, labelWidth:Number, valueWidth:Number, height:Number, initialValue:Number, minValue:Number, maxValue:Number, unitValue:Number, listener:Function = null) {
      
      const buttonWidth:Number = 20;
      
      _value = initialValue;
      _initialValue = initialValue;
      _minValue = minValue;
      _maxValue = maxValue;
      _unitValue = unitValue;
      _listener = listener;
      
      _label = new TextField();
      _label.text      = label;
      _label.autoSize    = TextFieldAutoSize.NONE;
      _label.width = labelWidth;
      _label.height = height;
      _label.textColor   = 0xFFFFFF;
      _label.border = true;
      _label.borderColor = 0xFFFFFF;
      _label.x = 0;
      _label.y = 0;
      addChild(_label);
      
      _number = new TextField();
      _number.text      = "" + _value;
      _number.autoSize    = TextFieldAutoSize.NONE;
      _number.width = valueWidth;
      _number.height = height;
      _number.textColor   = 0xFFFFFF;
      _number.border = true;
      _number.borderColor = 0xFFFFFF;
      _number.x = labelWidth;
      _number.y = 0;
      addChild(_number);
      
      _plusButton = new NiButton("+", height, height, 0.8);
      _plusButton.x = labelWidth + valueWidth;
      _plusButton.y = 0;
      _plusButton.addEventListener(MouseEvent.MOUSE_DOWN, plus);
      addChild(_plusButton);
      
      _minusButton = new NiButton("-", height, height, 0.8);
      _minusButton.x = labelWidth + valueWidth + buttonWidth;
      _minusButton.y = 0;
      _minusButton.addEventListener(MouseEvent.MOUSE_DOWN, minus);
      addChild(_minusButton);
    }
    
    private function plus(event:MouseEvent):void{
      _value += _unitValue;
      if(_maxValue < _value){
        _value = _maxValue;
      }
      _number.text = "" + _value;
      
      if(_listener != null){
        _listener..call(null, this, _value);
      }
    }
    
    private function minus(event:MouseEvent):void{
      _value -= _unitValue;
      if(_minValue > _value){
        _value = _minValue;
      }
      _number.text = "" + _value;
      
      if(_listener != null){
        _listener.call(null, this, _value);
      }
    }
    
    public function reset():void{
      _value = _initialValue;
      _number.text = "" + _value;
    }
    
    public function get value(): Number {
      return _value;
    }     
    
    public function set value(v:Number):void {
      _value = v;
      _number.text = "" + _value;
    }     
  }
  
}

参考

tags: zlashdot Flash Flash Flex Papervision3D

Posted by NI-Lab. (@nilab)