2014年 12月

JavaFX の MeshView を試してみる その2

JavaFX

このエントリーは JavaFX の MeshView を試してみる の続きです。

今回は立方体を表示させるプログラムを組んでみます。

立方体だからといって複雑になり難しくなることはありません。

しょせん、最小構成のポリゴン(  TriangleMesh ) の集まりです。

立方体の頂点の座標は下図のようにしました。

box

 

この立方体に貼るテクスチャは次のものを使います。

duke_uv

 

これを先ほの立方体へ貼ります。

解りやすくするためにテクスチャの座標とインデックスをいれた画像を用意しました。

t_map

 

前回のエントリーをお読みになった方は下記のソースコードを見たら何をやっているか理解できるでしょう。

今回はちょっとピッキング API でお遊びをいれています。

ピッキング API で取得した情報を標準出力に表示させたり、

オブジェクトの拡大縮小をやっています。( この部分は JavaFX の標準機能だけでシンプルな 3D トイピアノをつくろう を参考にしてください。)

scene.setOnMouseClicked(event -> {
    PickResult pickResult = event.getPickResult();        
    System.out.println(“PickResult Node : ” + pickResult.getIntersectedNode());
    System.out.println(“PickResult Face : ” + pickResult.getIntersectedFace());
    System.out.println(“PickResult TexCoord : ” + pickResult.getIntersectedTexCoord());
    System.out.println(“PickResult Distance : ” + pickResult.getIntersectedDistance());          
    if (pickResult.getIntersectedNode() == meshBox) {
        Point3D point3d = pickResult.getIntersectedPoint();
        if (point3d.getX() != 0.0d && point3d.getY() != 0.0d && point3d.getZ() != 0.0d) {
            meshBox.setScaleX(abs(point3d.getX() / 100.0d) * 1.8d);
            meshBox.setScaleY(abs(point3d.getY() / 100.0d) * 1.8d);
            meshBox.setScaleZ(abs(point3d.getZ() / 100.0d) * 1.8d);
        } else {
            meshBox.setScaleX(0.5d);
            meshBox.setScaleY(0.5d);
            meshBox.setScaleZ(0.5d);
        }
    } else {
        meshBox.setScaleX(1.0d);
        meshBox.setScaleY(1.0d);
        meshBox.setScaleZ(1.0d);
    }
});

これによって得られる情報は下記のようになります。

PickResult Node : MeshView@7bc4dd1a
PickResult Face : 10
PickResult TexCoord : Point2D [x = 0.4647918545001403, y = 0.5826962887300013]
PickResult Distance : 675.187084447299

最後の PickResult Distance はカメラからの距離となります。

ついでに 3D オブジェクトの情報もとってみました。

System.out.println(“PointElementSize : ” + this.getPointElementSize());
System.out.println(“TexCoordElementSize : ” + this.getTexCoordElementSize());
System.out.println(“FaceElementSize : ” + this.getFaceElementSize());

プログラムの実行結果で得られた値は下記のようになります。

PointElementSize : 3
TexCoordElementSize : 2
FaceElementSize : 6

これらは上から 3, 2, 6 といつも同じ数値を返すようです。

私はてっきり各配列の要素数を返すものだと思い込んでいたので少しばかり悩んで時間を無駄にしてしまいました。

これらの存在意義が解らないので未だにすっきりしていないし、間違って解釈している可能性もあるので霧の中にいるような感覚が今もあります。

あと、今回は複数のフェイスで 3D オブジェクトが構築されているので次のメソッドを使用します。

javafx.scene.shape.TriangleMesh クラスの ObservableIntegerArray getFaceSmoothingGroups() メソッドです。

これを使いフェイスをグルーピングしてスムーズに表示させることが可能となるようです。

this.getFaceSmoothingGroups().addAll(
        0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5
);

上記コードは、triangle A-C-B と triangle C-D-B をグルーピング ( 0 )

triangle E-F-G と G-F-H ( 1 )、triangle A-B-E と triangle E-B-F ( 2 ) … という具合にグルーピングしていきます。

最終的にはフェイスの数と faceSmoothingGroups の配列の要素数は等しくなります。

これを間違えてもコンパイルエラーにはなりませんし、プログラムもエラーで止まることなく実行されてしまいます。

ただ肝心の 3D オブジェクトは表示されることは無いでしょう。

あっ、空だと表示されますね。(当たり前か)

さて、この機能なんですが本当に効果があるのでしょうか?

私の環境では違いを確認できませんでした。

このような小さなプログラムでは効果が確認できないのかもしれません。

最後にこのプログラムの動画を貼っておきますので興味のある方はご覧くださいませ。

 

JavaFX 楽しすぎるから年末の TODO が減らない! どうしよう(>_<。)

Hatena タグ:

F(X)yz を SceneBuilder 2.0 で使ってみた。

JavaFX

JavaFX の 3D ライブラリとして現在活発に開発が進められている F(X)yz が SceneBuilder 2.0 で使用できることを José Pereda さんから教えてもらったので早速少しだけ試してみました。

まず、https://github.com/Birdasaur/FXyz からライブラリをダウンロードします。

それから SceneBuilder 2.0 にダウンロードしたライブラリをインポートします。

Library の右側に小さくある歯車のボタンをクリックして出てくるプルダウンメニューより Import JAR/FXML File… を選択します。

1

 

すると Import Dialog ウィンドウが表示されます。

必要なものだけ選んで Import Components ボタンをクリックします。

2

 

12月23日 午後7時05分追記

Git からダウンロードしたものだと上手のように最新の 3D ライブラリは含まれていません。

必ずプロジェクトをビルドしてからライブラリを追加しましょう。

fx

 

無事にインポートされたら Custom の項目に追加されたのが確認できます。

3

 

ちゃんとビルドしたライブラリをインポートすれば多くの 3D ライブラリが追加されます。

fx2

 

実際に Mesh オブジェクトを表示させるだけのプログラムを組むために下図のように配置及び変形させてみた。

4

 

適当に色を付けて表示させただけのプログラムの実行結果は下図のようになります。

5

