読者です 読者をやめる 読者になる 読者になる

巻物プレゼン

JavaFX

7/22 に JJUG のナイトセミナ Inside Lambda で「Project Lambda の基礎」というタイトルでプレゼンをしてきました。

内容は SlideShare で見てもらうとして、今回は私は前座で、とりは宮川さんの Lambda の内部構造。なので、今回はちょっと遊ばせてもらいました。

何を遊んだかというと、プレゼンの資料です ^ ^;;

この講演の前に、映画の Short Peace で大友克洋が巻物風のアニメーションをやっているということをテレビで見たのです。絵コンテも巻物ということで、すごい横長。これはおもしろいなぁと思ったわけです。

で、プレゼンでもやってみたくなってしまったわけです、巻物を。

でも、さすがに下の絵のように左から右へと動くわけにはいきません。というのも、そうすると縦書きにしなくては行けないからです。

かといって、上から下に動かすと巻物というより、掛け軸かすだれみたいになってしまいます。

ということで、下から上に動かすようにしてみました。

動きが決まったら、それをどうやって JavaFX で実現させるかです。

いちおう、3D でほんとに巻物を実現することも考えたのですが、すぐに止めました >< だって、そんなアニメーション作るの大変なんだもん。

では、どうやったかというと、簡単です。

手前に円筒を配置して、紙送りするときには、円筒を回転させるアニメーションをさせます。そして、円筒の後ろに平面のノードを配置して、円筒の回転と一緒に上に移動させるアニメーションを行います。

ただし、それだけだと円筒よりも下の部分が見えてしまって、円筒とノードが別々だということが分かってしまうので、円筒の中心より下は見えないようにクリッピングしてしまいます。

絵で描くと、こんな感じ。

ちなみに、下に配置するノードはほんとの巻物ではないので、長くする必要はないです。その代わりに、紙送りをするときに、今表示しているノードと次に表示するノードをピッタリくっつけて、移動させます。

では、これを JavaFX で書いてみます。

まず、円筒。JavaFX 8 だと、円筒やキューブのようなプリミティブな 3D のオブジェクトを表すクラスが追加されています。

円筒は javafx.scene.shape.Cylinder クラスです。

Cylinder クラスをそのまま描画させると円筒が立って表示されます。そのため、回転を行って水平方向に表示させたいのです。ところが、アニメーションで RotateTransition クラスを使いたいため、回転に rotate プロパティは使えません。というのも RotateTransition クラスが行うアニメーションは rotate プロパティを使用して回転を行わせているからです。

そのため、javafx.scene.transfom.Rotate クラスを使用して、回転を表現し、それをノードにセットするようにしました。

また、画面下方に表示させるために移動も行うのですが、アニメーションでは回転のみなので、こちらは setTranslateX/setTranslateY メソッドで行っています。

なお、ここでは表示サイズを 1024x768 と想定して記述しています。

        cylinder = new Cylinder(40, 1000.0);
        Rotate rotate = Transform.rotate(90, 0, 0);
        cylinder.getTransforms().add(rotate);
        cylinder.setTranslateX(510);
        cylinder.setTranslateY(740);

これをシーングラフに追加すればいいのですが、これだけだと円筒のようには見えません。というのも、JavaFX のデフォルトでは平行投影されているため、円筒が傾いていない限り、単なる四角に見えてしまうためです。

ちゃんと円筒のように膨らみを持たせるには、一点透視にする必要があります。これを行っているのが javafx.scene.Camera クラスです。

Camera クラスのサブクラスには平行投影する ParallelCamera クラスと、一点透視を行う PerspectiveCamera クラスがあります。デフォルトでは ParallelCamera クラスが使われているので、PerspectiveCamera クラスに切り替えます。

        Scene scene = new Scene(root, constants.getWidth(), constants.getHeight());
        scene.setCamera(new PerspectiveCamera());

さて、ここまでで実行してみます。

ちゃんと円筒と分かりますね。しかし、白い円筒ではちょっとあじけない。

そこで、テクスチャーマッピングという手法を使用します。簡単にいえば、イメージを描画オブジェクトの表面に貼ってしまうと手法です。

適当にフリーのイメージで巻物っぽい絵を探してきて、それを貼ってみました。

        PhongMaterial mat = new PhongMaterial();
        Image diffuseMap = new Image(getClass().getResource("images.jpg").toString());
        mat.setDiffuseMap(diffuseMap);
        cylinder.setMaterial(mat);

テクスチャーマッピングで貼るものは javafx.scene.paint.PhongMaterial クラスで表します。そこにイメージをセットして、Cylinder オブジェクトに setMatrial メソッドでセットします。

これでテクスチャーマッピングのできあがりです。実行してみると、こんな感じ。

それっぽくなってきました。

後は後ろにノードを配置させて動かすだけです。これは今までのプレゼンツールの機能を使っています。

ナイトセミナでは SVG を使ったのですが、ここでは FXML で書いてます。単に上に動かすアニメーションです。

そして、それと同時に円筒もアニメーションさせます。

でも、ただ円筒を回転させるのもつまらないし、いくらたっても減らない魔法の円筒になってしまいます。そこで、回転するアニメーションと一緒に、円筒の半径を減らしていくというアニメーションを同時に行っています。

また、半径が減れば、同じ量の紙送りするにはより回転させる必要があるので、回転量もだんだんと増えるようにしてみました。

    public void roll() {
        angle *= 1.1;
        
        RotateTransition trans = new RotateTransition(Duration.millis(2_000));
        trans.setAxis(new Point3D(1.0, 0.0, 0.0));
        trans.setNode(cylinder);
        trans.setByAngle(angle);
        trans.setInterpolator(Interpolator.LINEAR);
        trans.play();
        
        Timeline timeline = new Timeline(
            new KeyFrame(Duration.millis(2_000),
                         new KeyValue(cylinder.radiusProperty(), cylinder.getRadius()*.95))
        );
        timeline.play();
    }

ParallelTransition クラスは使っていないのですが、一緒に動かせばだいたい同時にアニメーションしてくれます。そんなに厳密なものではないので、これで十分。

忘れてましたけど、デフォルトだと回転軸は z 軸方向になっているので、これを x 軸方向にしておいてやる必要があります。

という部分をプレゼンツールにくっつけて、プレゼンしたのでした。

ただ、今までは 1 枚のページの手前になにか表示するという発想がなかったので、やっつけ感あふれる実装になっています。

もし、今後もこういうようにページの手前に表示する必要が出てきたら、ちゃんとした実装にします ^ ^;;;

ここで使ったものは GitHub にアップしてあります。いつも使っているプレゼンツールのサブセットだと思ってください。

makimono
https://github.com/skrb/makimono