2019年版

8月5日(月)

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

内容:

・JavaFX による3DCG

・関数型プログラミング

3DCGとJava

3DCGをJavaでプログラムするためのフレームワーク JavaFX を利用する。

JavaFX 3D: Javaの拡張ライブラリJavaFX の 3D機能

3DCGをJavaでプログラムする方法としては他にも、

Java 3D: Javaの標準のグラフィックスライブラリを利用

JOGL: Javaから OpenGL(CGプログラミングの共通規格) を利用

がある。

前半で

・ウインドウの表示

・3D図形の表示

・マウスクリックの処理

を扱う。

ボタン操作や選択メニューを利用するプログラムは扱わない。

後半で、関数型プログラミングによる3DCGを扱う。

AndroidとJava

AndroidスマートフォンのアプリをJavaで開発できる。

Androidのアプリ開発には、Android用のAPIと開発ツール(Android SDK)が必要。

今回の演習で作成するプログラムはAndroidでは利用できない。

開発環境の準備

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

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

実習室PCのファイル共有フォルダ、デスクトップの「レポートフォルダ」から以下の2つのフォルダをコピーする

(コピー元)

小橋 一秀 → javafx-sdk-11.0.2

小橋 一秀 → javaFXTest

(コピー先)

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

演習1

サンプルコードによる開発環境eclipseの動作確認

1.

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

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

次へ をクリック

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

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

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

完了 をクリック

2.

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

プロジェクト javaFXTest

パッケージ javaFXTest

クラス Test1

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

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

インポートしたパッケージjavaFXTestのクラスTest1が表示される。実行内容はまだ無い状態。

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

※ mainメソッドを持たないプログラムについて:

他のプログラムと組み合わせる部品としてのプログラム

Javaなどプログラミング言語には標準部品として多数のプログラムが提供されている

この様なプログラムを集めたものをクラスライブラリと呼ぶ

3.

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

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

3-2 名前の欄の 新規構成 を消して Test1 と入力する

3-3 プロジェクト を javaFXTest に更新する。

3-4 メイン・クラス javaFXTest.Test1 に入力

3-5 (X)= 引数 のタブをクリックする

VM 引数: に以下をコピー&ペーストして設定する。

--module-path=D:\javafx-sdk-11.0.2\lib

--add-modules=javafx.base

--add-modules=javafx.controls

--add-modules=javafx.fxml

--add-modules=javafx.graphics

--add-modules=javafx.media

--add-modules=javafx.swing

--add-modules=javafx.web

3-6 実行をクリック

プログラムが実行される。

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

コンソールの様子

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

}

}

startメソッドのStage型の引数の変数名を修正する。

arg0primaryStage に修正

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

}

}

コードとインデント

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

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

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

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

演習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 マテリアル(色・テクスチャ)

(参照)

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

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

応用2

Test2 クラス を作成する。

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

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

→ 右クリック → 貼り付け

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

コードを追加する。

実行する際は、

メニュー 実行 → 実行構成 の設定が必要

設定内容:

フィルター欄にある

Javaアプリケーション の中の(下の階層の) Test1 をマウスで右クリックして

複製 して Test1(1) を作る。

Test1(1)を選び、

名前Test2

メイン タブのメイン・クラスを javaFXTest.Test2

に変更する。

使用する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

深度判定を有効にする。(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座標)する変数x と変数y を用意する。

変数の準備:

class Test2 の内側 で、 start メソッドの { } の外側で宣言する。※1

double x,y;

以下のコードはstart メソッドの { } の内側に続けて追記する。※2

※1 ※2 それぞれメンバ変数(メソッド変数)、ローカル変数という。メンバ変数はオブジェクトの存在中(今回のプログラムではアプリケーションの実行中)利用可能で、ローカル変数はメソッドの実行中利用可能。ローカル変数はメソッド終了後は利用できなくなる。

それぞれの図形オブジェクトを追加したグループ 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);

マウスボタンのイベント処理※3

// マウスイベントのハンドラを登録

root.setOnMousePressed(e -> {

// マウスがクリックされたときの位置を記録

this.x = e.getSceneX();

this.y = e.getSceneY();

});

マウスドラッグのイベント処理※4

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;

});

※3 ※4 プログラムでキーボードやマウスに関する処理はイベント処理という。

main メソッドは eclipse の実行ボタンから起動できるが、キーボードやマウスの操作に対応するコードは、その操作が起きたタイミングで実行する必要がある。キーボードやマウスを操作したときにイベントが発生し、対応するイベント処理のコードが呼び出されるようになっている。

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

応用5

オブジェクトの位置とカメラからの距離をマウスホイールで操作する。

root.setOnScroll(e -> {

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

});

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

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

});

}

}

実験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 や 他の図形 関連のコードは削除する。

※ 削除するコードの判別が難しい場合は、図形をシーンに追加する以下のコードを見つけ、1行ずつコメントアウトするとよい。

//root.getChildren().add

コメントアウトする例。

以下を追加する。

// 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 を利用する処理 }

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

演習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 について

データはストリームから取り出すたびに生成される。その為、必要に応じた個数のデータを用意できる。

Stream.iterate はストリームを生成するために使用するメソッド。※5

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

※5 概念としては数列の 初期 と 漸化式 と似ている。

limit はストリームに対して要素の個数を設定するメソッド。

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

配列やListでは初期化時に要素の数を決める必要がある。要素0の配列やリストを定義することも可能。

さらにデータの追加削除が可能である。

しかし、プログラムの実行時にデータが不足した際にはデータを追加するコードを実行する必要がある。

対してStreamの定義と初期化においてはデータの個数は必要ない。

利点として、無限のデータ列を仮定して データが続く限り処理を続ける というコードを書くことができる。

※乱数の発生式の説明

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 の利用例

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

});