SceneBuilder を使っての 3D アプリケーションの正しい開発方法を未だに情報収集できてないので細かいことは良く解らないのでただ使えるじゃんで終わりです。

ちなみにテクスチャを狙い通りに貼る方法は解りませんでした。

最初に述べたように開発が積極的に行われているようなので今後いろんな 3D オブジェクトが追加されるだろうと期待しています。

最近ではクロスの 3D アニメーションなんかは凄いインパクトがありました。

12月23日 午後9時 追記

クロスとカーブスプリング、スプリングも表示させるだけのプログラムを組んで確認しました。

クロスにはテクスチャを貼ることができました。

fx_a

 

fx_b

 

きっと大きな幸せを呼んでくれるライブラリになると期待してます。(^_^)

Hatena タグ:

JavaFX の MeshView を試してみる

JavaFX

JavaFX 3D API の MeshView は使うことは無いだろうと思いノータッチでいました。

いつか暇な時にでも軽くどんなものなのか見ておこうとずっとほったらかしにして現在に至ってます。

その理由は実際に自分でポリゴンやテクスチャを定義するなんて大変なことはしない。

モデルインポーターを使うのが常套手段だろうと考えてました。

今でもその考えは変わっていませんが少しだけでもどんなものなのか試してみます。

それでは最小構成で頂点をみっつ持つ正三角形を表示させてみることにしました。

いつものようにネット上で情報を拾い集めてあーでもないこーでもないと API ドキュメントを眺めながらやっています。

英語が駄目なので API ドキュメントを眺めていると表現しました。読めません。(>_<。)

よって、いつものように間違いがあるかもしれませんのでご了承くださいませ。

 

このプログラムの実行結果は次の動画のようになります。

 

MeshView オブジェクトを生成するところ以外の部分は下記エントリーを参考にしてください。

JavaFX の標準機能だけでシンプルな 3D トイピアノをつくろう

javafx.scene.shape.MeshView クラスは 3D オブジェクトを生成をするクラスです。

javafx.scene.shape.TriangleMesh クラスのインスタンスを MeshView のコンストラクタに与えて生成します。

TriangleMesh クラスでは頂点、テクスチャ、フェイスなどが定義されます。

先ほどのプログラムのコードで MeshView クラスのインスタンス生成時のコンストラクタの引数の TriangleMesh の拡張クラスをみてみましょう。

ObservableFloatArray getPoints() メソッドで頂点情報を取得して

void addAll(float… elements) メソッドで頂点の位置情報を追加しています。

ObservableFloatArray getTexCoords() メソッドでテクスチャ情報を取得して

void addAll(float… elements) メソッドでテクスチャ位置情報を追加してます。

ちなみにテクスチャの左上は [ 0, 0 ] で右下は [ 1, 1 ] となります。

ちなみにこのプログラムではテクスチャは下図のように使っています。

duke2

ObservableFaceArray getFaces() メソッドでフェイス情報を取得して

void addAll(float… elements) メソッドで頂点に対してテクスチャをマッピングしたフェイスを追加します。

これらは全て配列として扱われています。

このような小さな 3D オブジェクトなら何とかなるでしょうけど複雑なものはお手上げですね。

やっていることは非常にシンプルです。

さて、このシンプルなプログラムの実行結果の動画を見て裏側が何も表示されないのは何故?と思われた方もいらっしゃるでしょう。

その秘密はエコです!

つまり、見えない所はわざわざ高い電気代使って計算しなくてもいいだろうと言う節約です。(^_^;)

本当の所はコンピュータリソースの節約でパフォーマンスアップを狙ったりするんでしょう。

デフォルトのカリングを確認してみます。

次のコードを追加して実行してみます。

    System.out.println(“CullFace : ” + myMesh.getCullFace());

実行結果は次のようになります。

CullFace : BACK

つまり裏側は描写しない設定です。

それではカリングの設定を無しにしてどうなるか確認してみましょう。

次のコードを追加して実行してみます。

    myMesh.setCullFace(CullFace.NONE);

プログラムの実行結果は次の動画のようになります。

 

裏側に表側のテクスチャ画像が暗いグレー色の中に透き通って見えるよう描写されています。

それではカリングの設定をフロントにして確認してみましょう。

次のコードのようにカリングの設定を変更してみます。

    myMesh.setCullFace(CullFace.FRONT);

プログラムの実行結果は次の動画のようになります。

 

フロント側は描写されていません。

裏側は先ほどのカリングの設定が無い場合の表示となっています。

ポリゴンの裏側の描写をしない(デフォルト設定)くらいしか使い道を思いつきませんがいろいろできそうですね。

さて、カリングの設定の確認を先にしてしまって話しが前後していまいましたが

ポリゴンに思い通りにテクスチャを貼るにはどうしたらいいのか確認します。

カリングの設定をデフォルトに戻して本題にうつります。

このプログラムは最小構成のポリゴン( TriangleMesh )なので頂点はみっつです。

正三角形の上の頂点は原点(0f, 0f, 0f)に位置してます。

左下の頂点は -100f, (float) (100 * Math.sqrt(3.0d)), 0f の位置

そして右下の頂点は 100f, (float) (100 * Math.sqrt(3.0d)), 0f の位置です。

    0, 0, 0,         // Top             idx 0
    -w / 2, h, 0,  // Bottom Left   idx 1
    w / 2, h, 0    // Bottom Right idx 2

これらは配列として格納されているので上からインデックスは、0, 1, 2 となります。

次にテクスチャを確認します。
テクスチャは左上が [ 0, 0 ] 、右下が [ 1, 1 ] となるから次のように設定しました。

    1 / 2f, 0,                          // idx 0
    0, (float) Math.sqrt(3) / 2,  // idx 1
    1, (float) Math.sqrt(3) / 2   // idx 2

これも配列として格納されるので上からインデックスは、0, 1, 2 となります。

これらを利用してポリゴンにテクスチャを貼っていきます。

    this.getFaces().addAll(
        0, 0, 1, 1, 2, 2
    );

