2020年版

8月18日(火)

午後の部 3時間目 4時間目 資料

内容:

  • JavaFX による3DCG

  • 関数型プログラミング

Javaの3DCG

この演習ではJavaの拡張ライブラリJavaFXを使用して3DCGを表示する。JavaFXによりアプリケーションのコード量を削減したい。他の3DCGを扱うライブラリにはJava 3D(Java標準のグラフィックスライブラリ)やJOGL(Java OpenGL、OpenGLはCGプログラミングの共通規格)などあるがコード量は多くなる。

演習の前半では

  • JavaFXのセットアップ

  • ウインドウの表示

  • 3D図形の表示

に取り組む。

後半では

  • マウスクリック処理

  • データ処理と3DCG

に関数型プログラミングで取り組む。

Javaのアプリ

開発ツール Android SDK でAndroidスマートフォンのアプリをJavaで開発できる。JavaFXからAndroidアプリを作成する方法も有る(関連記事)。

ゲーム Minecraft のPC版はJavaで開発されている。

eclipse(開発環境)の起動

File Explorerで以下のフォルダを開いて eclipse.exe をダブルクリックする

※2020年度は pleiades を使います。下の画像はpleiades48と古いフォルダになっていますが訂正します。

ワークスペース(プログラムやデータファイルの保存場所)の選択


ワークスペースは午前の講座で設定したものから変更しなくてOKです。

../workspace を選択する。

JavaFXの設定

javaFX は 2018年のJava11のバージョンから開発ツールの標準ライブラリから外れて外部ライブラリになった。

その為、javaFXを利用して開発するためには javaFXを開発環境 eclipseに組み込む必要がある。

実習室PCに設定されたファイル共有フォルダの「レポートフォルダ」から以下の2つのフォルダを受講者のパソコンにコピーする

(コピー元)

小橋 一秀 →プログラム演習3→ javafx-sdk-11.0.2

小橋 一秀 →プログラム演習3→ javaFXTest

(コピー先)

PC → ローカルディスク(D:)

この演習のJava環境について

実習室パソコンのメンテナンス上の都合でJava11 と JavaFX11 を用いる。最新版は Java14 とJavaFX14。

演習1

javaFXの設定とウィンドウ表示のサンプルコードの実行

プロジェクト名、パッケージ名、クラス名とプログラムファイルが作成してある演習用プロジェクトをeclipseにインポートする(読み込む)。

1-1.

パッケージ・エクスプローラ から プロジェクトをインポートする

右クリック → インポート → 一般 → 既存プロジェクトをワークスペースへ

次へ をクリック

ルート・ディレクトリーの選択: 「参照」をクリックし

PC → ローカルディスク(D:) → javaFXTest

フォルダーを選択 をクリック

完了 をクリック

1-2.

インポートされたものを確認:

プロジェクト javaFXTest

パッケージ javaFXTest

クラス Test1

パッケージ・エクスプローラーから

javaFXTest -> src -> Test1.java ファイルをダブルクリックして開く。

インポートしたパッケージjavaFXTestのクラスTest1が表示される。ここでは実行内容はまだプログラムされていない状態。

以下の作業で順次プログラムを追加していく。

※フォルダ名やファイル名はコードと対応したものを用いる必要がある。

1-3.

Test1.javaのコードをJavaアプリケーションとして実行するための設定をする。

メニューの 実行 → 実行構成 を以下の様に設定する

Javaアプリケーション を ダブルクリック

名前(N):の欄の 新規構成 を消して Test1 と入力する

プロジェクト(P:) javaFXTest に更新する。

メイン・クラス(M:) javaFXTest.Test1 と入力

ビルド設定の修正

JavaのコードTest1.javaとライブラリJavaFXからアプリを構築(ビルド)する設定をする。

1-4. 設定不要につき省略

メニューの プロジェクト(P) → プロパティ(P) から設定パネルを表示して

Java のビルド・パス → ライブラリー(L) のタブを開く

モジュールパス → JRE システム・ライブラリー

1-5.

実行をクリックする

プログラムが実行されるが実行結果は エラー になり、プログラムは動作を終了する。

コンソール(プログラムによるメッセージや実行時エラーのメッセージ表示画面)の様子

※ mainメソッドを持たないプログラムについて:他のプログラムと組み合わせる部品としてのプログラムJavaなどプログラミング言語には標準部品として多数のプログラムが提供されているこの様なプログラムを集めたものをクラスライブラリと呼ぶ

