2017年版
2017年12月23日(土)
午後の部 3時間目 4時間目 用資料
講習内容の案内文:
高校でプログラミング教育を行う「情報」科担当教諭および、プログラミング技能を学び
たい小中高等学校各科教諭を対象に、教育利用の基礎となるプログラミング技能をJavaの
実習を通して教授する。前半では、Javaの開発環境であるEclipseを導入し、簡単なプロ
グラム制作からデータ整列アルゴリズムなどの実装を通してアルゴリズムの考え方を学ぶ。
後半、3DCGのプログラムを学び、関数型プログラミングで図形による映像を制作する。
参考資料:
初めてJavaを触った人間がEclipseでJavaFXのGUIアプリを起動するまで
EclipseでJavaFXのコード補完が機能しないときの対処方法
JavaFX の標準機能だけでシンプルな3Dトイピアノをつくろう
扱う範囲:
・JavaFX による 3DCG
・アニメーションの設定
・オブジェククとの生成と管理
・関数型プログラミング
Javaとグラフィックス:
技術的選択肢
OpenGL JOGL
Java 3D
JavaFX 3D
Android のアプリ開発にはGoogle社の独自APIが必要
JavaFX に似た アプリ開発スタイル
OpenGL/ES スマートフォン向けグラフィックス環境の利用
JavaFX について wikipedia
・Java8から従来のSwingに変わりJavaのGUI ライブラリとして採用された新規API
Java: プログラム言語
Java8 の 8 はバージョン番号。バージョン変更に伴い、文法の追加、機能の追加、標準ライブラリの内容に更新(新規追加・旧機能削除)がある。
最新版は 2017.10 に発表された Java9
Swing: 旧GUIライブラリ
初期のライブラリには AWT(Abstract Windows Toolkit)がある。AWTの次に登場したGUIライブラリがSwing。
ネットや書籍のJavaのGUIプログラムのコードについて調べる際には、利用ライブラリについて要確認。
GUI: グラフィカル・ユーザ・インタフェース
ウインドウやボタン・メニューなどの部品を用いてアプリケーションをデザインし、操作できるようにしたもの
ライブラリ: プログラム言語で作成された部品の集合
データ入出力・数値処理・日付管理など様々な機能、特に高い利用頻度の機能については、
標準ライブラリとして予めプログラムされた部品(Javaの場合はクラス)が提供される。
ライブラリの部品は、他の部品と組み合わせてプログラムしやすいように設計されている。
アプリケーションは、ライブラリから部品を選択してプログラムで組み合わせて作成する。
API: アプリケーション・プログラミング・インターフェース
ライブラリの部品の仕様。部品の提供する機能とその利用方法
Java9 の API https://docs.oracle.com/javase/9/docs/api/overview-summary.html
・JavaFX では、Scene Builder というアプリを利用して、GUIの画面設計をプログラム無しで行える。 Scene Builderの紹介動画
※今回の演習では、 JavaFX の 3DCG関連の API を利用してアプリケーションを作成する。
アプリケーションのGUIは、
・ウインドウ
・3DCG表示用のシーン
・マウス
だけを利用する。ボタンやメニューなどのGUIは扱わない。シンプルなアプリケーションなので、Scene Builderは使用せずに、
プログラムだけでGUIアプリケーションを作成する。
AndroidとJava
スマートフォンのOSがAndroidの場合、スマフォのアプリはJavaで作成することが出来る。
Androidのアプリ開発には、Android用のAPIと開発ツール(Android SDK)が必要。
※今回の演習のプログラムはAndroidのアプリとしては利用できない。
演習の準備:
JavaFX を eclipse で利用するための設定を行う。
・eclipseを起動
・JavaFX を eclipse に設定
メニュー → ヘルプ → Eclipse マーケットプレース
検索欄:
入力 e(fx)clipse
虫眼鏡アイコンをクリック
インストール をクリック
選択 使用条件の条項に同意します
→ 完了
追加機能 e(fx)clipse がダウンロードしてインストールされるまでしばらく待つ
※ ↓このダイアログが表示されるまで待ち、今すぐ再始動 をクリック
設定の動作確認:
パッケージエクスプローラ に プロジェクトを作成する
右クリック → 新規 → Javaプロジェクト
プロジェクト名 javaFXTest
※ 大文字小文字に注意
プロジェクト名 → 右クリック → 新規 → クラス
クラス名 Test1
サンプルコード
package javaFXTest;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class Test1 extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
// 半径1の球体を作成して
Sphere sphere = new Sphere(1.0);
// 球体のマテリアルを赤色に設定
sphere.setMaterial(new PhongMaterial(Color.RED));
// 球体をZ軸方向に+5.0移動
sphere.setTranslateZ(5.0);
// グループノードに追加
Group group = new Group(sphere);
// シーングラフに追加
Scene root = new Scene(group, 500, 500, false);
// カメラを設定
root.setCamera(new PerspectiveCamera(true));
// ウィンドウを表示
primaryStage.setScene(root);
primaryStage.show();
}
}
実行ボタン → 実行 → Java アプリケーション からプログラムを実行する。
実行結果:
動作が確認できたら、ウインドウを閉じる。
3DCGの基礎知識:
解説動画 3DCGソフト Blender の画面で3DCGの基礎を案内
3次元空間の座標 X Y Z
スクリーンの座標 X Y
立体モデル
2D形状 Shape Circle、Rectangle、Text など
3D形状 Shape3D Box、Cylinder、MeshView、Sphere
マテリアル 参照: JavaFX 3D マテリアル(色・テクスチャ)
コードの解説:
javaFXのウィドウとシーンについて
JavaFXでのアニメーションおよびビジュアル効果 の 図7-2 参照。
ソースコード中のコメントを参照。
演習1
Test1 クラス のコード中の main launch start の実行順序の確認
例)コンソールにテキストを出力するコード System.out.println を利用して、メソッドの実行順序の確認をする。
public static void main(String[] args) {
System.out.println("start");
launch(args);
System.out.println("end");
}
@Override
public void start(Stage primaryStage) throws Exception {
System.out.println("next");
演習2
Test1 クラス のコード中の 数値 や 色名 を修正して結果を確認する。
例)
// 球体を作成して
Sphere sphere = new Sphere(0.5);
sphere.setMaterial(new PhongMaterial(Color.CYAN));
sphere.setTranslateZ(5.0);
sphere.setTranslateX(-2.0);
sphere.setTranslateY(1.0);
演習3
Test2 クラス を作成する。
パッケージエクスプローラ で Test1をコピーして Test2 を作成する。
Test1.java → 右クリック → コピー
→ 右クリック → 貼り付け
※ クラス名が自動的に Test2 に変更されるので、そのまま保存。
コードを追加する。
使用する3D図形の例)
Circle、Line、Polygon、Rectangle、Text
球 円筒 立方体 の追加の例)
Sphere sphere2 = new Sphere(1.5);
sphere2.setMaterial(new PhongMaterial(Color.BLUE));
sphere2.setTranslateZ(5.0);
sphere2.setTranslateX(0.8);
// 半径, 高さ
Cylinder cylinder = new Cylinder(0.5, 2.0);
cylinder.setMaterial(new PhongMaterial(Color.BLUE));
cylinder.setTranslateZ(5.0);
cylinder.setTranslateX(2.0);
// Z軸で回転
cylinder.setRotationAxis(Rotate.Z_AXIS);
cylinder.setRotate(45.0);
Box box = new Box(1.0, 2.0, 0.5);
box.setMaterial(new PhongMaterial(Color.RED));
// YとX軸で回転。回転の指示を追加。
box.getTransforms().add(new Rotate(30.0, Rotate.X_AXIS));
box.getTransforms().add(new Rotate(30.0, Rotate.Y_AXIS));
box.setTranslateZ(5.0);
box.setTranslateX(-2.0);
Group group = new Group(sphere, cylinder, box);
ボックスとテキストを追加する例)
Box box = new Box(1.0, 1.0, 1.0);
box.setTranslateZ(0.0);
box.setTranslateX(1.0);
box.setMaterial(new PhongMaterial(Color.AZURE));
Text text = new Text();
text.setFont(new Font(20));
text.setText("The quick brown fox jumps over the lazy dog");
text.getTransforms().add(new Rotate(-15.0, Rotate.X_AXIS));
text.setTranslateX(-20);
text.setTranslateZ(30.0);
text.setScaleX(1.0);
text.setFill(Color.RED);
// グループノードに追加
Group group = new Group(sphere, box,text);
コードの実行順序とレンダリング結果の比較
前後関係が考慮されていないことの確認
→ 幾つかのオブジェクトを重なる位置に配置。
→ オブジェクトを追加する順番を入れ替えて確認する。
演習4
深度判定を有効にする。(Zバッファを有効にする)
FarClipを変更する。
// シーングラフに追加
Scene root = new Scene(group, 500, 500, true);
// カメラを設定
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(800);
root.setCamera(camera);
演習5
カメラの位置を修正する。
camera.setTranslateZ(-5.0);
演習6
オブジェクトのグループをマウスで回転させる。
グループを原点を中心に回転する設定を追加:
// 立方体を含むグループを回転させるための Rotate を登録
Rotate rotateX = new Rotate(0.0, Rotate.X_AXIS);
Rotate rotateY = new Rotate(0.0, Rotate.Y_AXIS);
Translate translate = new Translate(0.0, 0.0, 0.0);
group.getTransforms().addAll(rotateX, rotateY, translate);
マウスのボタンを押した位置を記録(ドラッグ開始のXY座標)
座標記録用変数の準備: start メソッドの外側で宣言する。
double x,y;
マウスボタンのイベント処理
// マウスイベントのハンドラを登録
group.setOnMousePressed(e -> {
// マウスがクリックされたときの位置を記録
this.x = e.getSceneX();
this.y = e.getSceneY();
});
マウスドラッグのイベント処理
group.setOnMouseDragged(e -> {
// ドラッグした距離に応じて、立方体を含むグループを回転させる
double nowX = e.getSceneX();
double nowY = e.getSceneY();
double dx = this.x - nowX;
double dy = this.y - nowY;
// Y方向へのドラッグは、 X軸で回転させる
rotateX.setAngle(rotateX.getAngle() - dy * 0.5);
// X方向へのドラッグは、 Y軸で回転させる
rotateY.setAngle(rotateY.getAngle() + dx * 0.5);
this.x = nowX;
this.y = nowY;
});
演習7
オブジェクトの位置とカメラからの距離をマウスで操作する。
group.setOnScroll(e -> {
group.setTranslateZ(group.getTranslateZ() + e.getDeltaY()/1_000);
});
応用
演習3のコードをヒントにして、雪だるま や 三色団子 のCGの作成を試みる。
演習8
Test3 クラス を作成する。
パッケージエクスプローラ で Test1をコピーして Test3 を作成する。
Test1.java → 右クリック → コピー
→ 右クリック → 貼り付け
※ クラス名が自動的に Test3 に変更されるので、そのまま保存。
コードを追加する。
アニメーション機能を利用する。 参照: アニメーションの基本
5秒(5000ms) かけて、sphereの X 座標を 現在位置から 2.0に移動する。
Timeline timeline = new Timeline();
KeyFrame kf = new KeyFrame(Duration.millis(5000), new KeyValue(sphere.translateXProperty(), 2.0));
timeline.getKeyFrames().add(kf);
timeline.play();
演習9
上記のアニメーションを完了後、逆再生し、初めから繰り返す設定を追加する。
Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
KeyFrame kf = new KeyFrame(Duration.millis(500), new KeyValue(sphere.translateXProperty(), 2.0));
timeline.getKeyFrames().add(kf);
timeline.play();
演習10
X方向の移動に加えて、Y方向の移動のアニメーションを設定する。
KeyFrame kf2 = new KeyFrame(Duration.millis(1000), new KeyValue(sphere.translateYProperty(), 1.0));
timeline.getKeyFrames().addAll(kf,kf2);
応用
新たに3Dオブジェクトを group に追加して、アニメーションの設定を試みる。
追加するオブジェクトについては、演習3を参照。
演習11
Test4 クラス を作成する。
パッケージエクスプローラ で Test1をコピーして Test4 を作成する。
Test2.java → 右クリック → コピー
→ 右クリック → 貼り付け
※ クラス名が自動的に Test4 に変更されるので、そのまま保存。
コードを追加する。
X座標のリストに応じて球を並べる。
※ 元々記述されている、Sphere と Group 関連のコードは削除する。
以下を代わりに追加する。
Group group = new Group();
// X座標のリストを用意
List<Double> xs = Arrays.asList(-2.0, -1.0, 0.0, 1.0, 2.0);
// 球を作成してgroupに追加。
xs.forEach(x -> {
Sphere s = new Sphere(0.25);
s.setMaterial(new PhongMaterial(Color.RED));
s.setTranslateX(x);
group.getChildren().add(s);
});
演習12
球を10000個用意し、それぞれを各XYZ座標が-1.0~+1.0の範囲のランダムな座標に移動する。その後、グループに配置する。
// 球のストリーム。球を無限に発生する装置。
Stream<Sphere> ss = Stream.iterate(new Sphere(0.01), e -> new Sphere(0.01));
// 乱数を発生する装置。
Random rand = new Random();
// 球を10000個取り出してランダムに配置する。
ss.limit(10000).forEach(s -> {
s.setTranslateX(rand.nextDouble()*2.0 -1.0);
s.setTranslateY(rand.nextDouble()*2.0 -1.0);
s.setTranslateZ(rand.nextDouble()*2.0 -1.0);
s.setMaterial(new PhongMaterial(Color.CYAN));
group.getChildren().add(s);
});
演習13
原点にある10000個の球を、それぞれの中心がランダムな座標の球に変換する。その様な球の中から、中心座標が X+Y+Z > 1.0 の条件を満たすものだけを選び、グループに登録する。
// 球のストリーム。球を無限に発生する装置。
Stream<Sphere> ss = Stream.iterate(new Sphere(0.01), e -> new Sphere(0.01));
// 乱数を発生する装置。
Random rand = new Random();
// 球を10000個取り出してランダムに配置する。
Stream<Sphere> ss2 = ss.limit(10000).map(s -> {
s.setTranslateX(rand.nextDouble()*2.0 -1.0);
s.setTranslateY(rand.nextDouble()*2.0 -1.0);
s.setTranslateZ(rand.nextDouble()*2.0 -1.0);
s.setMaterial(new PhongMaterial(Color.CYAN));
return s;
});
// 球の xyz座標が x + y + z > 1.0 であるものを選択して追加
ss2.filter(s -> s.getTranslateX() + s.getTranslateY() + s.getTranslateZ() > 1.0)
.forEach(s -> group.getChildren().add(s));
確認テスト
Stream の利用例
中間処理を追加する。
10000個の球を座標によって振り分けた後、
・Listデータとして条件にマッチしたものをまとめる
・Listの要素数を数える
・Listデータをグループに登録する。
// 球の xyz座標が x + y + z > 1.0 であるものを選択して追加
List<Sphere> ss3 = ss2.filter(s -> s.getTranslateX() + s.getTranslateY() + s.getTranslateZ() > 1.0)
.collect(Collectors.toList());
System.out.println(ss3.size());
ss3.forEach(s -> group.getChildren().add(s));
参照 Java8 Stream APIの基本(7) - 終端操作2(Stream#collect)
条件により球を2種類に分割し、片方の色を変更する。
Map<Boolean, List<Sphere>> ss3 = ss2.collect(Collectors.partitioningBy(s -> s.getTranslateX() + s.getTranslateY() + s.getTranslateZ() > 1.0));
System.out.println(ss3.get(true).size());
System.out.println(ss3.get(false).size());
ss3.get(true).forEach(s -> group.getChildren().add(s));
ss3.get(false).forEach(s -> {
s.setMaterial(new PhongMaterial(Color.YELLOW));
group.getChildren().add(s);
});
今回の演習課題の作業を済ませたコードのファイルをこのページに添付しておくので参考にしてください。