p0, t0, p1, t1, p3, t3 と頂点座標に対してテクスチャ座標をマッピングしていくだけです。

このテクスチャマッピングを利用して少し遊んでみます。

テクスチャ画像を裏返しで貼ることはできるでしょうか?

試してみました。

次のようにテクスチャマッピングを変更してみました。

    this.getFaces().addAll(
        0, 0, 1, 2, 2, 1
    );

プログラムの実行結果は次の動画のように裏返しで表示されました。(^_^)

 

次は裏側にテクスチャを貼ってみます。

次のようにテクスチャマッピングを変更してみます。

    this.getFaces().addAll(
        0, 0, 2, 1, 1, 2
    );

プログラムの実行結果は次の動画のようになります。

 

裏側に貼られています。

ん? 表側が表示されてなかった。

あれれ?カリングによって裏側は表示されないはずですよね。

つまり、頂点のインデックスを反時計回りから時計回りとしたことで表と裏が逆になっただけということ。

もう少しあそんでみます。

裏側に裏返しのテクスチャを貼ってみます。

簡単ですね。次のようにテクスチャマッピングを変更します。

   this.getFaces().addAll(
        0, 0, 2, 2, 1, 1
    );

プログラムの実行結果は次の動画のようになります。

 

これも先ほどと同じく頂点インデックスが時計回りとなったことを利用しただけです。

さて、ここで表と裏に同じテクスチャを貼るにはどうすればいいでしょうか?

残念ながら JavaFX 3D API には boolean backFaceNormalFlip() メソッドは無いようです。

でも、安心してください。答えは既に出ています。

次のようにテクスチャマッピングを変更してみます。

    this.getFaces().addAll(
        0, 0, 1, 2, 2, 1,
        0, 0, 2, 1, 1, 2
    );

プログラムの実行結果は次のようになります。

 

表と裏にちゃんとテクスチャ画像が表示されました。(^_^)

この方法が良いか悪いかは解りません。

ひょっとしたらもっとスマートは方法があるかもしれません。

最後に裏側の画像を表裏ひっくり返したものにしたい場合は次のようにテクスチャマッピングを変更してみます。

    this.getFaces().addAll(
        0, 0, 1, 2, 2, 1,
        0, 0, 2, 2, 1, 1
    );

プログラムの実行結果は次の動画のようになります。

 

最小構成での確認はこれでおしまいです。

時間があれば立方体でも試してみたいと思います。

JavaFX 楽しくて時間がどんどん溶けていく(>_<。)

Hatena タグ:

JavaFX の標準機能だけでシンプルな 3D トイピアノをつくろう

JavaFX

このエントリーは、JavaFX Advent Calendar 2014, 11日目です。

昨日は @toruwest さんの「JavaFXのTreeViewでアニメーションしてみる」でした。

明日は @skrb さんの「Java Advent Calendarと一緒になにか書きます」です。

今年も残り少なくなってきていろいろと忙しくなってきました。

そんな今日この頃、何故か「猫ふんじゃた」のメロディがマイブームとなってきました。

忙しいのなら猫の手も借りたい状況なのに踏んじゃったらいけないのにね!

と言うわけで「猫ふんじゃった」を弾きたくなって JavaFX で ToyPiano アプリケーションを作ることにしました。

JavaFX のサンプルに Xylophone という 3D アプリケーションがあります。

それを参考に JavaFX 8 の標準機能だけを使って作ってみます。

今回作るのはトイピアノなので和音にも対応させるためにタッチインタフェースを利用します。

よって動作可能なのはタッチパネル対応の OS 限定となってしまいます。

