今年の JavaOne でチェスの駒に扮した Duke が格好良く歩いていた。
JavaFX 3D テクノロジーを駆使したデモアプリケーションが披露されて会場をわかせていた。
とても素敵だったが Maya と言う高価な 3DCG ソフトと 3DViewer (何故か今は動かないらしい)を使って作られているらしい。(未確認です。)
ネット上にも 3D アニメーション作成のチュートリアルやサンプルもまだ見当たらない。
とは言え、JavaFX 8 には標準で 3DAPI が実装されているので近い将来目にすることもあるだろう。
でも、新しい物好きの私は待ちきれないので手探りでグリグリしてみた。
日本語での情報は下記サイトが解りやすくて良いですね。
カメラの円軌道のさせ方は非常に参考になりました。(^^)
// 感謝のコード
IntStream.rangeClosed(1, 1_000_000).forEach(i -> {
System.out.println(“Thanks!”);
});
現在ネット上にある情報をあちことからかき集め、ちゃんと読めない英語の API ドキュメントを格闘しながら Duke を歩かせることに成功した!
サブシーン用のカメラの設定を
PerspectiveCamera subCam = new PerspectiveCamera(true);
とすることによって、確実に座標位置を把握することに成功した。(たぶん・・・)
JavaOne と同じことはできないがボードの上を Duke が落ちないように行ったり来たり歩かせることにした。
今回は前回使っていたプログラムをカメラの設定変更によって少し変更し、
モデルも自前で用意した。
メタセコイアというリーズナブルな 3DCG ソフトを使用して作りました。
ただ、後で解ったことだけど今は Blender という 3DCG ソフトが人気らしい。
Blender は比較的新しい参考書も出ているし、雑誌やムックでも取り上げられてる機会が多い。
メタセコイアは人気はあるけど新しい参考書や雑誌は見ないですね。
どちらにしろ不器用な私には使いこなせません。(>_<。)
話を核心に戻して、3D アニメーションアプリを作るのに必要なものを考えてみました。
まず、手足が動かせる Duke のモデル。
これはパーツを分けてそれぞれ Timeline を使ってアニメーションさせてみました。
次に、チェスボード。 といってもそれらしきものでいいのでこれも簡単に済ませました。
あとはこれらをジムさんが作られた JFX3DModelImporters を使わせていただきインポートします。
後は座標位置を調べて AnimationTimer を使いアニメーションさせています。
ボードから落ちないようにターンさせてます。
ターンの部分も Timeline を使ってます。
こんなに AnimationTimer や Timeline を使って問題ないのかなって思ったりもしますが動いてます。( ̄。 ̄;)
ひたすら行ったり来たしするだけの面白みの無いアプリとなってます。
これでは寂しいので音楽を流すことにしました。
ちょっと画面も寂しいので DUKE WALK と文字オブジェクトモデルも作って追加しました。
せっかくだからこれも動かしたくなりますよね。
オーディオスペクトラムリスナーを実装して文字数の文だけ 0.01 秒ごとにサンプリングしてそれをもとに適当にスケーリングさせて賑やかにしました。
以上、非常にシンプルに作ることができました。
ただ、影を映す方法が解らなかった。
JavaOne のデモも影は無いように見えるので現時点では無理なのかもしれない。
これでも私的には Complete Success! (^^)
完全版の動画は次になります。各種カメラ操作による写り方が理解できます。
それではこのプログラムを載せておきます。
プログラムの詳しい内容は説明しませんが何をやっているかだいたい解るようにコメントをいれてあります。
特に複雑なことはしてないので解らないことはないでしょう。
注意して欲しいのは、まだ JavaFX 触り初めて間もない私があちこちからかき集めた情報と英語の API ドキュメントとにらめっこしてたぶんこれでいいのだろうと作ったものだということです。
間違いがあれば御教示いただければ幸いです。
package jp.yucchi.dukewalk;
import com.interactivemesh.jfx.importer.ImportException;
import com.interactivemesh.jfx.importer.obj.ObjModelImporter;
import java.net.URL;
import java.util.stream.IntStream;
import javafx.animation.AnimationTimer;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.When;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
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.SubScene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TitledPane;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.HBox;
import javafx.scene.media.AudioSpectrumListener;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
public class DukeWalk extends Application implements AudioSpectrumListener {
// 移動
private final DoubleProperty xPos = new SimpleDoubleProperty(0);
private final DoubleProperty yPos = new SimpleDoubleProperty(0);
private final DoubleProperty zPos = new SimpleDoubleProperty(-1_000);
// スケール
private final DoubleProperty sxPos = new SimpleDoubleProperty(1.0);
private final DoubleProperty syPos = new SimpleDoubleProperty(1.0);
private final DoubleProperty szPos = new SimpleDoubleProperty(1.0);
// 回転
private final DoubleProperty rxPos = new SimpleDoubleProperty(0);
private final DoubleProperty ryPos = new SimpleDoubleProperty(0);
private final DoubleProperty rzPos = new SimpleDoubleProperty(0);
// 表示モード
private final BooleanProperty showWireframe = new SimpleBooleanProperty(false);
// 座標
private double anchorAngleX;
private double anchorAngleY;
private final DoubleProperty angleX = new SimpleDoubleProperty(0);
private final DoubleProperty angleY = new SimpleDoubleProperty(0);
private double anchorX;
private double anchorY;
// Duke 反転処理用
private final DoubleProperty turnAngleY = new SimpleDoubleProperty(0);
// Duke 手足アニメーション用
DoubleProperty angleWalk_r_X = new SimpleDoubleProperty(0);
DoubleProperty angleWalk_l_X = new SimpleDoubleProperty(0);
DoubleProperty angleWalk_l_arm = new SimpleDoubleProperty(0);
DoubleProperty angleWalk_r_arm = new SimpleDoubleProperty(0);
// タイムライン
private Timeline dukeAnim;
// カメラ用アニメーションタイマー
private AnimationTimer walkTimer;
// 方位角
private double azimuth;
// 前回時間
private long previousHandledTime;
// 回転速度
private final DoubleProperty azimuthRateProperty = new SimpleDoubleProperty(15);
// カメラ高さ
private final DoubleProperty elevationProperty = new SimpleDoubleProperty(7);
// 半径
private static final double SUBCAMERA_RADIUS = 5_500;
// Duke 反転アニメーション用タイムライン
private Timeline rightTurn;
private Timeline leftTurn;
// BGM
private final Media bgm = new Media(getClass().getResource("music/bgm.mp3").toExternalForm());
// メディアプレーヤー
private MediaPlayer mp_bgm;
private AudioSpectrumListener audioSpectrumListener;
// 文字オブジェクト用メッシュビュー配列
private MeshView[] backMesh;
@Override
public void start(Stage primaryStage) {
Group root = new Group();
Label translateLabel = new Label("Translate");
Label xLabel = new Label("X");
Label yLabel = new Label("Y");
Label zLabel = new Label("Z");
Label scaleLabel = new Label("Scale "); // 良い子はこんなことしちゃだめよ!
Label sxLabel = new Label("X");
Label syLabel = new Label("Y");
Label szLabel = new Label("Z");
Label rotateLabel = new Label("Rotate "); // 良い子はこんなことしちゃだめよ!
Label rxLabel = new Label("X");
Label ryLabel = new Label("Y");
Label rzLabel = new Label("Z");
Label drawModeLabel = new Label("Wireframe");
Label resetLabel = new Label(" Reset"); // 良い子はこんなことしちゃだめよ!
Label walkLabel = new Label(" Duke Walk!"); // 良い子はこんなことしちゃだめよ!
Label stopLabel = new Label(" Duke Stop!"); // 良い子はこんなことしちゃだめよ!
Slider xSlider = new Slider(-3000, 3000, 0);
Slider ySlider = new Slider(-3000, 3000, 0);
Slider zSlider = new Slider(-5000, 5000, 0);
Slider sxSlider = new Slider(0.01, 10, 1.0);
Slider sySlider = new Slider(0.01, 10, 1.0);
Slider szSlider = new Slider(0.01, 10, 1.0);
Slider rxSlider = new Slider(-360, 360, 0);
Slider rySlider = new Slider(-360, 360, 0);
Slider rzSlider = new Slider(-360, 360, 0);
xSlider.setPrefSize(533, 0);
ySlider.setPrefSize(533, 0);
zSlider.setPrefSize(533, 0);
sxSlider.setPrefSize(533, 0);
sySlider.setPrefSize(533, 0);
szSlider.setPrefSize(533, 0);
rxSlider.setPrefSize(533, 0);
rySlider.setPrefSize(533, 0);
rzSlider.setPrefSize(533, 0);
xSlider.setShowTickLabels(true);
xSlider.setMajorTickUnit(500);
ySlider.setShowTickLabels(true);
ySlider.setMajorTickUnit(500);
zSlider.setShowTickLabels(true);
zSlider.setMajorTickUnit(500);
sxSlider.setShowTickLabels(true);
sxSlider.setMajorTickUnit(2.0);
sySlider.setShowTickLabels(true);
sySlider.setMajorTickUnit(2.0);
szSlider.setShowTickLabels(true);
szSlider.setMajorTickUnit(2.0);
rxSlider.setShowTickLabels(true);
rxSlider.setMajorTickUnit(90);
rySlider.setShowTickLabels(true);
rySlider.setMajorTickUnit(90);
rzSlider.setShowTickLabels(true);
rzSlider.setMajorTickUnit(90);
CheckBox meshCheckBox = new CheckBox("Wireframe");
Button resetBtn = new Button();
resetBtn.setText("Reset");
Button walkBtn = new Button();
walkBtn.setText("Walk!");
Button stopBtn = new Button();
stopBtn.setText("Stop!");
final Accordion accordion = new Accordion();
final TitledPane translatePane = new TitledPane("Camera Translate", new HBox(30, translateLabel, xLabel, xSlider, yLabel, ySlider, zLabel, zSlider));
final TitledPane scalePane = new TitledPane("Camera Scale", new HBox(30, scaleLabel, sxLabel, sxSlider, syLabel, sySlider, szLabel, szSlider));
final TitledPane rotatePane = new TitledPane("Camera Rotate", new HBox(30, rotateLabel, rxLabel, rxSlider, ryLabel, rySlider, rzLabel, rzSlider));
final TitledPane drawModePane = new TitledPane("DrawMode & Reset & Duke Walk!", new HBox(20, drawModeLabel, meshCheckBox, resetLabel, resetBtn, walkLabel, walkBtn, stopLabel, stopBtn));
translatePane.setAnimated(true);
scalePane.setAnimated(true);
rotatePane.setAnimated(true);
drawModePane.setAnimated(true);
accordion.getPanes().addAll(translatePane, scalePane, rotatePane, drawModePane);
accordion.setExpandedPane(translatePane);
root.getChildren().add(accordion);
accordion.setLayoutX(17);
accordion.setLayoutY(873);
Scene scene = new Scene(root, 1910, 1020, true);
scene.setFill(Color.PINK);
PerspectiveCamera cam = new PerspectiveCamera(false);
scene.setCamera(cam);
root.getChildren().add(cam);
// サブシーン用のサブカメラ
PerspectiveCamera subCam = new PerspectiveCamera(true);
// Field of View
subCam.setFieldOfView(45.5);
// Clipping Planes
subCam.setNearClip(1.0);
subCam.setFarClip(1_000_000);
AnchorPane subRoot = new AnchorPane();
subRoot.setBackground(Background.EMPTY);
Group allGroup = new Group();
// 3D モデルをインポート
// Duke
ObjModelImporter dukeImporter = new ObjModelImporter();
// Bord
ObjModelImporter baseImporter = new ObjModelImporter();
// 文字オブジェクト
ObjModelImporter backImporter = new ObjModelImporter();
try {
URL objUrl = this.getClass().getResource("Duke/duke.obj");
dukeImporter.read(objUrl);
} catch (ImportException e) {
// G'catch! :p
e.printStackTrace();
return;
}
MeshView[] dukeMesh = dukeImporter.getImport();
dukeImporter.close();
Group dukeGroup = new Group();
for (MeshView _dukeMesh : dukeMesh) {
dukeGroup.getChildren().addAll(_dukeMesh);
_dukeMesh.drawModeProperty().bind(new When(showWireframe).then(DrawMode.LINE).otherwise(DrawMode.FILL));
}
try {
URL objUrl = this.getClass().getResource("Duke_base/base.obj");
baseImporter.read(objUrl);
} catch (ImportException e) {
// G'catch! :p
e.printStackTrace();
return;
}
MeshView[] baseMesh = baseImporter.getImport();
baseImporter.close();
Group baseGroup = new Group();
for (MeshView _baseMesh : baseMesh) {
baseGroup.getChildren().addAll(_baseMesh);
_baseMesh.drawModeProperty().bind(new When(showWireframe).then(DrawMode.LINE).otherwise(DrawMode.FILL));
}
try {
URL objUrl = this.getClass().getResource("back_duke/back_duke.obj");
backImporter.read(objUrl);
} catch (ImportException e) {
// G'catch! :p
e.printStackTrace();
return;
}
backMesh = backImporter.getImport();
backImporter.close();
Group backGroup = new Group();
for (MeshView _backMesh : backMesh) {
backGroup.getChildren().addAll(_backMesh);
_backMesh.drawModeProperty().bind(new When(showWireframe).then(DrawMode.LINE).otherwise(DrawMode.FILL));
}
allGroup.getChildren().addAll(dukeGroup, baseGroup, backGroup);
// Duke 歩行ターニングポイント
// オフセットマージン (適当に設定)
int offsetMargin = (int) (dukeGroup.getLayoutBounds().getDepth() / 6);
// ターニングポイント -Z
int bordTurnPoint = (int) (baseGroup.getLayoutBounds().getMinZ() - dukeGroup.getLayoutBounds().getMinZ());
// ターニングポイント +Z
int bordEndPoint = (int) (baseGroup.getLayoutBounds().getMaxZ() - dukeGroup.getLayoutBounds().getDepth() / 2 - offsetMargin);
// ボードを非表示
baseGroup.setVisible(false);
// 文字を非表示
backGroup.setVisible(false);
// 環境光設定
AmbientLight ambient = new AmbientLight();
ambient.setColor(Color.rgb(150, 150, 150, 0.6));
ambient.getScope().addAll(allGroup);
// ポイントライト設定
PointLight point = new PointLight();
point.setColor(Color.rgb(255, 255, 255, 1.0));
point.setLayoutX(-700);
point.setLayoutY(-1_000);
point.setTranslateZ(-1_200);
point.getScope().addAll(allGroup);
subRoot.getChildren().addAll(allGroup, subCam, ambient, point);
// Create SubScene
SubScene subScene = new SubScene(subRoot, 1920, 860, true, SceneAntialiasing.BALANCED);
subScene.setFill(Color.ALICEBLUE);
subScene.setCamera(subCam);
root.getChildren().addAll(subScene);
// マウスでモデル操作用
Rotate xRotate;
Rotate yRotate;
allGroup.getTransforms().setAll(
xRotate = new Rotate(0, Rotate.X_AXIS),
yRotate = new Rotate(0, Rotate.Y_AXIS)
);
xRotate.angleProperty().bind(angleX);
yRotate.angleProperty().bind(angleY);
subScene.setOnMousePressed(event -> {
anchorX = event.getSceneX();
anchorY = event.getSceneY();
anchorAngleX = angleX.get();
anchorAngleY = angleY.get();
});
subScene.setOnMouseDragged(event -> {
angleX.set(anchorAngleX - (anchorY - event.getSceneY()));
angleY.set(anchorAngleY + anchorX - event.getSceneX());
});
// サブカメラ操作用
// 移動
xSlider.valueProperty().bindBidirectional(xPos);
subCam.translateXProperty().bind(xPos);
ySlider.valueProperty().bindBidirectional(yPos);
subCam.translateYProperty().bind(yPos);
zSlider.valueProperty().bindBidirectional(zPos);
subCam.translateZProperty().bind(zPos);
// 拡大縮小
sxSlider.valueProperty().bindBidirectional(sxPos);
subCam.scaleXProperty().bind(sxPos);
sySlider.valueProperty().bindBidirectional(syPos);
subCam.scaleYProperty().bind(syPos);
szSlider.valueProperty().bindBidirectional(szPos);
subCam.scaleZProperty().bind(szPos);
// 回転
Rotate rxRotate;
Rotate ryRotate;
Rotate rzRotate;
subCam.getTransforms().setAll(
rxRotate = new Rotate(0, Rotate.X_AXIS),
ryRotate = new Rotate(0, Rotate.Y_AXIS),
rzRotate = new Rotate(0, Rotate.Z_AXIS)
);
rxRotate.angleProperty().bind(rxPos);
ryRotate.angleProperty().bind(ryPos);
rzRotate.angleProperty().bind(rzPos);
rxSlider.valueProperty().bindBidirectional(rxPos);
rySlider.valueProperty().bindBidirectional(ryPos);
rzSlider.valueProperty().bindBidirectional(rzPos);
// Duke ターン用
Rotate tyRotate;
dukeGroup.getTransforms().setAll(
tyRotate = new Rotate(0, Rotate.Y_AXIS)
);
tyRotate.angleProperty().bind(turnAngleY);
// 表示モード
meshCheckBox.selectedProperty().bindBidirectional(showWireframe);
// リセット
resetBtn.setOnAction((ActionEvent event) -> {
// Icon 変更
Image myIcon = new Image(this.getClass().getResource("icon/duke_3.png").toString());
primaryStage.getIcons().add(myIcon);
// 音楽停止
if (mp_bgm != null) {
mp_bgm.stop();
}
if (walkTimer != null || dukeAnim != null) {
dukeAnim.stop();
walkTimer.stop();
if (leftTurn != null) {
leftTurn.stop();
}
if (rightTurn != null) {
rightTurn.stop();
}
}
// ボード非表示
baseGroup.setVisible(false);
// 文字を非表示
backGroup.setVisible(false);
// Duke の位置を初期化
dukeGroup.setTranslateZ(0);
// Duke の向きを初期化
turnAngleY.set(0);
// 設定初期化
xPos.set(0);
yPos.set(0);
zPos.set(-1_000);
angleWalk_r_X.set(0);
angleWalk_l_X.set(0);
angleWalk_l_arm.set(0);
angleWalk_r_arm.set(0);
anchorAngleX = 0;
anchorAngleY = 0;
angleX.set(0);
angleY.set(0);
sxPos.set(1.0);
syPos.set(1.0);
szPos.set(1.0);
rxPos.set(0);
ryPos.set(0);
rzPos.set(0);
});
// Duke Walk 開始
walkBtn.setOnAction((ActionEvent event) -> {
// Icon 変更
Image myIcon = new Image(this.getClass().getResource("icon/duke_1.png").toString());
primaryStage.getIcons().add(myIcon);
// 音楽再生、スペクトラムリスナー実装
audioSpectrumListener = this;
mp_bgm = new MediaPlayer(bgm);
mp_bgm.setAudioSpectrumListener(audioSpectrumListener);
mp_bgm.setAudioSpectrumInterval(0.01);
mp_bgm.setAudioSpectrumNumBands(8);
mp_bgm.setVolume(0.9);
mp_bgm.setAutoPlay(true);
mp_bgm.setCycleCount(MediaPlayer.INDEFINITE);
// ボードを表示
baseGroup.setVisible(true);
// 文字を表示
backGroup.setVisible(true);
// Duke の位置を初期化
dukeGroup.setTranslateZ(0);
// Duke の向きを初期化
turnAngleY.set(0);
// 設定初期化
azimuth = 0;
previousHandledTime = 0L;
anchorAngleX = 0;
anchorAngleY = 0;
angleX.set(0);
angleY.set(0);
xPos.set(0);
yPos.set(0);
zPos.set(-1_200);
sxPos.set(1.0);
syPos.set(1.0);
szPos.set(1.0);
rxPos.set(0);
ryPos.set(0);
rzPos.set(0);
// Duke モデル手足動作用
Rotate r_legRotate;
Rotate l_legRotate;
Rotate r_armRotate;
Rotate l_armRotate;
dukeMesh[2].getTransforms().setAll(
r_legRotate = new Rotate(0, Rotate.X_AXIS));
dukeMesh[3].getTransforms().setAll(
l_legRotate = new Rotate(0, Rotate.X_AXIS));
dukeMesh[4].getTransforms().setAll(
r_armRotate = new Rotate(0, Rotate.Z_AXIS));
dukeMesh[5].getTransforms().setAll(
l_armRotate = new Rotate(0, Rotate.X_AXIS));
r_legRotate.angleProperty().bind(angleWalk_r_X);
l_legRotate.angleProperty().bind(angleWalk_l_X);
l_armRotate.angleProperty().bind(angleWalk_l_arm);
r_armRotate.angleProperty().bind(angleWalk_r_arm);
// 歩行モーション
dukeAnim = new Timeline(
new KeyFrame(
new Duration(500),
new KeyValue(angleWalk_r_X, -25),
new KeyValue(angleWalk_l_X, 25),
new KeyValue(angleWalk_l_arm, -20),
new KeyValue(angleWalk_r_arm, 12, Interpolator.EASE_BOTH)
),
new KeyFrame(
new Duration(1_000),
new KeyValue(angleWalk_r_X, 0),
new KeyValue(angleWalk_l_X, 0),
new KeyValue(angleWalk_l_arm, 0),
new KeyValue(angleWalk_r_arm, 0, Interpolator.EASE_BOTH)
),
new KeyFrame(
new Duration(1_500),
new KeyValue(angleWalk_r_X, 25),
new KeyValue(angleWalk_l_X, -25),
new KeyValue(angleWalk_l_arm, 20),
new KeyValue(angleWalk_r_arm, 12, Interpolator.EASE_BOTH)
),
new KeyFrame(
new Duration(2_000),
new KeyValue(angleWalk_r_X, 0),
new KeyValue(angleWalk_l_X, 0),
new KeyValue(angleWalk_l_arm, 0),
new KeyValue(angleWalk_r_arm, 0, Interpolator.EASE_BOTH)
)
);
dukeAnim.setCycleCount(Timeline.INDEFINITE);
dukeAnim.play();
// 歩行
walkTimer = new AnimationTimer() {
@Override
public void handle(long now) {
update(now);
}
private void update(long now) {
if (previousHandledTime == 0) {
previousHandledTime = now;
return;
}
// サブカメラを半径 SUBCAMERA_RADIUS の円の中心むけて回転撮影する。
azimuth += azimuthRateProperty.get() * (now - previousHandledTime) / 1_000_000_000;
previousHandledTime = now;
xPos.set(Math.sin(Math.toRadians(azimuth)) * SUBCAMERA_RADIUS);
zPos.set(-1 * Math.cos(Math.toRadians(azimuth)) * SUBCAMERA_RADIUS);
yPos.set(-1 * Math.sin(Math.toRadians(elevationProperty.getValue())) * SUBCAMERA_RADIUS);
ryPos.set(-1 * azimuth);
// 歩行制御
if (dukeGroup.getTranslateZ() > bordTurnPoint && turnAngleY.get() == 0) {
dukeGroup.setTranslateZ(dukeGroup.getTranslateZ() + -5);
if (dukeGroup.getTranslateZ() < bordTurnPoint + 1) {
rightTurn();
}
}
// 歩行制御
if (dukeGroup.getTranslateZ() < bordEndPoint && turnAngleY.get() == 180) {
dukeGroup.setTranslateZ(dukeGroup.getTranslateZ() + 5);
if (dukeGroup.getTranslateZ() > bordEndPoint - 1) {
leftTurn();
}
}
}
// 歩行右反転処理
private void rightTurn() {
rightTurn = new Timeline(
new KeyFrame(
new Duration(700),
new KeyValue(turnAngleY, 180, Interpolator.EASE_BOTH)
));
rightTurn.play();
}
// 歩行左反転処理
private void leftTurn() {
leftTurn = new Timeline(
new KeyFrame(
new Duration(700),
new KeyValue(turnAngleY, 0, Interpolator.EASE_BOTH)
));
leftTurn.play();
}
};
walkTimer.start();
});
// 歩行ストップ
stopBtn.setOnAction((ActionEvent event) -> {
// Icon 変更
Image myIcon = new Image(this.getClass().getResource("icon/duke_2.png").toString());
primaryStage.getIcons().add(myIcon);
// 音楽停止
if (mp_bgm != null) {
mp_bgm.stop();
}
if (walkTimer != null || dukeAnim != null) {
dukeAnim.stop();
walkTimer.stop();
if (leftTurn != null) {
leftTurn.stop();
}
if (rightTurn != null) {
rightTurn.stop();
}
}
});
// Icon 設定
Image myIcon = new Image(this.getClass().getResource("icon/cludia_icon.png").toString());
primaryStage.getIcons().add(myIcon);
primaryStage.setTitle("Duke Walk");
primaryStage.setScene(scene);
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
@Override
public void spectrumDataUpdate(double timestamp, double duration, float[] magnitudes, float[] phases) {
// バックの文字オブジェクトのスケーリング
IntStream.range(0, magnitudes.length).forEach(i -> {
backMesh[i].setScaleX((magnitudes[i] + 60.01) * 0.1 + 1.0);
backMesh[i].setScaleY((magnitudes[i] + 60.01) * 0.1 + 1.0);
backMesh[i].setScaleZ((magnitudes[i] + 60.01) * 0.1 + 1.0);
});
}
}