1-6.設定不要につき省略

JavaコンパイラはJavaのコードをアプリケーションの構成部品となるclassファイルに変換する開発プログラムである。コンパイラにTest1.javaに記述したコードのJavaのバージョンを設定すると対応するバージョンのJava言語の文法やライブラリについてエラー修正のヒントや自動的コード作成のサポート機能を利用できるようになる。

コンパイラの設定

メニューの プロジェクト → プロパティー から

Java コンパイラー の設定を開く

プロジェクト固有の設定を可能にする(O)をチェックする

コンパイラー準拠レベル(I): を 11 に変更する。

適用して閉じる。

1-7.

エラーの内容を確認して、以下の様にプログラムを修正する。

手順:

public class Test1 の右側に空白を開けて extends Application と追記する。

※class A extends B は A というクラスがBというタイプのクラスであることを宣言するJavaの文法

Application に赤い下線が付く(エラーの指摘)

エラー個所にマウスを重ねてクイックフィックス(自動訂正機能)から Application をインポートします(javafx.application) を選ぶ。

※import javafx.application.Application; の1行が追加される。Javaは import 宣言文でコードで使用するクラス名(ここではApplication)をどのライブラリ(ここではJavaFX)に格納されたクラス名と結びつけるか決める。

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 自動生成されたメソッド・スタブ

}

}

startメソッドのStage型の引数の変数名を後のコードで使用するものに修正する。

arg0primaryStage に修正

※javaFXライブラリのApplicationクラスに必須のコードとして start メソッドが宣言されている。startの対応する { } の内側にアプリケーション起動時の実行内容を記述する。

1-8.

エラーを修正したプログラムを実行する。

実行ボタン(緑の右向き三角)をクリックしてプログラムを実行する。

※実行構成を済ませてあるのでプログラムを実行ボタンを押すだけで実行できる。

保存するリソース(ここでは修正したプログラムファイルのこと)を選ぶで OK を押す。

1-9.設定不要につき省略

実行時エラーが起きる。

module-info.java を次のように修正する。Application クラスの実行に必要なjavaFXのモジュールの controls と graphics を利用する宣言 requiresと javaFXTestパッケージのクラス Test1 を javaFXから呼び出せるように設定する(パソコンのOSのWindowsからTest1を起動できるようにする)。

module javaFXTest {

requires javafx.controls;

requires javafx.graphics;

opens javaFXTest to javafx.graphics;

}

1-10.

プログラムが起動して正常に動作する(エラーが出ない)。

プログラムが起動した後の処理をプログラムしていないので、パソコンの画面上では何も起こらない。

さらにプログラムを止める方法もプログラムしていないので、プログラムは止まらない。

コンソールの停止ボタン(赤い四角)を押してプログラムを強制終了する。

1-11.

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();

}

}

1-12.

実行ボタンを数回押す。右側の実行構成のサブメニューのスイッチが付いた実行ボタンを押すこと※1


プログラムを実行する度にWindowが表示される。

Windowを ×ボタン で消すとプログラムが終了してウインドウが消える(ようにApplictionはプログラムされている)。このプログラムは 実行状態のままxボタンが押されて終了させられるのを待ち構えている。

eclipseのコンソールの 停止ボタン(赤い■) を押してもプログラムは終了しWindowも消える。

※1 ここで使用する実行ボタンに注意。左側のボタンは実行中のプログラムは再起動する。

演習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();

}

}

シーンの背景色を設定する。

シーンs1 がウィンドウに追加された次の行に以下のコードを挿入する。

// シーンの背景色の設定

s1.setFill(Color.AQUA);

Colorがエラーになるので、javafx.scene.paint.Color をインポートするように修正する。

javaFXのウィンドウとシーンについて

JavaFXでのアニメーションおよびビジュアル効果図7-2 参照

プログラムのバックアップ

パッケージ・エクスプローラの Test1.java をコピー&ペーストして Test2.java を作成してかあらTest2.javaを応用1や2を参考にして改造する。

実行構成を新規作成して名前を Test2 に メイン・クラスを javaFXTest.Test2 に設定して実行する。

応用1

AQUAの他の色に背景色を変更する。

Colorまで一旦削除してから Color. とドットを入力してコード補完機能の選択候補から選んで修正し実行ボタンで動作を確認する。

