2018年版
8月8日(木)
午後の部 3時間目 4時間目 資料
内容:
・JavaFX による3DCG
・関数型プログラミング
3DCGとJava
比較的簡単に3DCGをJavaでプログラムできるので JavaFX を利用する。
JavaFX 3D: Javaの拡張ライブラリJavaFX の 3D機能
他にも3DCGをプログラムする方法としては、
Java 3D: Javaの標準のグラフィックスライブラリを利用
JOGL: Javaから OpenGL(CGプログラミングの共通規格) を利用
がある。
前半で
・ウインドウの表示
・3D図形の表示
・マウスクリックの処理
を扱う。
ボタン操作や選択メニューを利用するプログラムは扱わない。
後半で、関数型プログラミングによる3DCGを扱う。
AndroidとJava
AndroidスマートフォンのアプリをJavaで開発できる。
Androidのアプリ開発には、Android用のAPIと開発ツール(Android SDK)が必要。
今回の演習で作成するプログラムはAndroidでは利用できない。
開発環境の準備
JavaFX を eclipse で利用するための設定を行う。
・eclipseを起動する
ファイルエクスプローラー → ボリューム D:(D:ドライブ) → Pleiades47 → eclipse → eclipse.exe をダブルクリック
・ワークスペースを設定する
ファイル → ワークスペースの切り替え → その他
eclipse が再起動する
英文メッセージで old version に関する注意が表示されるので OK を押す
・eclipseにJavaFXによるアプリを開発する機能を追加する
設定作業:
メニュー → ヘルプ → 新規ソフトウェアのインストール
1. 作業対象を入力
サイトを入力または選択入力(コピペ)→ http://download.eclipse.org/releases/oxygen/
2. 追加ボタンを押す
レポジトリ名を入力 → oxygen
3. フィルター入力 (コピペ)→ e(fx)clipse
4. インストールする項目 e(fx)eclipse - IDE のチェックボックスをクリックして選択
5. 以下の作業を続け、最後に完了ボタンを押す
次へ をクリック
再び 次へ をクリック
選択する ●使用条件の条項に同意します
完了 をクリック
追加機能 e(fx)clipse がダウンロードしてインストールされるまでしばらく待つ
↓このダイアログが表示されるまで待ち、今すぐ再始動 をクリック
JavaFXをeclipseで利用する準備終了。
演習1
サンプルコードによる開発環境eclipseの動作確認
1.
パッケージエクスプローラ に プロジェクトを作成する
右クリック → 新規 → Javaプロジェクト
プロジェクト名 javaFXTest
※ 大文字小文字に注意
完了 をクリック
2.
javaFXTest プロジェクトにクラスを作成する
プロジェクト名(javaFXTest) → 右クリック → 新規 → クラス
クラス名 Test1
完了 をクリック
ここで設定した名前Test1を持つ、実行内容の無いプログラムが表示される。
※ mainメソッドを持たないプログラムについて:
他のプログラムと組み合わせる部品としてのプログラム
Javaなどプログラミング言語には標準部品として多数のプログラムが提供されている
この様なプログラムを集めたものをクラスライブラリと呼ぶ
3.
メニューの 実行 → 実行構成 を以下の様に設定する
3-1 Javaアプリケーション を ダブルクリック
3-2 名前に入力 Test1
3-3 メイン・クラスに入力 javaFXTest.Test1
3-4 実行をクリック
プログラムが実行される。
実行結果は エラー になり、プログラムは動作を終了する。
コンソールの様子
4.
エラーの内容を確認して、以下の様にプログラムを修正する。
手順:
public class Test1 の右側に空白を開けて extends Application 追記する。
Application に赤い下線が付く(エラーの指摘)
エラー個所にマウスを重ねてクイックフィックス(自動訂正機能)から Application をインポートします(javafx.application) を選ぶ。
Test1に赤い下線が付く
エラー個所にマウスを重ねてクイックフィックスから 実装されていないメソッドの追加 を選ぶ。
エラー訂正完了後のプログラムの様子:
package javaFXTest;
import javafx.application.Application;
import javafx.stage.Stage;
public class Test1 extends Application{
@Override
public void start(Stage primaryStage) throws Exception {
// TODO 自動生成されたメソッド・スタブ
}
}
5.
エラーを修正したプログラムを実行する。
5-1
実行ボタン(緑の右向き三角)をクリックしてプログラムを実行する。
※実行構成が済んだプログラムは実行ボタンから実行できる。
5-2
保存するリソース(ここでは修正したプログラムファイルのこと)を選ぶで OK を押す。
5-3
プログラムが起動して正常に動作する(エラーが出ない)。
プログラムが起動した後の処理をプログラムしていないので、パソコンの画面上では何も起こらない。
さらにプログラムを止める方法もプログラムしていないので、プログラムは止まらない。
コンソールの停止ボタン(赤い四角)を押してプログラムを強制終了する。
6.
Windowを表示するようにプログラムを修正する。
start メソッドに次のコマンドを追加する。
primaryStage.show();
修正後のプログラム:
package javaFXTest;
import javafx.application.Application;
import javafx.stage.Stage;
public class Test1 extends Application{
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.show();
}
}
7.
実行ボタンを数回押す。
プログラムを実行する度にWindowが表示される。
Windowsを ×ボタン で消すとプログラムも終了する。
eclipseのコンソールの 停止ボタン(赤い■) を押してもプログラムは終了しWindowも消える。
演習2
シーンの表示
ここに掲載したサンプルコードを指示に従って段階的に組み込みながらプログラムの機能と動作を確認していく。
サンプルコード
package javaFXTest;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Test1 extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// ルートグループを作成
Group root = new Group();
// シーンs1に追加。シーンのサイズは幅500 x 高さ500
Scene s1 = new Scene(root, 500, 500, false);
// ウィンドウにシーンs1を設定
primaryStage.setScene(s1);
// ウィンドウを表示
primaryStage.show();
}
}
javaFXのウィンドウとシーンについて
JavaFXでのアニメーションおよびビジュアル効果 の 図7-2 参照
シーンの背景色を設定する。
挿入する位置については説明する。
// シーンの背景色の設定
s1.setFill(Color.AQUA);
演習3
球体の表示
package javaFXTest;
import javafx.application.Application;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
public class Test1 extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// ルートグループを作成
Group root = new Group();
// シーンs1にルートグループrootを追加。シーンのサイズは幅500 x 高さ500
Scene s1 = new Scene(root, 500, 500, false);
// ウィンドウにシーンs1を設定
primaryStage.setScene(s1);
// ウィンドウを表示
primaryStage.show();
// シーンの背景色の設定
s1.setFill(Color.AQUA);
// シーンを撮影するカメラを作成
Camera cam = new PerspectiveCamera(true);
// カメラをZ軸の-5.0に移動(画面の手前側に移動)
cam.setTranslateZ(-5.0);
// カメラをシーンに設定
s1.setCamera(cam);
// 半径1の球体を作成。初期座標は (x,y,z)=(0,0,0)
Sphere sp = new Sphere(1.0);
// グループの要素に sp を追加
root.getChildren().add(sp);
}
}
演習4
球体の色の変更
球体の移動
// 球体のマテリアルを赤色に設定
sp.setMaterial(new PhongMaterial(Color.RED));
// 球体をX軸方向に+0.3移動
sp.setTranslateX(0.3);
実行結果:
動作が確認できたら、ウインドウを閉じる。
応用1
Test1 クラス のコード中の 数値 や 色名 を修正して結果を確認する。
例)
変更する色の選択は eclipse のコード補完機能を利用してキーワードを選択するとよい
Color.
このように↑ピリオドまで削除し ピリオドを入力しなおす
または
変更したいキーワードをマウスで選択し Ctrl + スペースキー を押す
補完機能の利用例)
コードの修正例)
// シーンの背景色の設定
s1.setFill(Color.BLACK);
// 球体のサイズの変更
Sphere sp = new Sphere(0.5);
// 色の変更
sp.setMaterial(new PhongMaterial(Color.CYAN));
// 座標の変更
sp.setTranslateZ(5.0);
sp.setTranslateX(-2.0);
sp.setTranslateY(1.0);
例の実行結果)
3DCGの基礎知識:
解説動画 3DCGソフト Blender の画面で3DCGの基礎を案内
3次元空間の座標 X Y Z
スクリーンの座標 X Y
立体モデル
2D形状 Shape Circle、Rectangle、Text など
3D形状 Shape3D Box、Cylinder、MeshView、Sphere
マテリアル 参照: JavaFX 3D マテリアル(色・テクスチャ)
応用2
Test2 クラス を作成する。
パッケージエクスプローラ で Test1.java をコピーして Test2 を作成する。
Test1.java → 右クリック → コピー
→ 右クリック → 貼り付け
※ クラス名が自動的に Test2 に変更されるので、そのまま保存。
コードを追加する。
実行する際は、
メニュー 実行 → 実行構成 の設定が必要
使用する3D図形の例)
Circle、Line、Polygon、Rectangle、Text
球 円筒 立方体 の 追加と回転 の例)
// Sample1
Sphere sp2 = new Sphere(1.5);
root.getChildren().add(sp2);
sp2.setMaterial(new PhongMaterial(Color.BLUE));
sp2.setTranslateX(0.8);
Cylinder cy = new Cylinder(0.5, 2.0); // 半径, 高さ
root.getChildren().add(cy);
cy.setMaterial(new PhongMaterial(Color.BLUE));
cy.setTranslateX(2.0);
// Z軸で回転
cy.setRotationAxis(Rotate.Z_AXIS);
cy.setRotate(45.0);
Box box = new Box(1.0, 2.0, 0.5); // x y z 方向のサイズ
root.getChildren().add(box);
box.setMaterial(new PhongMaterial(Color.RED));
box.setTranslateX(-2.0);
// YとX軸の両方で回転。
box.getTransforms().add(new Rotate(30.0, Rotate.X_AXIS));
box.getTransforms().add(new Rotate(30.0, Rotate.Y_AXIS));
実行結果
ボックスとテキストを追加する例)
//Sample 2
Box box = new Box(1.0, 1.0, 1.0);
box.setTranslateZ(0.0);
box.setTranslateX(1.0);
box.setMaterial(new PhongMaterial(Color.AZURE));
root.getChildren().add(box);
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(-10.0, Rotate.X_AXIS));
text.setTranslateX(-200);
text.setTranslateY(5);
text.setTranslateZ(60.0);
text.setFill(Color.RED);
root.getChildren().add(text);
実行結果
コードとインデント
プログラムの構造を把握しやすくするため、インデント(字下げ)機能を活用する
オートインデントとオートフォーマット
Ctrl + A でコード全体を選択
Ctrl + Shit + F オートフォーマット
コードの実行順序とレンダリング結果の比較
前後関係が考慮されていないことの確認
→ 幾つかのオブジェクトを重なる位置に配置。
→ オブジェクトを追加する順番を入れ替えて確認する。
演習5
深度判定を有効にする。(Zバッファを有効にする)
FarClip(物体を描画するカメラからの最大距離)を変更する。
以下のコードの赤文字の部分を修正・追加
※ true は スイッチON(機能有効化)を意味するキーワード
※ falese は スイッチOFF(機能無効化)を意味するキーワード
// シーンs1にルートグループrootを追加。シーンのサイズは幅500 x 高さ500
Scene s1 = new Scene(group, 500, 500, true);
// シーンを撮影するカメラを作成
PerspectiveCamera cam = new PerspectiveCamera(true);
// カメラをZ軸の-5.0に移動(画面の手前側に移動)
cam.setTranslateZ(-5.0);
cam.setFarClip(800);
// カメラをシーンに設定
s1.setCamera(cam);
実行結果
演習6
カメラの位置を修正する。
cam.setTranslateZ(-15.0);
応用3
Test2.java をコピーして Test3 を作成する。
雪だるま や 三色団子 など単純な図形によるCG
を作成する。
元のコードを修正・コピー・追加・削除して作成する。
応用4
Test2.java を編集する。
マウスでCGを操作するコードを追加する。
マウスのボタンを押した位置を記録(ドラッグ開始のXY座標)する変数(メモリ)を用意する。
変数の準備:
start メソッドの { } の外側で宣言する。
double x,y;
以下のコードはstart メソッドの { } の内側に続けて追記する。
それぞれの図形オブジェクトを追加したグループ root をマウスで回転させる。
原点(0,0,0)を中心にX軸とY軸で回転する設定をrootに追加:
// シーンの回転量 RotateX と RotateY を準備
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);
// グループを回転する
root.getTransforms().addAll(rotateX, rotateY, translate);
マウスボタンのイベント処理
// マウスイベントのハンドラを登録
root.setOnMousePressed(e -> {
// マウスがクリックされたときの位置を記録
this.x = e.getSceneX();
this.y = e.getSceneY();
});
マウスドラッグのイベント処理
root.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;
});
応用5
オブジェクトの位置とカメラからの距離をマウスホイールで操作する。
root.setOnScroll(e -> {
root.setTranslateZ(root.getTranslateZ() + e.getDeltaY()/1000);
});
実験A
マウスの操作とコードの変数の関係を確認する
太文字のコードを対応する位置に追加
初期値の確認
double x, y;
{
System.out.println("init x= " + x);
System.out.println("init y= " + y);
}
マウスをクリックした際の座標を表示
root.setOnMousePressed(e -> {
// マウスがクリックされたときの位置を記録
this.x = e.getSceneX();
this.y = e.getSceneY();
System.out.println("click x= " + x);
System.out.println("click y= " + y);
});
マウスの移動量を表示
root.setOnMouseDragged(e -> {
// ドラッグした距離に応じて、立方体を含むグループを回転させる
double nowX = e.getSceneX();
double nowY = e.getSceneY();
double dx = this.x - nowX;
double dy = this.y - nowY;
System.out.println("dx= " + dx);
System.out.println("dy= " + dy);
休憩予定
後半では Java の関数型プログラミングを体験します。
List や Stream を利用します。
関数インターフェース を利用します。
演習7
Test4 クラス を作成する。
パッケージエクスプローラ で Test2をコピーして Test4 を作成する。
Test2.java → 右クリック → コピー
→ 右クリック → 貼り付け
※ クラス名が自動的に Test4 に変更されるので、そのまま保存。
コードを追加する。
X座標の値を並べたリストを利用して球体を描画する。
※ 元々記述されている、Sphere や 他の図形 関連のコードは削除する。
以下を代わりに追加する。
// X座標のリストを用意
List<Double> xs = Arrays.asList(-2.0, -1.0, 0.0, 1.0, 2.0);
// リストxsの値に応じて球を作成してrootに追加
xs.forEach(x -> {
Sphere s = new Sphere(0.25);
s.setMaterial(new PhongMaterial(Color.RED));
s.setTranslateX(x);
root.getChildren().add(s);
});
関数インターフェース
x -> { x を利用する処理 }
の部分は ラムダ式 という表記で プログラムを名前を付けずにデータとして他のプログラムに埋め込む際に利用する。
演習8
球を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));
root.getChildren().add(s);
});
※ストリーム Stream について
データはストリームから取り出すたびに生成される。その為、必要に応じた個数のデータを用意できる。
Listもデータの追加削除が可能なので任意の個数のデータを扱える。しかしデータが不足した場合などは、データを追加するコードを実行する必要がある。
※乱数の発生式の説明
rand.nextDouble() 0.0~1.0の範囲のランダムな値
rand.nextDouble()*2.0 0.0~2.0の範囲のランダムな値
rand.nextDouble()*2.0 -1.0 -1.0~1.0の範囲のランダムな値
演習9
10000個の 0.01 という値を、ランダムな座標の球に変換する。その様な球の中から、中心座標が X+Y+Z > 1.0 の条件を満たすものだけを選び、グループに登録する。
// Double 不動点小数値のストリーム、初期値 0.01 次の値 0.01
Stream<Double> ds = Stream.iterate(0.01, e -> 0.01);
// 乱数を発生する装置。
Random rand = new Random();
// 値を10000個取り出してランダムに配置する。
ds
.limit(10000)
.map(d -> {
Sphere s = new Sphere(d);
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;
})
.filter(s -> s.getTranslateX() + s.getTranslateY() + s.getTranslateZ() > 1.0)
.forEach(s -> root.getChildren().add(s));
※map はデータの変換処理を行うメソッド ここでは 0.01 を Sphere(0.01)に変換している。
※return s コードは、map で処理した最終結果(ランダムな座標のSphere)を次の処理に渡すためのコマンド。
※filterはデータの選択を行うメソッド
応用6
演習9のコードを修正する。
// Double 不動点小数値のストリーム、初期値 0.01 次の値 0.011, 0.012, 0.013 ....
Stream<Double> ds = Stream.iterate(0.01, e -> e + 0.001);
// 乱数を発生する装置。
Random rand = new Random();
// 値を100個取り出してランダムに配置する。
ds
.limit(100).map(d -> {
Sphere s = new Sphere(d);
s.setTranslateX((rand.nextDouble() *2.0*d - d)*5.0);
s.setTranslateY((rand.nextDouble() *2.0*d - d)*5.0);
s.setTranslateZ(d*20);
s.setMaterial(new PhongMaterial(Color.CYAN));
return s;
})
.forEach(s -> root.getChildren().add(s));
※ .filter の部分は削除します。
※ 青文字の数値は、Sphereのサイズ d に応じて XYに散らばる倍率、Zに遠ざかる倍率
実行例)
確認テスト
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 -> root.getChildren().add(s));
※元のss2.filterの処理は削除して、上記のコードと入れ替える。
参照 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));
root.getChildren().add(s);
});