JavaFX の MeshView を試してみる その2
このエントリーは JavaFX の MeshView を試してみる の続きです。
今回は立方体を表示させるプログラムを組んでみます。
立方体だからといって複雑になり難しくなることはありません。
しょせん、最小構成のポリゴン( TriangleMesh ) の集まりです。
立方体の頂点の座標は下図のようにしました。
この立方体に貼るテクスチャは次のものを使います。
これを先ほの立方体へ貼ります。
解りやすくするためにテクスチャの座標とインデックスをいれた画像を用意しました。
前回のエントリーをお読みになった方は下記のソースコードを見たら何をやっているか理解できるでしょう。
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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
package jp.yucchi.dukemeshbox; import static java.lang.Math.abs; import javafx.application.Application; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Point3D; 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.input.PickResult; 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 DukeMeshBox extends Application { private double anchorX; private double anchorY; private double anchorAngleX = 0; private double anchorAngleY = 0; private final DoubleProperty angleX = new SimpleDoubleProperty(20.0d); private final DoubleProperty angleY = new SimpleDoubleProperty(25.0d); @Override public void start(Stage primaryStage) { final MeshView meshBox = new MeshView(new MeshBox(200f, 200f, 200f)); Image diffuseMap = new Image(DukeMeshBox.class.getResource("resources/duke_uv.jpg").toExternalForm()); PhongMaterial material = new PhongMaterial(); material.setDiffuseMap(diffuseMap); meshBox.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(meshBox); Rotate xRotate; Rotate yRotate; meshBox.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()); }); 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); } }); primaryStage.setTitle("Duke MeshBox"); primaryStage.setScene(scene); primaryStage.show(); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } private class MeshBox extends TriangleMesh { MeshBox(float w, float h, float d) { float hw = w / 2f; float hh = h / 2f; float hd = d / 2f; float x0 = 0f; float x1 = 1f / 4f; float x2 = 2f / 4f; float x3 = 3f / 4f; float x4 = 1f; float y0 = 0f; float y1 = 1f / 4f; float y2 = 2f / 4f; float y3 = 3f / 4f; float y4 = 1f; this.getPoints().addAll( hw, hh, hd, //point A hw, hh, -hd, //point B hw, -hh, hd, //point C hw, -hh, -hd, //point D -hw, hh, hd, //point E -hw, hh, -hd, //point F -hw, -hh, hd, //point G -hw, -hh, -hd //point H ); this.getTexCoords().addAll( x1, y1, // idx 0 x2, y1, // idx 1 x0, y2, // idx 2 x1, y2, // idx 3 x2, y2, // idx 4 x3, y2, // idx 5 x4, y2, // idx 6 x0, y3, // idx 7 x1, y3, // idx 8 x2, y3, // idx 9 x3, y3, // idx 10 x4, y3, // idx 11 x1, y4, // idx 12 x2, y4 // idx 13 ); this.getFaces().addAll( 0, 10, 2, 5, 1, 9, //triangle A-C-B 2, 5, 3, 4, 1, 9, //triangle C-D-B 4, 7, 5, 8, 6, 2, //triangle E-F-G 6, 2, 5, 8, 7, 3, //triangle G-F-H 0, 13, 1, 9, 4, 12, //triangle A-B-E 4, 12, 1, 9, 5, 8, //triangle E-B-F 2, 1, 6, 0, 3, 4, //triangle C-G-D 3, 4, 6, 0, 7, 3, //triangle D-G-H 0, 10, 4, 11, 2, 5, //triangle A-E-C 2, 5, 4, 11, 6, 6, //triangle C-E-G 1, 9, 3, 4, 5, 8, //triangle B-D-F 5, 8, 3, 4, 7, 3 //triangle F-D-H ); this.getFaceSmoothingGroups().addAll( 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ); System.out.println("PointElementSize : " + this.getPointElementSize()); System.out.println("TexCoordElementSize : " + this.getTexCoordElementSize()); System.out.println("FaceElementSize : " + this.getFaceElementSize()); } } } |
今回はちょっとピッキング 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 が減らない! どうしよう(>_<。)
TAGS: JavaFX | 2014年12月23日3:48 PM | Comment : 0