もちろん、タッチパネルは必須です。(^^;

せっかく 3D アプリケーションにするのだから鍵盤もそれなりに動かしてみます。

全てを書こうとすると凄く長くなってしまいますので JavaFX 3D API だけを取り上げます。

タッチインタフェースやアニメーションタイマーの使用については割愛させていただきます。

と言っても他の部分は特に難しいことはしてないので!

完成版は次の動画のようになります。

 

それでは、さらっと JavaFX 3D API について調べてみましょう。

2D と 3D の違いは何か?

おおざっぱに言うと 2D の座標に Z 軸が追加され、ライトやカメラを意識しなければならなくなった。

その代わりに表現力が豊になった。

本当におおざっぱですがそんなもんです。

では、JavaFX ではじめから用意されている 3D オブジェクト(Primitive Shapes)を表示させるシンプルなプログラムを見てみましょう。

JavaFX では三つの 3D オブジェクトがはじめから javafx.scene.shape パッケージに定義されています。

Box

Cylinder

Sphere

上記のプログラムはそれらを下図のように表示させています。

1

 

これら以外は自分で TriangleMesh / MeshView を駆使して 3D オブジェクトを作り上げるか、モデルインポーターを使用するかしなければならないでしょう。

さて、このプログラムはどのように理解すればいいのでしょうか?

順番にみていきましょう。

    final Box box = new Box(100.0d, 100.0d, 100.0d);

これは、辺の長さが横、縦、奥行きそれぞれ 100 の長さの立方体のオブジェクトを作っています。

    final Cylinder cylinder = new Cylinder(50.0d, 100.0d);

こちらは、半径 50、高さ 100 のシリンダーオブジェクトを作ってます。

    final Sphere sphere = new Sphere(50.0d);

最後に半径 50 の球体オブジェクトを作ってます。

はじめから定義されているのでお手軽に作れます。

これら 3D オブジェクトはこのままでは全て座標原点を中心に配置され重なってしまいます。

なので次のように移動させています。

    // box を左へ (-200) 移動 (X 軸)
        box.setTranslateX(-200.0d);
       
        // cylinder を右へ (200) 移動 (X 軸) そして上へ (-100) 移動 (Y 軸)
        cylinder.setTranslateX(200.0d);
        cylinder.setTranslateY(-100.0d);

javafx.scene.Node クラスの setTranslateX(double value), setTranslateY(double value), setTranslateZ(double value) と言う便利な移動メソッドがあります。

それぞれ X 軸、Y 軸、Z 軸方向へ移動できます。

ここで注意が必要なのは Y 軸 です。

最初にも説明したように JavaFX の 3D 座標は 2D のものに Z 軸が加えられたものなので上方向に移動させる場合は、

setTranslateY(double value) の引数の値は負の値になります。

次はカメラについて見てみます。

カメラには PerspectiveCamera (透視投影カメラ) と ParallelCamera (直投影カメラ) の二種類があります。

今回は PerspectiveCamera を使用します。

        // 透視投影カメラ
        final PerspectiveCamera cam = new PerspectiveCamera(true);
        // Field of View
        cam.setFieldOfView(45.5d);
        // Clipping Planes
        cam.setNearClip(1.0d);
        cam.setFarClip(1_000_000.0d);

PerspectiveCamera cam = new PerspectiveCamera(true); の引数は fixedEyeAtZeroプロパティで true に設定することによって

カメラの位置が原点に配置されます。

ちなみにこの場合の原点の位置は画面のど真ん中です。

これを true に設定することによって容易にカメラを動かすことが可能となります。

カメラを原点位置に生成することは上記のように簡単にできます。

次にカメラの写り具合を設定します。

視野角の設定は以下のようにします。

   cam.setFieldOfView(45.5d);

引数の値、視野角 45.5 度で設定しました。何処かで OpenGL の記事か何かでこれくらいが良いと見たような記憶があるので(^^;

次にカメラからどれくらいの距離を写すのか設定します。

カメラからこれだけ離れていれば写すよってのが

    cam.setNearClip(1.0d);

で、引数の値 1.0 で設定されてます。これ以上近い物は写りません。

カメラからどれだけ離れているところまで写すかは

    cam.setFarClip(1_000_000.0d);

引数の値、1_000_000 まで写ります。

これ以上遠くは写りません。

これら設定はケースバイケースかお好みで!

カメラを配置、設定したのはいいけどこれでは原点配置なので sphere のど真ん中なので残念な状態です。

そこでカメラを移動させてみます。

Z 軸手前方向にカメラを移動させます。

        // カメラを 500 後退させる。(Z 軸を -500)
        cam.setTranslateZ(-500.0d);

これで原点から 500 Z 軸方向に後退した位置にカメラを配置できました。

あとはこのカメラを Scene にセットすればいいだけです。

        scene.setCamera(cam);

このプログラムの実行結果から原点配置の状態でカメラのレンズは Z 軸、正の方向に向いていることが確認できました。

これで Primitive Shapes の生成と移動、PerspectiveCamera の生成と設定、移動、撮影ができるようになりました。

ここで ParallelCamera (直投影カメラ) を使うとどうなるか試してみましょう。

先ほどのカメラの定義とカメラの移動を次のように変更します。

        // 直投影カメラ
        final ParallelCamera cam = new ParallelCamera();

        // Field of View ありません!
        // cam.setFieldOfView(45.5d);
       
        // Clipping Planes
        cam.setNearClip(1.0d);
        cam.setFarClip(1_000_000.0d);

ParallelCamera (直投影カメラ)のコンストラクタに fixedEyeAtZeroプロパティを設定する引数はありません。

Field of View も設定できません。

直投影カメラの性質上必要ないのでしょう。

では、ParallelCamera (直投影カメラ)を使った場合プログラムの実行結果がどうなるのか確認してみます。

1a

Primitive Shapes の Sphere の中心が左上の角の部分( 2D の原点)となるように表示されています。

Sphere と ParallelCamera の位置情報を下記コードを書き加えて調べてみます。

        System.out.println(“Sphere の位置情報 ” + sphere.getBoundsInParent());
        System.out.println(“ParallelCameraの位置情報 ” + cam.getBoundsInParent());

実行結果で得られた値は次のようになります。

Sphere の位置情報 BoundingBox [minX:-50.0, minY:-50.0, minZ:-50.0, width:100.0, height:100.0, depth:100.0, maxX:50.0, maxY:50.0, maxZ:50.0]
ParallelCameraの位置情報 BoundingBox [minX:0.0, minY:0.0, minZ:0.0, width:0.0, height:0.0, depth:0.0, maxX:0.0, maxY:0.0, maxZ:0.0]

3D オブジェクトとカメラの座標の原点は同一ではないことが解ります。

デフォルトの 2D カメラってことですね。

Sphere と Cylinder と Box を移動させてみます。

        sphere.setTranslateX(scene.widthProperty().divide(2).doubleValue());    
        sphere.setTranslateY(scene.getHeight() / 2.0d);

        cylinder.setTranslateX(scene.widthProperty().divide(2).doubleValue() + 200.0d);
        cylinder.setTranslateY(scene.getHeight() / 2.0d);
       
        box.setTranslateX(scene.widthProperty().divide(2).doubleValue() – 200.0d);
        box.setTranslateY(scene.getHeight() / 2.0d – 100.0d);

プログラムの実行結果は下図のようになります。

1b

Sphere の位置情報 BoundingBox [minX:270.0, minY:130.0, minZ:-50.0, width:100.0, height:100.0, depth:100.0, maxX:370.0, maxY:230.0, maxZ:50.0]
ParallelCameraの位置情報 BoundingBox [minX:0.0, minY:0.0, minZ:0.0, width:0.0, height:0.0, depth:0.0, maxX:0.0, maxY:0.0, maxZ:0.0]

PerspectiveCamera (透視投影カメラ)とは違いすべてのオブジェクトが並行に表示されてます。

距離感というか奥行きが全然解らないですね。

ParallelCamera (直投影カメラ)も試したことだし次へ進みましょう。

3D オブジェクト(Primitive Shapes)を表示させることはできるようになったけど、

でも白黒なんていつの時代だ?っていうのはちょっとね・・・

とりあえず cylinder に色をつけてみましょう。

先ほどのプログラムで Primitive Shapes を生成したあとに下記コードを追加します。

        // フォンシェーディングを設定
        final PhongMaterial cylinderMaterial = new PhongMaterial();
        // 拡散光による色の設定
        cylinderMaterial.setDiffuseColor(Color.RED);
        // スペキュラカラー(反射光の色)の設定
        cylinderMaterial.setSpecularColor(Color.MAGENTA);
        // マテリアルを設定
        cylinder.setMaterial(cylinderMaterial);
        // ドローモードを設定
        cylinder.setDrawMode(DrawMode.FILL);

上記コードのコメントにあるように、フォンマテリアルオブジェクトを生成し、拡散光による色の設定、反射光による色の設定をします。

そしてそのマテリアルを cylinder オブジェクトに設定しています。

ドローモードも LINE とか設定できますが FILL を設定しています。

LINE はワイヤーモデルの状態です。

上記のようにマテリアルの設定は javafx.scene.paint.PhongMaterial クラスの setチョメチョメ() メソッドで設定します。

チョメチョメは何を設定するか解るように記述されています。(ごめんなさい。変な表現で)

ドローモードの設定は javafx.scene.shape.Shape3D クラスの setDrawMode(DrawMode value) です。

プログラムの実行結果は下図のようになります。

2

ちょっとこのままでは色のついてない残り二つが寂しのでそれらにも色をつけてみました。

ここで Sphere だけスペキュラパワーを設定してみました。

デフォルト値は 32.0d なので半分の 16.0d で設定しました。

        // フォンシェーディングを設定
        final PhongMaterial boxMaterial = new PhongMaterial();
        // 拡散光による色の設定
        boxMaterial.setDiffuseColor(Color.GREEN);
        // スペキュラカラー(反射光の色)の設定
        boxMaterial.setSpecularColor(Color.LIGHTGREEN);
        // マテリアルを設定
        box.setMaterial(boxMaterial);
        // ドローモードを設定
        box.setDrawMode(DrawMode.FILL);
       
        // フォンシェーディングを設定
        final PhongMaterial sphereMaterial = new PhongMaterial();
        // 拡散光による色の設定
        sphereMaterial.setDiffuseColor(Color.BLUE);
        // スペキュラカラー(反射光の色)の設定
        sphereMaterial.setSpecularColor(Color.LIGHTCYAN);
        // スペキュラパワーを設定
        sphereMaterial.setSpecularPower(16.0d);      
        // マテリアルを設定
        sphere.setMaterial(sphereMaterial);
        // ドローモードを設定
        sphere.setDrawMode(DrawMode.FILL);

このプログラムの実行結果は下図のようになります。

3

3D オブジェクトに色も付けることができたし次はライトを設定してみましょう。

ライトはアンビエントライトとポイントライトの二種類が用意されています。

アンビエントライトは環境光( 光源からの光が直接当たっていない部分を照らす光、物理的には光を受けている面からの散乱光である )のことです。

ポイントライトは点光源です。点光源を置いた位置から全方向に光りが伸びていきます。

その光りは距離が離れるにしたがって弱くなっていきます。

それでは先ほどのプログラムにこれら二つのライトを配置してみましょう。

        // アンビエントライト
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(Color.rgb(184, 134, 11, 0.5));
       
        // ポイントライト
        PointLight point = new PointLight();
        point.setColor(Color.GHOSTWHITE);
       
        // ポイントライトを移動
        point.setTranslateX(-800.0d);
        point.setTranslateY(-300.0d);
        point.setTranslateZ(-800.0d);

        root.getChildren().addAll(box, cylinder, sphere, ambient, point);

アンビエントライトとポイントライトを上記コードのように javafx.scene.LightBase クラスの設定メソッドを使い設定します。

それぞれの色を設定しているだけで特に難しいことはしなくていいようです。

ただポイントライトの配置が原点になってしまうのでポイントライトを移動させています。

移動させないと Sphere の中心位置に配置されるので Sphere が表示されなくなります。

これら二つのライトを配置したプログラムの実行結果は下図のようになります。

4

さて、ライトって一つだけしか配置できないのでしょうか?

ちょっと試してみましょう。

        // ポイントライト 2
        PointLight point_2 = new PointLight();
        point_2.setColor(Color.YELLOW);
       
        // ポイントライト 2 を移動
        point_2.setTranslateX(800.0d);
        point_2.setTranslateY(-300.0d);
        point_2.setTranslateZ(-800.0d);

        root.getChildren().addAll(box, cylinder, sphere, ambient, point, point_2);

プログラムの実行結果は下図のようになりました。

5

複数のライトが配置できるようです。

でも、特定の 3D オブジェクトにだけライトを当てるにはどうすればいいのでしょうか?

これも簡単に設定できます。

それでは先ほど追加したポイントライト 2 を Sphere だけに当てるようにしてみます。

下記コードのようにスコープを設定するだけです。

        point.getScope().add(sphere);

それでは確認のためにプログラムを実行してみます。

6

期待通りに Sphere だけポイントライト 2 が当たっています。

ライトを駆使すれば綺麗な絵を得ることができますね!

次は 3D オブジェクトを回転させてみましょう。

左に表示されている Box を左奥の辺を中心位置として水平に45度右回転させます。

次のコードを先ほどのプログラムに書き加えてください。

        // Box を水平45度右回転させる。回転の中心( ピボット )は左奥の辺とする
        box.getTransforms().setAll(new Rotate(45.0d, -50.0d, 0.0d, 50.0d, Rotate.Y_AXIS));

実行結果は下図のようになります。

7

javafx.scene.transform.Rotate インスタンス生成時に第一引数 回転角、第二引数、第三引数、第四引数はピボットの位置を X, Y ,Z 軸として設定します。

第五引数は回転軸を設定します。

それらを javafx.scene.Node クラスの getTransforms() で Box のトランスフォームオブジェクトを取得し setAll() メソッドで設定しています。

今回は setAll() メソッドを使いましたが次のような方法もあります。

    box.getTransforms().addAll(new Rotate(45.0d, -50.0d, 0.0d, 50.0d, Rotate.Y_AXIS));

 

これは今回目標とする ToyPiano アプリケーションで使うと問題が出るので前述の setAll() メソッド を使用します。

その問題は何かと言うとタッチプレスされたら鍵盤をある角度だけピボットを設定されたところから回転させ、

タッチリリースされたら動かした分だけ戻すという処理を add() メソッドを使って実装すると想定していた角度より大きく( 2倍 )動いてしまうことがあります。

何気に動かして動かした分戻したらいいやってのは駄目なこともあるんです。

このようなことを避けるため setAll() メソッドを使います。

これで 3D オブジェクトの移動、回転、そしてカメラとライトの設定ができるようになりました。

さて、これまでの実行結果を見て気が付いた方もいらっしゃると思うのですが 3D オブジェクトにアンチエイリアスが効いてないようです。

デフォルトではアンチエイリアスは無効となっているようなのでこれを有効にしてみましょう。

プログラムの Scene を生成するコードを次のように変更します。

        Scene scene = new Scene(root, 640.0d, 360.0d, true, SceneAntialiasing.BALANCED);
       
        scene.setFill(Color.BLACK);

何故かアンチエイリアスの設定をとる引数を使うと背景色の設定ができなくなります。

そのため別に背景色を設定するコードを付け加えました。

実行結果は下図のようになり今までとは違いエッジが滑らかに表示されます。

8

 

次に 3D オブジェクトの拡大やテクスチャの貼り付けなども説明したいところですが今回の ToyPiano アプリケーションでは使用していないので割愛させていただきます。

と言いつつ・・・簡単にどのようにするのか紹介します。

興味の無い方はこの部分は読み飛ばしてくださいませ。

ではよく見かけるサンプルを JavaFX で試してみましょう。

テクスチャを貼るには javafx.scene.paint.PhongMaterial クラスを使用します。

PhongMaterial インスタンスを生成するときにコンストラクタの引数として渡すか、

各種イメージを javafx.scene.paint.PhongMaterial クラスの設定メソッド

    setDiffuseMap(Image value)

    setBumpMap(Image value)

    setSpecularMap(Image value)

    setSelfIlluminationMap(Image value)

を使って設定するだけです。

非常にお手軽に設定できます。

このプログラムの実行結果は下図のようになります。

9

それぞれがどのように作用しているのか少しみておきましょう。

setDiffuseMap(Image value) だけを設定した場合プログラムの実行結果は下図のようになり、

でこぼこ感がまったくなくのっぺりしたようになります。

10

使ったイメージファイルはこれです。(サイズは違います)

11

 

setBumpMap(Image value) を設定することによりでこぼこ感を演出することができます。

イメージファイルはこれを使用します。

12

 

プログラムの実行結果は下図のようになります。

13

setSpecularMap(Image value) は反射光を制御するもので次のようなイメージファイルを使用します。

黒くなっている所は反射光を出さないようです。

14

では SpecularMap が設定されていない場合はどうなるのかみてみます。

setSpecularMap(Image value) を未設定としたうえでプログラムに反射光の設定を加えます。

        sphereMaterial.setSpecularColor(Color.RED);
       
        sphereMaterial.setSpecularPower(3.0d);

このプログラムの実行結果は下図のようになります。

赤い反射光がしっかりと表示されてます。

15

setSpecularMap(Image value) が設定されていれば反射光を制御可能となります。

setSpecularMap(Image value) を適用して実行すると下図のように反射光が適用されている部分が限定できます。

15b

ちょっと解りづらいけど灰色の部分だけ薄く反射されているのが確認できます。

解りやすくするために SpecularMap イメージファイルを下図のように反射ベース色を灰色から白色へと変更してみます。

15c

これで実行すると下図のようになります。

15d

SpecularMap を上手く活用すれば繊細な描画が可能となりそうです。

それでは DiffuseColor を設定してみます。

プログラムに次のコードを書き加えます。

        sphereMaterial.setDiffuseColor(Color.ORANGERED);

このプログラムを実行すると 設定した DiffuseColor オレンジレッドが適用され赤っぽい地球が表示されます。

面白いですね。

まるでガミラス帝国に攻撃されて放射能汚染された地球のようです。ww

16

最後に setSelfIlluminationMap(Image value) を試してみましょう。

これ使い道が良く解らないのですが名前からすると自発光のようです。

なのでライトを無しにして、マテリアルの設定は setSelfIlluminationMap(Image value) だけにしました。

        // SelfIlluminationMap
        sphereMaterial.setSelfIlluminationMap(
                new Image(this.getClass().getResource(“resources/earth_texture.jpg”).toExternalForm(),
                        8_192 / 2.0d,
                        4_092 / 2.0d,
                        true,
                        true
                )
        );

 

        root.getChildren().addAll(sphere);

 

実行結果は下図のようになりました。

17

やっぱり自発光の設定のようです。

発光色や強さの設定ができないかと調べてみたのですが解りませんでした。

以上テクスチャの貼り方を簡単に説明させていただきました。

ToyPiano アプリケーションではテクスチャは貼ってないので蛇足となってしまいましたがお気に入りのテクスチャを持っている方は貼ってお楽しみください。

蛇足ついでに 3D オブジェクトの拡大縮小もやっておきましょう。

Sphere がマウスクリックされた座標位置を取得しそれらの値を適当に加工して拡大縮小させてみます。

プログラムに下記コードを追加します。

        scene.setOnMouseClicked(event -> {
            PickResult pickResult = event.getPickResult();
            if (pickResult.getIntersectedNode() == sphere) {
                Point3D point3d = pickResult.getIntersectedPoint();
                if (point3d.getX() != 0.0d && point3d.getY() != 0.0d && point3d.getZ() != 0.0d) {
                    sphere.setScaleX(abs(point3d.getX() / 500.0d) * 1.2d);
                    sphere.setScaleY(abs(point3d.getY() / 500.0d) * 1.2d);
                    sphere.setScaleZ(abs(point3d.getZ() / 500.0d) * 1.2d);
                } else {
                    sphere.setScaleX(0.5d);
                    sphere.setScaleY(0.5d);
                    sphere.setScaleZ(0.5d);
                }
            } else {
                sphere.setScaleX(1.0d);
                sphere.setScaleY(1.0d);
                sphere.setScaleZ(1.0d);
            }
        });

 

プログラムの実行結果は下図のようになります。

18

19

PickResult クラスを使い Scene のクリックされたものが Sphere オブジェクトかどうか

getIntersectedNode() メソッドで判定し、そうであれば

getIntersectedPoint() メソッドで Point3D インスタンスを渡します。

Point3D クラスを使い、X, Y, Z 座標位置情報を取得してそれらの値の絶対値を Sphere の半径で割りそして 1.2 倍してます。

javafx.scene.Node クラスのメソッドを利用してそれぞれ X, Y, Z 座標値に適用してます。

setScaleX(double value)

setScaleY(double value)

setScaleZ(double value)

上記プログラムでは X, Y, Z 軸の何れかの座標位置が 0 なら拡大縮小を 0.5 倍するようにしています。

Sphere オブジェクトがクリックされてなければ拡大縮小は元に(1.0 倍)に戻します。

拡大縮小も簡単にできますね!

ついでだから回転させてみましょう。

Z 軸を 23.5 度傾けて反時計回りに回転させてみます。

プログラムに下記のようみ変更します。

実行結果は下の動画のようになります。

AnimationTimer で地球を 23.5 度傾けて反時計回りに回転させているコードは次の部分です。

               // 23.5度傾けて自転させる。
                sphere.getTransforms().setAll(new Rotate(23.5d, Rotate.Z_AXIS), new Rotate(-angle, Rotate.Y_AXIS));

これを次のように記述してしまうと思い通りにいきません。

                angleZ.set(23.50d);
                angleY.set(angle);

うっかりこれやってしまうと Z 軸が 23.5 度傾いてから水平に反時計回りに回ってしまいます。

これはこの後のカメラを動かす場合でも同じように注意が必要となります。

せっかくここまで試したことだしもう少しだけ寄り道をしましょう。

自転する地球の周りに月を公転させてみます。

ポイントライトは太陽の位置とします。(大きさや距離に正確な値は使ってないです。適当です。)

今までの技術でさらっとできてしまいます。

このプログラムの実行結果は次の動画のようになります。

 

この動画から解るように地球や月で太陽の光(ポイントライト)が遮られたときにできるはずの影ができません。

少し残念な結果となりました。

ちょっと脱線していまいましたが ToyPiano アプリケーションに必要な最後の機能をそろそろ紹介します。

カメラを動かす!

これができるようになったら ToyPiano アプリケーションで使っている 3D 関係の技術は全てあなたのものです。

32 鍵盤とみみっちいこと言わずに 88 鍵盤の本格的な 3D Piano アプリケーションだって作れるかもしれません。

それでは次のようなプログラムを組んでみましょう。

Cylinder を手前に45度傾けて、ピボットは最下部で一番手前の位置として表示させる。

これは今までの記事の中で簡単にできることが証明されています。

トグルボタンを適当に配置して、ボタンが押されたらカメラを Cylinder を中心に水平に回転させてみます。

もちろんカメラは常に Cylinder に向いてます。

これをアニメーション表示さてます。

トグルボタンを再度押してアニメーションを止めて初期状態に戻します。

この記事をここまで読んでくださった方は X, Z 軸の位置を計算して移動させて、カメラが Cylinder の中心に向かうように

Y 軸を回転させればいいだけじゃん。って思ったでしょう。

おそらく多くの方は次のようなプログラムを思いつくのではないかと思います。

このプログラムの実行結果は下図のようになります。

20

なんかとんでもないことになってますね。

動画はこちらです。

カメラを動かすとトグルボタンも平べったい平面上に配置されている状態なのが確認されました。

ちょっとこれはまずいです。

トグルボタンが押しにくいじゃありませんか!(そこか!?

ちなみにこのトグルボタンはちゃんと機能します。(^_^;)

特にこのプログラムを説明するところは無いのですが、何故か四つ目のポイントライトが効きません。

カメラが回転していることを解りやすくするために四方から PointLight を当てようとしたのですが・・・

PointLight の件はちょっと解らないので深く追求せずに肝心の問題に移ります。( ̄。 ̄;)

このように UI 用のオブジェクト(コントロール)などを今まで通りに撮して、3D オブジェクトを撮す場合に影響を及ぼさないようにするにはどうすればいいのか?

実はこの問題を簡単に解決するために特別な Node が用意されています。

javafx.scene.SubScene です。

これを使って今までの 2D 用と 3D 用を分離させてしまいます。

カメラも 3D 用に( SubScene 用 ) 別に一つだけ特別に用意されています。

それではこのプログラムを SubScene を使って期待通りに動くようにしましょう。

先ほどのプログラムと見た目は違うけどやってることは同じです。

コントロールの配置がこれで自在にできるので先ほどのプログラムとこの部分は随分違ってます。

また、Scene 用のカメラが定義されてないのですが心配には及びません。

デフォルトのカメラが利用されてコントロールは表示されます。

Cylinder を撮すために回転しているカメラは SubScene に設定された専用のカメラなので先ほどのプログラムのように UI (コントロール)まで撮しません。

実に合理的にこの問題を解決していますね。

それでは SubScene を使って改善されたプログラム見てみましょう。

SubScene って簡単に便利に使えますね!(^_^)

プログラムの実行結果は次のようになります。

21

 

ToyPiano アプリケーションを作るために必要な JavaFX 3D API はこれで全てです。

JavaFX は標準で 3D API を持っているからちょっとしたことなら簡単に使うことができます。

仕事では使う機会がないかもしれませんが息抜きに遊んでみるのもいいかもしれませんね。(^_^)

長くなってしまいますが鍵盤数を減らした完成版のプログラムを載せておきます。

私はスーパーサイヤ人並の素人なのでいつもながら汚いコードですが参考になれば幸いです。

間違い、アドバイス等は大歓迎ですのでお気付きの点があればご教示くださいませ。

最後まで読んでくださってありがとうございます。

感謝をこめて注意が必要なことを書いておきます。

タッチインタフェースを使っているので次の例外が投げられるかもしれません。

Exception in thread “JavaFX Application Thread” java.lang.RuntimeException: Too many touch points reported

これが投げられても動いていればいいのですがタッチが効かなくなる場合があります。(マウスは動きます)

Twitter でつぶやいたら @aoetk さんから次のようなアドバイスをいただきました。

「処理を非同期にするのも一つの手ですね。タッチだとこの辺がどうしてもシビアになりますねえ。

Windows Storeアプリ開発に使うWinRT APIでは、50ミリ秒以上掛かる処理は非同期にせよといわれているようです。」

ありがとうございました。(^_^)

早速イベント処理を下記のように非同期に変更してみました。

        keyboard_f.setOnTouchPressed(te -> {

            Task<Void> task = new Task<Void>() {

                @Override
                protected Void call() throws Exception {

                    recordingEvent(recording, eventRecording, actionEventTime, keyboard_f, te);

                    Platform.runLater(() -> {

                        pressedWhiteKeyBoard(f, keyboard_f, whiteKeyBoardDepth);

                    });

                    return null;
                }

            };

            executor_1.execute(task);

        });
       

残念ながら改善されませんでした。

何が悪いのか解らずどうしようって悩んでいたのですが今年の9月の末に購入した VAIO ノートで動かしたら全然問題なしでした。

プログラムを組んでいた PC だけなのでひょっとしたらハードの問題かもしれません。

タッチパネルの性能?

タッチパネルのドライバとか?

何はともあれ新しい方の PC で問題ないから良しとしましょう。(ヲヒ

もう一つ、タッチインタフェースを使っているのでピアノを演奏しているときにタッチドラッグが発生して不用意に動いてしまうことがあります。

これを防止するために苦肉の策として次のようなことをしました。

これが正しいかどうかは解りませんが何らかの処理が必要となります。

        // プレイバック以外のタッチドラッグを禁止させるために
        subScene.setOnTouchPressed(te -> {
            touchPressedCount++; // フィールドへアクセス 取扱注意
        });

        subScene.setOnTouchReleased(te -> {
            touchReleasedCount++; // フィールドへアクセス 取扱注意
        });

    // 略

        subScene.setOnMouseDragged(event -> {
            if (touchPressedCount == touchReleasedCount || activePlayback) {
                angleX.set(anchorAngleX – (anchorY – event.getSceneY()));
                angleY.set(anchorAngleY + anchorX – event.getSceneX());
            }
        });

3D 同様タッチインタフェースを使った解説が少ないのでこういったときの定番処理というのが良く解りません。

先日もマウススクロールによるカメラポジションの変更で随分悩みました。

JavaFXでマウスホイールを使ってのズームインズームアウトは可能か?

タッチインタフェースの完全無欠の使い方を知りたい今日この頃です。

あと、カメラの移動、回転なんですがこのプログラムでは、見下ろし角度(X 軸)と中心への角度(Y 軸)でカメラを回転させています。

               // カメラ移動及び回転
                xPos.set(Math.sin(Math.toRadians(azimuth)) * radius);
                zPos.set(-1.0d * Math.cos(Math.toRadians(azimuth)) * radius);
                yPos.set(heightProperty.getValue());
                subCam.getTransforms().setAll(new Rotate(-1.0d * azimuth, Rotate.Y_AXIS), new Rotate(-1.0d * downAngle, Rotate.X_AXIS));

このコードの最後の行でカメラを回転させています。

ここで最後の行を次のようなコードを書くとはまります。

        ryPos.set(-1.0d * azimuth);
        rxPos.set(-1.0d * downAngle);

理屈的には同じように感じるのですがまったく違います。

残念ながらとんでもないカメラワークとなってしまいます。

見下ろし角をつけずに Y 軸だけ回転させるのであれば次のコードで問題ありません。

               // カメラ移動及び回転
                xPos.set(Math.sin(Math.toRadians(azimuth)) * radius);
                zPos.set(-1.0d * Math.cos(Math.toRadians(azimuth)) * radius);
                yPos.set(heightProperty.getValue());
                ryPos.set(-1.0d * azimuth);

回転させるコードを書くときは注意が必要です。

あとはいたってシンプルなものなので作って遊んでみるのもいいかもしれません。

実際に Box オブジェクトだけを使い移動と回転だけしかさせていません。

OneDrive にこのプログラムを置いてありますので遊んでみたい方は次のリンクからどうぞ。

https://onedrive.live.com/redir?resid=EAE4ED7554BC7AB6!34658&authkey=!AP29iHq_NpAEKiM&ithint=folder%2c

一応、何かトラブルが起こって損害が発生しても責任は一切負えませんので(^_^;)

/**
*
* @おまけのお知らせ

* SwingNodeとTextFlow
*
* JavaFX で英語力を補う
*
*/

最後にひと言

JavaFX 楽しい!


JavaFX で英語力を補う

JavaFX

このエントリーは、JavaFX Advent Calendar 2014, 11日目のおまけです。

昨日は @toruwest さんの「JavaFXのTreeViewでアニメーションしてみる」でした。

明日は @skrb さんの「Java Advent Calendarと一緒になにか書きます」です。

JavaFX Advent Calendar 2014、6日目の Katsumi Kokuzawa さんの「JavaFXでMarkdownエディタを作る」を読んで

リアルタイムに入力されたテキストが変換され表示されるのって格好いいなぁって思ったので自分もチャレンジしてみた。

私は英語が駄目な残念な人間なので Google さんと JavaFX さんに助けてもらうことにしました。

翻訳と音声は java-google-translate-text-to-speech を利用させていただきました。

翻訳は下記 FXMLDocumentController クラスのソースコード 145 行目から 152 行目までのところで処理をしています。

文字を一部置き換えているのはエラーが出るためのその場しのぎの適当な対応です。

それと長文になるとエラーが出ます。

そういう仕様なのかバグなのか、使い方が悪いのかは解りません。(^_^;)

とりあえず的な(使い物にならない)翻訳機を作ったよというお話しでした。

動画とプログラムのコードを貼っておきます。

 

これで英語力が上がるとうれしいけど努力しないでってのは完全に無理な話しですね。

JavaFX 楽しい!

Hatena タグ:

« 古い記事