JavaFX の MeshView を試してみる
JavaFX 3D API の MeshView は使うことは無いだろうと思いノータッチでいました。
いつか暇な時にでも軽くどんなものなのか見ておこうとずっとほったらかしにして現在に至ってます。
その理由は実際に自分でポリゴンやテクスチャを定義するなんて大変なことはしない。
モデルインポーターを使うのが常套手段だろうと考えてました。
今でもその考えは変わっていませんが少しだけでもどんなものなのか試してみます。
それでは最小構成で頂点をみっつ持つ正三角形を表示させてみることにしました。
いつものようにネット上で情報を拾い集めてあーでもないこーでもないと API ドキュメントを眺めながらやっています。
英語が駄目なので API ドキュメントを眺めていると表現しました。読めません。(>_<。)
よって、いつものように間違いがあるかもしれませんのでご了承くださいませ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
package jp.yucchi.myfirstmesh; import javafx.application.Application; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.AmbientLight; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.PointLight; import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.image.Image; import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; import javafx.scene.shape.MeshView; import javafx.scene.shape.TriangleMesh; import javafx.scene.transform.Rotate; import javafx.stage.Stage; /** * * @author Yucchi */ public class MyFirstMesh extends Application { private double anchorX; private double anchorY; private double anchorAngleX = 0; private double anchorAngleY = 0; private final DoubleProperty angleX = new SimpleDoubleProperty(0); private final DoubleProperty angleY = new SimpleDoubleProperty(0); @Override public void start(Stage primaryStage) { final MeshView myMesh = new MeshView(new MyMesh(200f, (float) (100 * Math.sqrt(3.0d)), 0f)); Image diffuseMap = new Image(MyFirstMesh.class.getResource("resources/duke.jpg").toExternalForm()); PhongMaterial material = new PhongMaterial(); material.setDiffuseMap(diffuseMap); myMesh.setMaterial(material); // 環境光設定 final AmbientLight ambient = new AmbientLight(); ambient.setColor(Color.rgb(90, 90, 90, 0.6)); // ポイントライト設定 PointLight point = new PointLight(); point.setColor(Color.WHITE); point.setLayoutX(-600.0d); point.setLayoutY(-200.0d); point.setTranslateZ(-400.0d); PointLight point2 = new PointLight(); point2.setColor(Color.SKYBLUE); point2.setLayoutX(600.0d); point2.setLayoutY(-200.0d); point2.setTranslateZ(-400.0d); PerspectiveCamera camera = new PerspectiveCamera(true); // Field of View camera.setFieldOfView(45.5d); // Clipping Planes camera.setNearClip(1.0d); camera.setFarClip(1_000_000.0d); camera.setTranslateZ(-800); final Group root = new Group(myMesh); Rotate xRotate; Rotate yRotate; myMesh.getTransforms().setAll( xRotate = new Rotate(0, Rotate.X_AXIS), yRotate = new Rotate(0, Rotate.Y_AXIS) ); xRotate.angleProperty().bind(angleX); yRotate.angleProperty().bind(angleY); root.getChildren().addAll(ambient, point, point2); Scene scene = new Scene(root, 1_024, 768, true, SceneAntialiasing.BALANCED); scene.setFill(Color.ALICEBLUE); scene.setCamera(camera); scene.setOnMousePressed(event -> { anchorX = event.getSceneX(); anchorY = event.getSceneY(); anchorAngleX = angleX.get(); anchorAngleY = angleY.get(); }); scene.setOnMouseDragged(event -> { angleX.set(anchorAngleX - (anchorY - event.getSceneY())); angleY.set(anchorAngleY + anchorX - event.getSceneX()); }); primaryStage.setTitle("MyFirstMesh"); primaryStage.setScene(scene); primaryStage.show(); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } private class MyMesh extends TriangleMesh { MyMesh(float w, float h, float d) { this.getPoints().addAll( 0, 0, 0, // Top -w / 2, h, 0, // Bottom Left w / 2, h, 0 // Bottom Right ); this.getTexCoords().addAll( 1 / 2f, 0, // idx 0 0, (float) Math.sqrt(3) / 2, // idx 1 1, (float) Math.sqrt(3) / 2 // idx 2 ); this.getFaces().addAll( 0, 0, 1, 1, 2, 2 ); } } } |
このプログラムの実行結果は次の動画のようになります。
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 ] となります。
ちなみにこのプログラムではテクスチャは下図のように使っています。
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 楽しくて時間がどんどん溶けていく(>_<。)
TAGS: JavaFX | 2014年12月22日4:15 PM | Comments : 2