巻物プレゼン
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