torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFX 3Dの座標系とカメラの向きについて

JavaFX 3Dの座標系があやふやでしたので、整理をするためにいろいろ調査・実験をしています。

JavaFX 2D、3Dの座標系の定義

JavaFX 2Dの座標系


JavaFX 2Dの座標系は右の図のように、画面左上隅を原点とし、横方向がX軸(右向きが正)、縦方向がY軸(下方向が正)となる座標系です。
これは、数学でおなじみの座標系とはY軸の向きが逆となるため、JavaFXで2次元のグラフィックスを扱うときに考慮が必要です。

JavaFX 3Dの座標系

JavaFX 3Dの座標系は右の図のように、先ほどのJavaFX 2Dの座標系にZ軸を追加したものとなります。Z軸は画面に垂直な方向(奥行が正)となります。

JavaFX 2Dのときは、数学の座標系とはY軸の向きが異なっていました。そのため、その2D座標系にZ軸を追加しただけのJavaFX 3D座標系は、てっきり数学の座標系とは異なるものと思い込んでいました。


しかし、JavaFX 3Dの座標系を右図のように、Z軸を上下方向(上方向を正)となるように回転させてみます。すると、JavaFX 3Dの座標系が、実は数学でおなじみの3次元の座標系と同じXYZ軸の組み合わせになっていることが分かります。



この座標系は、右手系座標系と言われています。右図のように右手の親指、人差し指、中指をそれぞれ直交になるように曲げて(フレミングの右手の法則の要領で)、親指をX軸、人差し指をY軸、中指をZ軸とし、指先方向を正に取る向きに座標軸を構成するのが右手系座標系となります。

ということで、JavaFXは2D座標系は数学座標系と異なるXY軸の構成でしたが、JavaFXの3Dの座標系は数学座標系と同じXYZ軸の構成になります。

ただし、Z軸が画面上の縦方向でないので、数学的な座標系に3次元物体を配置して画面に表示すると物体が倒れたような見え方となってしまいます。

配置する物体を、画面に合わせて回転させるというアプローチもありますが、3次元グラフィックスではカメラ位置(視点)を動かして見え方を変えるのが主流(常套)なようです。
そこで、カメラ位置(視点)を、Z軸を画面上の縦(上下)方向となるように、デフォルトの位置(視点)から変更する方法を探っていきます。

カメラの向き

JavaFX 3Dでは右の図のように、デフォルトのカメラの設定は、位置が原点にあり、カメラの方向はZ軸の正方向、カメラの上下はY軸方向でY軸の負方向が上となります。
図中の、原点付近の箱がカメラで、箱から出ている点線の矢印がカメラの視点方向、箱からNの矢印がカメラの上方向です。

数学的な座標系では、Z軸が上下方向となるので、デフォルトのカメラ設定は原点から真上を向いたような視点となってしまいます。

そこで、カメラの向きを数学的な座標系に合うように(Z軸の正方向が画面の上方向になるように)変更します。

右手系座標系の回転の方向について

右手系座標系では、座標変換で回転をする際の回転方向は、右の図のように回転の軸を、親指を立てたときの親指の方向が軸の正の方向となるように右手で握ります。その際に人差し指〜小指の指先が回転の正の方向となります。

これを覚えておけば、回転の際にどっちが正でどっちが負だったかなと悩むことが少なくなります。

カメラの向きをZ軸正方向が上になるようにする

カメラの向きを、まずZ軸正方向が上になるようにします。それには、右図のようにカメラをX軸を回転軸として-90度回転させます。角度の正負は、右手でX軸をつかみ、親指がX軸の正方向となるようすると、曲げた指の逆方向に90度回すことが分かるので、-90度を指定しています。

この回転で、カメラの上方向がZ軸の正方向、カメラの視線方向はY軸の正方向となります。

3次元の座標軸(X軸、Y軸、Z軸)とカメラの向きのデモプログラム

