今回は3Dオブジェクトの座標変換を見ていく。
■ 座標系とは
座標系とは『物体の位置を(x,y,z)などの数値で表すための指標』である。このため、中心(0,0,0)をどこに設定するかだけでも無数の座標系が存在する。さらに、x=1を1mとするか1cmとするかでも座標系は異なる。
JavaFXにおいて、3Dオブジェクトはそれぞれ独自の座標系(ローカル座標系)の中心(0,0,0)に配置されている。それに対してレンダリングに利用する座標系をワールド座標系という。3Dオブジェクトのインスタンス化直後は、ローカル座標系=ワールド座標系となっている。
3Dオブジェクトの座標変換とはこのローカル座標系を移動させることであり、所属する3Dオブジェクトとも連動して移動することとなる。
■ 座標変換用のクラス
JavaFXで座標変換を表すクラスは以下の通りである。
クラス |
内容 |
Translate |
3次元の平行移動を表す |
Rotate |
3次元の回転を表す |
Scale |
3次元の拡大・縮小を表す |
Shear |
せん断(shear)を表す |
Affine |
座標変換用の4x3行列を表す。複数の座標変換をまとめる際などに利用する
引数なしでインスタンス化した場合は単位行列となる |
■ 座標変換の適用
3DオブジェクトやGroupクラスなどにあるgetTransforms関数で、3Dオブジェクトに設定されている座標変換のリストを取得できる。getTransforms().clear関数により座標変換を初期化でき、getTransforms().add関数により座標変換を追加できる。座標変換はadd関数で追加した順に実行される。
■ 座標変換の合成
getTransforms().add関数にて、座標変換リストに複数の座標変換を設定できることは説明したが、リストではなく1つの座標変換オブジェクトにまとめたい場合はAffineクラスを利用する。new Affine()で恒等変換(移動しない座標変換。単位行列に相当)を作成し、Affine::append関数で座標変換を追加できる。座標変換はappend関数で追加した順に実行される。
座標変換リスト/Affineクラスのどちらを利用する場合も、座標変換を合成する順番によって座標変換の内容が変わるため注意が必要である。
■ サンプルプログラム
以下に座標変換を利用するサンプルプログラムを示す。サンプルでは以下の3つのオブジェクトを描画している。
- 白い立方体:座標変換なし
- 赤い立方体:z軸を中心として45度回転後、xyz座標を2倍に拡大し、y方向に-3平行移動
- 青い立方体:y方向に-3平行移動後、z軸を中心として45度回転後、xyz座標を2倍に拡大
◇サンプルコード
package application_fx;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.LightBase;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
public class TestTransform extends Application
{
public static void main(String[] args)
{ launch( args );}
@Override
public void start(Stage primaryStage) throws Exception
{
// シーングラフのルートを作成
Group root = new Group();
// BOX(白)を作成
Box white = new Box();
PhongMaterial whiteMaterial = new PhongMaterial();
whiteMaterial.setDiffuseColor( Color.WHITE );
white.setWidth( 1 );
white.setHeight( 1 );
white.setDepth( 1 );
white.setMaterial( whiteMaterial );
root.getChildren().add( white );
// BOX(赤)を作成
Box red = new Box();
PhongMaterial redMaterial = new PhongMaterial();
redMaterial.setDiffuseColor( Color.RED );
red.setWidth( 1 );
red.setHeight( 1 );
red.setDepth( 1 );
red.setMaterial( redMaterial );
root.getChildren().add( red );
// BOX(青)を作成
Box blue = new Box();
PhongMaterial blueMaterial = new PhongMaterial();
blueMaterial.setDiffuseColor( Color.BLUE );
blue.setWidth( 1 );
blue.setHeight( 1 );
blue.setDepth( 1 );
blue.setMaterial( blueMaterial );
root.getChildren().add( blue );
// 白い箱に恒等変換を設定
red.getTransforms().add( new Affine() );
// 赤い箱に変換を設定
red.getTransforms().add( new Rotate( 45 , new Point3D( 0 , 0 , 1 ) ) );
red.getTransforms().add( new Scale( 2.0 , 2.0 , 2.0 ) );
red.getTransforms().add( new Translate( 0 , -3 , 0 ) );
// 青い箱に変換を設定
blue.getTransforms().add( new Translate( 0 , -3 , 0 ) );
blue.getTransforms().add( new Rotate( 45 , new Point3D( 0 , 0 , 1 ) ) );
blue.getTransforms().add( new Scale( 2.0 , 2.0 , 2.0 ) );
// カメラを設定
Camera camera = new PerspectiveCamera( true );
camera.getTransforms().add( new Translate( 0.0 , 0.0 , -30 ) );
root.getChildren().add( camera );
// 照明を設定
LightBase light = new PointLight();
light.setTranslateZ( -30.0 );
root.getChildren().add( light );
// 3D用のーンを作成
Scene scene = new Scene( root , 500 , 500 , true );
scene.setFill( Color.BLACK );
scene.setCamera( camera );
// ウィンドウ表示
primaryStage.setScene( scene );
primaryStage.show();
}
}
◇実行結果
◇解説
- 白、赤、青の立方体を作成した際は、ワールド座標系=ローカル座標系となっている(32行目~59行目)。
- 白の立方体には恒等変換を設定している(63行目)ため、中心から移動していない。
- 赤の立方体は、ローカル座標系を『z軸を中心として45度回転後、xyz座標を2倍に拡大し、y方向に-3平行移動』している(66行目~68行目)。
- 青の立方体は、ローカル座標を『y方向に-3平行移動後、z軸を中心として45度回転後、xyz座標を2倍に拡大』している(71行目~73行目)。赤の立方体とは異なり、平行移動の前にローカル座標の軸が45度回転/ローカル座標が2倍に拡大されているため、ワールド座標系でいうy軸が45度傾いた方向に-5*2=-10平行移動し、立方体も2倍に拡大している。
OpenGL等では回転が『ワールド座標系の(0,0,0)で回転』となっていた(と記憶している)が、JavaFXでは『その場で回転』となっている点に注意が必要である。
■ 参照
- JavaDoc - クラスTransform