ウィンドウのサイズを正方形以外のサイズにする。あるいは幅や高さを0に設定した場合どうなるか確認する(両方0でもよい)。

応用2

ランダムな幅や高さを設定する。

ランダムな数値を生成するコードの例

// ランダムな値を生成するクラス

Random rnd = new Random();

int w = 200 + rnd.nextInt(100); // 200 to 299

int h = 100 + rnd.nextInt(200); // 100 to 299

Scene s1 = new Scene(root, w, h, false);

ランダムな色を設定する。

RGBカラーをランダムで設定する例( r g b のどれかを r = 0.5; の様に固定値にしたり、+0.5 するなどを試す)

double r = rnd.nextDouble();

double g = rnd.nextDouble();

double b = rnd.nextDouble();

Color c = new Color(r, g, b, 1.0);

s1.setFill(c);

演習3

Test1.javaにコードを追加する。

球体の表示

3Dシーンを撮影するカメラ Camera を配置する。

カメラの前に球体を配置する。

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.0の球体を作成。初期座標は (x,y,z)=(0,0,0)

Sphere sp = new Sphere(1.0);


// グループの要素に sp を追加

root.getChildren().add(sp);

}

}

コードとインデント

プログラムの構造を把握しやすくするため、インデント(字下げ)機能を活用する

オートインデントとオートフォーマット

Ctrl + A でコード全体を選択

Ctrl + Shit + F オートフォーマット

演習4

球体の色の変更

球体の移動

以下のコードで行う。

// 球体のマテリアルを赤色に設定

sp.setMaterial(new PhongMaterial(Color.RED));

// 球体をX軸方向に+0.3移動

sp.setTranslateX(0.3);

実行結果:

動作が確認できたら、ウインドウを閉じる。

(参照)

JavaFX 2D、3Dの座標系の定義

https://torutk.hatenablog.jp/entry/20170813/p1


応用3

Test1 クラス のコード中の 数値 や 色名 を修正して結果を確認する。

変更する色の選択は eclipse のコード補完機能を利用してキーワードを選択するとよい

Color.

このように↑ピリオドまで削除し ピリオドを入力しなおすと補完候補が表示される。

コードの修正例)

// シーンの背景色の設定

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);

コードの修正例の実行結果)




球体を増やすには

Sphere sp2 = new Sphere(1.0);

root.getChildren().add(sp2);

sp2.setTranslateX(0.8);

の様に sp とは別の変数を使用して別の球体を作成して座標を移動させる。

応用4

Test3 クラス を作成する。

パッケージエクスプローラ で Test1.java をコピーして Test3 を作成する。

Test3.java → 右クリック → コピー

→ 右クリック → 貼り付け

※ クラス名が自動的に Test3 に変更されるので、そのまま保存。

実行するには メニュー 実行 → 実行構成 から実行構成を新規作成して

名前Test3

メイン・クラスを javaFXTest.Test3

に設定する。


演習8の準備をついでに行う。

後で使用するためにTest4 クラス を同様にコピーして作成しておく。


Test3.java を改造して3DCGの描画を試す。

使用する3D図形のクラス名の例)

BoxCylinderSphere

CircleLinePolygonRectangleText

球 円筒 立方体 の 追加と回転 の例)

// 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 box2 = new Box(1.0, 1.0, 1.0);

box2.setTranslateZ(0.0);

box2.setTranslateX(1.0);

box2.setMaterial(new PhongMaterial(Color.AZURE));

root.getChildren().add(box2);


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);

実行結果

コードの実行順序とレンダリング結果の比較

図形の描画で前後関係が考慮されていないことが確認できる。

→ 幾つかのオブジェクトを重なる位置に配置。

→ オブジェクトを追加する順番を入れ替えて確認する。

つまり、コードで図形を配置する度に配置前のシーンに重ね塗りをして描画している。


演習5

Test1.java (とTest3.javaがあればそれも)を修正する。

深度判定を有効にする(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);

カメラを遠ざけることでシーン全体が縮小して描画される。

演習7

キーボードやマウスに関する処理をイベント処理という。

マウスを利用するコードの作成

Test1.java を編集する。

マウスでCGを操作するコードを追加する。

マウスのボタンを押した位置を記録(ドラッグ開始のXY座標)する変数x と変数y を用意する。

変数の準備:

class Test1 の内側 で、 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;

});


//マウスホイールのイベント処理