デフォルトの姿勢(位置だけ移動)のカメラで見た3次元の座標軸

JavaFX 3DのShape3D(Cylinder)を使って、X軸、Y軸、Z軸を表示するプログラムを作成しました。各軸は色で識別できるようにし、画面右上に凡例として色と軸の対比を記載しています。各軸の正方向には球を配置し、軸の向きが分かるようしています。カメラの向きがデフォルトで、位置をZ軸の負の方向に移動しています。

右の図のように、X軸(赤色)が画面の横方向(右が正)、Y軸(緑色)が画面の縦方向(下が正)、Z軸(青色)が画面の奥行方向となっているのが分かります。

カメラの設定は次です。

デモプログラムでは、任意の座標変換を引数に指定して3次元表示用のPerspectiveCameraを生成する共通メソッドを用意しました。
視野角(Field of View)を45度、カメラの対象距離を最遠400に設定しています。

    private PerspectiveCamera createCamera(Transform... args) {
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setFieldOfView(45.0);
        camera.setFarClip(400);
        camera.getTransforms().addAll(args);
        return camera;
    }

デフォルトの向きで、位置だけを移動するPerspectiveCameraは次のように生成します。それをSubSceneにセットします。

    subScene.setCamera(createCamera(new Translate(0, 0, -150)));
カメラの向きをZ軸正方向を上とした3次元の座標軸

X軸を回転軸としてカメラを-90度回転しました。
右の図のように、X軸(赤色)が画面の横方向(右が正)、Y軸(緑色)が画面の奥行方向、Z軸(青色)が画面の縦方向(上が正)と回転していることが分かります。

Z軸正方向が画面縦(上)の向きで、位置を移動するPerspectiveCameraは次のように生成します。それをSubSceneにセットします。

    Transform[] cameraTransforms = new Transform[] {
        new Rotate(-90, Rotate.X_AXIS),
        new Translate(0, 0, -150)
    };
   subScene.setCamera(createCamera(cameraTransforms));

複数のTransformを配列で設定すると、配列の最初のTransformから順次適用されていきます*1

Rotateで最初に90度回転したことで、カメラのローカルな座標系のZ軸は画面表示では縦方向(上が負)となるので、続くTranslateのZ座標の移動が画面表示では上に移動したようになります。

カメラの視点を変更する

カメラの視線を原点に向けたまま、カメラの位置を変えてみます。
位置は、右図のように方位(azimuth)と仰角(elevation)で指定します。

まず、Transform配列に、azimuthとelevationに対応する変換を追加します。あとから操作で角度を変えられるように、参照を保持しておきます。

    private final Rotate azimuthRotate = new Rotate(0, Rotate.Y_AXIS);
    private final Rotate elevationRotate = new Rotate(0, Rotate.X_AXIS);

    private final Transform[] cameraTransforms = new Transform[] {
        new Rotate(-90, Rotate.X_AXIS),
        azimuthRotate,
        elevationRotate,
        new Translate(0, 0, -150)
    };

ここで、azimuthの回転軸をY軸、elevationの回転軸をX軸としています。本来数学的に導出したかったのですが、ロジックが確立できなかったので、試行錯誤の結果から導出したものです。

azimuthRotateとelevationRotateのそれぞれの角度を、画面のスライダーの値と結び付けて、画面操作でカメラ位置を変更できるようにします。

    @FXML
    private Slider azimuthSlider;
    @FXML
    private Slider elevationSlider;

    :
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        azimuthRotate.angleProperty().bind(azimuthSlider.valueProperty());
        elevationRotate.angleProperty().bind(elevationSlider.valueProperty());

方位(azimuth)0度の指定時の位置、仰角(elevation)の正負がずれていますが、右の画面のようにスライダーの操作でカメラ位置が移動していきます。

*1:このあたりの詳しい説明がなかなか見当たらず、振る舞いから整理したところの説明です。