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 に正の数を指定したときの回転方向
それぞれの軸の負から正の方向へ向いたときに、反時計回り。
この Cube オブジェクトの ratationX, ratationY, ratationZ の値を増加したときの回転方向
以下の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)