//オブジェクトの位置とカメラからの距離を操作する

root.setOnScroll(e -> {

root.setTranslateZ(root.getTranslateZ() + e.getDeltaY()/100);

});

main メソッドは eclipse の実行ボタンから起動できるがキーボードやマウスの操作に対応するコードはその操作が起きたタイミングで実行する必要がある。

キーボードやマウスを操作したときにイベントが発生して対応するイベント処理のコードが呼び出されるようになっている。

e -> {コード} の文法について:javaの関数インターフェースと呼ばれる記法。ラムダ式や関数式などとも呼ばれる。

setOnMousePressed

setOnMouseDragged

setOnScroll

はイベント処理を行うコードを登録するメソッドで、その引数としてコードを書くことができるようになっている。

この様にコード自体を部品としてコードで処理するスタイルのプログラムが関数型プログラミングの特徴の1つ。

ここまでのコードを一通り記載した例)

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.paint.PhongMaterial;

import javafx.scene.shape.Box;

import javafx.scene.shape.Cylinder;

import javafx.scene.shape.Sphere;

import javafx.scene.text.Font;

import javafx.scene.text.Text;

import javafx.scene.transform.Rotate;

import javafx.scene.transform.Translate;

import javafx.stage.Stage;


public class Test1 extends Application {

double x, y;


@Override

public void start(Stage primaryStage) throws Exception {

// TODO 自動生成されたメソッド・スタブ

// ルートグループを作成

Group root = new Group();

// シーンs1に追加。シーンのサイズは幅500 x 高さ500

Scene s1 = new Scene(root, 500, 500, true);

// ウィンドウにシーンs1を設定

primaryStage.setScene(s1);

primaryStage.show();

// シーンの背景色の設定

s1.setFill(Color.BLACK);

// シーンを撮影するカメラを作成

Camera cam = new PerspectiveCamera(true);

// カメラをZ軸の-5.0に移動(画面の手前側に移動)

cam.setTranslateZ(-15.0);

cam.setFarClip(800);

// カメラをシーンに設定

s1.setCamera(cam);

// 半径1の球体を作成。初期座標は (x,y,z)=(0,0,0)

Sphere sp = new Sphere(0.5);

// グループの要素に sp を追加

root.getChildren().add(sp);

// 球体のマテリアルを赤色に設定

sp.setMaterial(new PhongMaterial(Color.CYAN));

// 球体をX軸方向に+0.3移動

sp.setTranslateZ(5.0);

sp.setTranslateX(-2.0);

sp.setTranslateY(1.0);

// 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 box2 = new Box(1.0, 1.0, 1.0);

box2.setTranslateZ(0.0);

box2.setTranslateX(1.0);

box2.setMaterial(new PhongMaterial(Color.AZURE));

root.getChildren().add(box2);

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);

// シーンの回転量 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;

});

root.setOnScroll(e -> {

root.setTranslateZ(root.getTranslateZ() + e.getDeltaY() / 100);

});

}

}

演習8

Test4.java を編集する。

X座標の値を並べたリストを利用して球体を描画する。

座標データを元に球体を作成しながら配置する処理を以下のコードを追加して行う。

// 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);

});

参照: Java8のStreamを使いこなす

関数インターフェース

x -> { x を利用する処理 }

の部分は ラムダ式 という表記で プログラムを名前を付けずにデータとして他のプログラムに埋め込む際に利用する。


演習9

Test1.java を編集する。

球を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);

});

Javaのストリーム Stream について:

Streamからのデータは取り出すたびに自動的に生成できる。

Stream.iterate はストリームを生成するために使用するメソッド。概念としては数列の 初期値 と続く値を 漸化式 で表現するのと似ている。

ストリームの 初期値 と 値の更新処理 を与えてストリームを定義する。

limit はストリームから取り出す要素の個数を設定するメソッド。

forEach はストリームに対して逐次処理を行うメソッド。

乱数の発生式の説明:

rand.nextDouble() 0.0~1.0の範囲のランダムな値

rand.nextDouble()*2.0 0.0~2.0の範囲のランダムな値

rand.nextDouble()*2.0 -1.0 -1.0~1.0の範囲のランダムな値


演習10

Test2.javaを編集する。

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はデータの選択を行うメソッド

実行例)

Stream の利用例

参照 Java8 stream APIサンプルコード

中間処理を追加する。

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);

});