はじめての JavaFX 3D

今回はちょっと背伸びをして、3D を試してみたいと思います。

先日、シリンダーをちょこっと作ってみて割と簡単に作れるんだと思い調子ぶっこく私です。

実は Getting Started with JavaFX 3D Graphics と言うチュートリアルが出ました。

しかし、バリバリの英語なので良く解りません。

いつものことですがそれは置いといて、あーでもない、こーでもない、と JavaFX 3D API と戯れてみました。

そういうことなので適当なことをいつもと同じようにしてます。(^_^;)

今回は下の画像のようなものを作って、スライダーでグリグリ動かせるようにしてみました。

1

2

3

4

また、X軸とY軸だけですけどレイアウトの変更もスライダーでできるようにしてみました。

ついでにライトと反射光も設定できるようにしました。

リセットスイッチとカメラ関係もと思いましたがめんどくさくなってきたので止めにしました。(ヲヒ

それではいい加減で適当なプログラムですが載せておきますね。

間違い等ありましたら優しく御教示くださいませ!

package jp.yucchi.cylinder_3d;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Cylinder_3D extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        // FXMLDocumentController に Scene を渡す。
        // もっとスマートな方法があるかもしれない。
        FXMLLoader loader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml"));
        Parent root = (Parent) loader.load();
        FXMLDocumentController c = loader.getController();
        Scene scene = new Scene(root);
        c.setThisScene(scene);

//        // こっちでも問題は無い。
//        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
//        Scene scene = new Scene(root);
//        PerspectiveCamera cam = new PerspectiveCamera(false);
//        scene.setCamera(cam);
        stage.setTitle("はじめての JavaFX 3D");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

package jp.yucchi.cylinder_3d;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.AmbientLight;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.control.TitledPane;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;

public class FXMLDocumentController implements Initializable {

    @FXML
    private AnchorPane ap;

    @FXML
    private AnchorPane ap_3d;

    @FXML
    private TitledPane Transforms;

    @FXML
    private TitledPane Layout;

    @FXML
    private TitledPane Light;

    @FXML
    private Slider Transform_X;

    @FXML
    private Slider Transform_Y;

    @FXML
    private Slider Transform_Z;

    @FXML
    private Slider Layout_X;

    @FXML
    private Slider Layout_Y;

    @FXML
    private ComboBox DiffuseColorCombo;

    @FXML
    private ComboBox SpecularColorCombo;

    @FXML
    private ComboBox AmbientLightCombo;

    @FXML
    private ComboBox PointLightCombo;

    @FXML
    private Slider Ambient_slider;

    @FXML
    private Slider Point_slider;

    private Color diffuseColor;

    private Color specularColor;

    private AmbientLight ambient;

    private PointLight point;

    @FXML
    public void handleDiffuse(ActionEvent event) {

        switch (DiffuseColorCombo.getValue()) {
            case "WHITE":
                diffuseColor = Color.WHITE;
                break;
            case "AQUA":
                diffuseColor = Color.AQUA;
                break;
            case "YELLOW":
                diffuseColor = Color.YELLOW;
                break;
            case "RED":
                diffuseColor = Color.RED;
                break;
            case "PINK":
                diffuseColor = Color.PINK;
                break;
            case "VIOLET":
                diffuseColor = Color.VIOLET;
                break;
            case "GREEN":
                diffuseColor = Color.GREEN;
                break;
            case "ORANGE":
                diffuseColor = Color.ORANGE;
                break;
            case "BLACK":
                diffuseColor = Color.BLACK;
                break;
            default:
                diffuseColor = Color.GHOSTWHITE;
        }

        material.setDiffuseColor(diffuseColor);

    }

    @FXML
    public void handleSpecular(ActionEvent event) {

        switch (SpecularColorCombo.getValue()) {
            case "WHITE":
                specularColor = Color.WHITE;
                break;
            case "AQUA":
                specularColor = Color.AQUA;
                break;
            case "YELLOW":
                specularColor = Color.YELLOW;
                break;
            case "RED":
                specularColor = Color.RED;
                break;
            case "PINK":
                specularColor = Color.PINK;
                break;
            case "VIOLET":
                specularColor = Color.VIOLET;
                break;
            case "GREEN":
                specularColor = Color.GREEN;
                break;
            case "ORANGE":
                specularColor = Color.ORANGE;
                break;
            case "BLACK":
                specularColor = Color.BLACK;
                break;
            default:
                specularColor = Color.GHOSTWHITE;
        }

        material.setSpecularColor(specularColor);

    }

    @FXML
    public void handleAmbientLight(ActionEvent event) {

        switch (AmbientLightCombo.getValue()) {
            case "WHITE":
                ambient.setColor(Color.rgb(255, 255, 255, Ambient_slider.getValue()));
                break;
            case "AQUA":
                ambient.setColor(Color.rgb(0, 255, 255, Ambient_slider.getValue()));
                break;
            case "YELLOW":
                ambient.setColor(Color.rgb(255, 255, 0, Ambient_slider.getValue()));
                break;
            case "RED":
                ambient.setColor(Color.rgb(255, 0, 0, Ambient_slider.getValue()));
                break;
            case "PINK":
                ambient.setColor(Color.rgb(255, 192, 203, Ambient_slider.getValue()));
                break;
            case "VIOLET":
                ambient.setColor(Color.rgb(238, 130, 238, Ambient_slider.getValue()));
                break;
            case "GREEN":
                ambient.setColor(Color.rgb(0, 128, 0, Ambient_slider.getValue()));
                break;
            case "ORANGE":
                ambient.setColor(Color.rgb(255, 165, 0, Ambient_slider.getValue()));
                break;
            case "BLACK":
                ambient.setColor(Color.rgb(0, 0, 0, Ambient_slider.getValue()));
                break;
            default:
                ambient.setColor(Color.rgb(184, 134, 11, Ambient_slider.getValue()));
        }

    }

    @FXML
    public void handlePointtLight(ActionEvent event) {

        switch (PointLightCombo.getValue()) {
            case "WHITE":
                point.setColor(Color.rgb(255, 255, 255, Point_slider.getValue()));
                break;
            case "AQUA":
                point.setColor(Color.rgb(0, 255, 255, Point_slider.getValue()));
                break;
            case "YELLOW":
                point.setColor(Color.rgb(255, 255, 0, Point_slider.getValue()));
                break;
            case "RED":
                point.setColor(Color.rgb(255, 0, 0, Point_slider.getValue()));
                break;
            case "PINK":
                point.setColor(Color.rgb(255, 192, 203, Point_slider.getValue()));
                break;
            case "VIOLET":
                point.setColor(Color.rgb(238, 130, 238, Point_slider.getValue()));
                break;
            case "GREEN":
                point.setColor(Color.rgb(0, 128, 0, Point_slider.getValue()));
                break;
            case "ORANGE":
                point.setColor(Color.rgb(255, 165, 0, Point_slider.getValue()));
                break;
            case "BLACK":
                point.setColor(Color.rgb(0, 0, 0, Point_slider.getValue()));
                break;
            default:
                point.setColor(Color.rgb(184, 134, 11, Point_slider.getValue()));
        }

    }

    private Scene scene;
    private PerspectiveCamera cam;
    private Cylinder cylinder;
    private PhongMaterial material;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // 3Dオブジェクトを生成。
        Shape3D cl = make3DObject(500, 350, 80, 250);
        // 周囲の光源オブジェクトを定義します。
        ambient = new AmbientLight();
        ambient.setColor(Color.rgb(184, 134, 11, 0.5));

        // 点光源を設定。
        point = new PointLight();
        point.setColor(Color.rgb(255, 255, 255, 1));
        // レイアウト設定。
        point.setLayoutX(400);
        point.setLayoutY(100);
        point.setTranslateZ(-1100);
        point.getScope().add(cl);
        ap_3d.getChildren().addAll(cl, point, ambient);

        // これより設定値変更処理
        Transform_X.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            cylinder.getTransforms().add(new Rotate(newValue.doubleValue() - oldValue.doubleValue(), Rotate.X_AXIS));
        });

        Transform_Y.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            cylinder.getTransforms().add(new Rotate(newValue.doubleValue() - oldValue.doubleValue(), Rotate.Y_AXIS));
        });

        Transform_Z.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            cylinder.getTransforms().add(new Rotate(newValue.doubleValue() - oldValue.doubleValue(), Rotate.Z_AXIS));
        });

        Layout_X.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            cylinder.setLayoutX(newValue.doubleValue());
        });

        Layout_Y.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            cylinder.setLayoutY(newValue.doubleValue());
        });

        Ambient_slider.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            if (AmbientLightCombo.getValue() != null) {
                switch (AmbientLightCombo.getValue()) {
                    case "WHITE":
                        ambient.setColor(Color.rgb(255, 255, 255, newValue.doubleValue()));
                        break;
                    case "AQUA":
                        ambient.setColor(Color.rgb(0, 255, 255, newValue.doubleValue()));
                        break;
                    case "YELLOW":
                        ambient.setColor(Color.rgb(255, 255, 0, newValue.doubleValue()));
                        break;
                    case "RED":
                        ambient.setColor(Color.rgb(255, 0, 0, newValue.doubleValue()));
                        break;
                    case "PINK":
                        ambient.setColor(Color.rgb(255, 192, 203, newValue.doubleValue()));
                        break;
                    case "VIOLET":
                        ambient.setColor(Color.rgb(238, 130, 238, newValue.doubleValue()));
                        break;
                    case "GREEN":
                        ambient.setColor(Color.rgb(0, 128, 0, newValue.doubleValue()));
                        break;
                    case "ORANGE":
                        ambient.setColor(Color.rgb(255, 165, 0, newValue.doubleValue()));
                        break;
                    case "BLACK":
                        ambient.setColor(Color.rgb(0, 0, 0, newValue.doubleValue()));
                        break;
                    default:
                        ambient.setColor(Color.rgb(184, 134, 11, newValue.doubleValue()));
                }
            } else {
                ambient.setColor(Color.rgb(184, 134, 11, newValue.doubleValue()));
            }
        });

        Point_slider.valueProperty().addListener((ObservableValue observable, Number oldValue, Number newValue) -> {
            if (PointLightCombo.getValue() != null) {
                switch (PointLightCombo.getValue()) {
                    case "WHITE":
                        point.setColor(Color.rgb(255, 255, 255, newValue.doubleValue()));
                        break;
                    case "AQUA":
                        point.setColor(Color.rgb(0, 255, 255, newValue.doubleValue()));
                        break;
                    case "YELLOW":
                        point.setColor(Color.rgb(255, 255, 0, newValue.doubleValue()));
                        break;
                    case "RED":
                        point.setColor(Color.rgb(255, 0, 0, newValue.doubleValue()));
                        break;
                    case "PINK":
                        point.setColor(Color.rgb(255, 192, 203, newValue.doubleValue()));
                        break;
                    case "VIOLET":
                        point.setColor(Color.rgb(238, 130, 238, newValue.doubleValue()));
                        break;
                    case "GREEN":
                        point.setColor(Color.rgb(0, 128, 0, newValue.doubleValue()));
                        break;
                    case "ORANGE":
                        point.setColor(Color.rgb(255, 165, 0, newValue.doubleValue()));
                        break;
                    case "BLACK":
                        point.setColor(Color.rgb(0, 0, 0, newValue.doubleValue()));
                        break;
                    default:
                        point.setColor(Color.rgb(255, 255, 255, newValue.doubleValue()));
                }
            } else {
                point.setColor(Color.rgb(255, 255, 255, newValue.doubleValue()));
            }
        });

    }

    private Shape3D make3DObject(double x, double y, double radius, double height) {
        // シリンダーオブジェクトを生成。
        cylinder = new Cylinder(radius, height);
        // フォンシェーディングを設定。
        material = new PhongMaterial();
        // テクスチャを貼る。
        Image diffuseMap = new Image(getClass().getResource("Duke.jpg").toString());
        material.setDiffuseMap(diffuseMap);
        cylinder.setMaterial(material);
        // 散乱光による色の設定
        material.setDiffuseColor(Color.GHOSTWHITE);
        // スペキュラカラー(反射光の色)の設定。
        material.setSpecularColor(Color.BLACK);
        cylinder.setMaterial(material);
        cylinder.setLayoutX(x);
        cylinder.setLayoutY(y);
        cylinder.getTransforms().add(new Rotate(0, Rotate.X_AXIS));
        cylinder.getTransforms().add(new Rotate(0, Rotate.Y_AXIS));
        cylinder.getTransforms().add(new Rotate(0, Rotate.Z_AXIS));
        return cylinder;

    }

    void setThisScene(Scene scene) {
        this.scene = scene;
        cam = new PerspectiveCamera(false);
        scene.setCamera(cam);
    }

}











  
    
    
      
        
          
            
              
                
                
                
                
            
          
        
      
      
        
        
          
            
              
                
                
                
            
          
        
        
          
            
              
                
            
          
        
      
    
  


いつものように動画もサービスです!

余計なお世話でしょうが Java 8 実行環境の勇者様にはこちらをお試しでします。

Webstart: click to launch this app as webstart (Java 8 実行環境必須)

Hatena タグ:

非同期処理を試してみた。

JavaFX をはじめてからいろいろ解らないことがあって思うように進まない。

9月、10月は個人的にいろいろ面倒な季節であってそのうちにクリスマスやお正月と忙しくなる。

それでも少しだけでも! と今回は「JavaFX 2ではじめる、GUI開発 第14回 非同期処理 」を試してみる。

デスクトップアプリでは必ず必要となる技術だけに記事の解説も丁寧に書かれていました。

感謝感激!です。

非同期処理を実装するにあたって JavaFX には Task クラスがありそれが便利に使えます。

また、Platform.runLater(() –> { // … });

でスレッドを起こすことができてだいたい Swing でできていたことは可能なようです。

詳しくは上記の記事をご覧になることをお勧めします。

まだ、記事を読んだだけでいろいろ試してはいないのですが、「スレッド間のデータ交換」が面白そうだったので

実際に動かしてみました。

NetBeans プロジェクトのサンプルプログラムがダウンロードで提供されていたので非常に助かります。

ただ、ローカルのファイルを開くとあまり重い処理にはならないので解りにくかった。

そこで JavaFX と openJDK のサイトの HTML ファイルを取得して表示させるようにしてみました。

結局、あーでもない、こーでもないと下の動画のようなものができあがってしまいました。(^_^;)

おまけに自信の無い不安なソースコードも載せておきますので間違い等ありましたら教えていただければ幸いです。

package jp.yucchi.tryasinc;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class TryAsync extends Application {
    
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
        
        Scene scene = new Scene(root);
        stage.setTitle("非同期処理");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
    
}
package jp.yucchi.tryasinc;

import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextArea;
import javafx.scene.control.TitledPane;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

public class FXMLDocumentController implements Initializable {

    @FXML
    private Label state;

    @FXML
    private Button startButton;

    @FXML
    private Button cancelButton;

    @FXML
    private TitledPane text_1;

    @FXML
    private TitledPane text_2;

    @FXML
    private TextArea textArea_1;

    @FXML
    private TextArea textArea_2;

    @FXML
    private ProgressIndicator progressIndicator;

    private final Works works = new Works();

    private ExecutorService executor;

    private final BlockingQueue queue_javafx = new LinkedBlockingQueue<>();
    private final BlockingQueue queue_openJDK = new LinkedBlockingQueue<>();

    private Timeline timer;

    private CompletableFuture javaFX;
    private CompletableFuture openJDK;
    private CompletableFuture doAllWorks;

    Task task;
    private StringBuilder builder_javafx;
    private StringBuilder builder_openJDK;

    @FXML
    public void handleStart(ActionEvent event) {
        
        textArea_1.clear();
        textArea_2.clear();
        startButton.setDisable(true);
        cancelButton.setDisable(false);
        int procs = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors();

        executor = Executors.newFixedThreadPool(procs);

        progressIndicator.setOpacity(1.0);

        javaFX = CompletableFuture.supplyAsync(() -> works.showJavaFX(), executor);
        openJDK = CompletableFuture.supplyAsync(() -> works.showOpenJDK(), executor);
        doAllWorks = CompletableFuture.allOf(javaFX, openJDK);

        task = new Task() {

            @Override
            protected Void call() throws Exception {

                doAllWorks.thenRunAsync(() -> {
                });
                doAllWorks.get();

                return null;

            }

        };
        new Thread(task).start();

        task.stateProperty().addListener((ObservableValue value, Worker.State oldState, Worker.State newState) -> {
            state.setText(oldState.toString() + " --> " + newState.toString());
        });

        Platform.runLater(() -> {

            task.setOnSucceeded((WorkerStateEvent e) -> {
                progressIndicator.setOpacity(0.0);
                System.out.println("Succeeded");
            });

            task.setOnFailed((WorkerStateEvent e) -> {
                System.out.println("Failed");
            });

            task.setOnRunning((WorkerStateEvent e) -> {
                System.out.println("Running");
            });

            task.setOnCancelled((WorkerStateEvent e) -> {
                System.out.println("Cancelled");
            });

            task.setOnScheduled((WorkerStateEvent e) -> {
                System.out.println("Scheduled");
            });

            timer = new Timeline(
                    new KeyFrame(
                            Duration.millis(100), (ActionEvent e) -> {
                                try {
                                    builder_javafx = new StringBuilder();
                                    builder_openJDK = new StringBuilder();

                                    while (!queue_javafx.isEmpty()) {
                                        String line = queue_javafx.take();
                                        builder_javafx.append(line);
                                        builder_javafx.append("\n");
                                    }

                                    while (!queue_openJDK.isEmpty()) {
                                        String line = queue_openJDK.take();
                                        builder_openJDK.append(line);
                                        builder_openJDK.append("\n");
                                    }

                                    if (!(task.isCancelled())) {
                                        textArea_1.appendText(builder_javafx.toString());
                                        textArea_2.appendText(builder_openJDK.toString());
                                    }
                                } catch (InterruptedException ex) {
                                }

                                if (task.isDone() || task.isCancelled()) {
                                    if (!(doAllWorks.isDone())) {
                                        executor.shutdownNow();
                                    }
                                    
                                    timer.stop();
                                    textArea_1.home();
                                    textArea_2.home();
                                    queue_javafx.clear();
                                    queue_openJDK.clear();
                                    builder_javafx = null;
                                    builder_openJDK = null;
                                    startButton.setDisable(false);
                                    cancelButton.setDisable(true);
                                    Toolkit.getDefaultToolkit().beep();
                                }

                                if (doAllWorks.isDone()) {
                                    executor.shutdown();
                                }
                            })
            );
            timer.setCycleCount(Timeline.INDEFINITE);
            timer.play();

        });
    }

    @FXML
    public void handleCancel(ActionEvent event) {
        if (task.isRunning()) {
            task.cancel();         
            textArea_1.appendText("\n\n<-----  キャンセルしました。  ----->");
            textArea_2.appendText("\n\n<-----  キャンセルしました。  ----->");
            progressIndicator.setOpacity(0.0);
            if (!(doAllWorks.isDone())) {
                executor.shutdownNow();
            }
        }
    }

    private class Works {

        private String showJavaFX() {

            BufferedReader in = null;
            try {
                in = new BufferedReader(new InputStreamReader(new URL("https://www.java.net//community/javafx").openStream()));
            } catch (IOException ex) {
                Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
            }

            in.lines().forEach(e -> {
                queue_javafx.offer(e);
            });

            return null;

        }

        private String showOpenJDK() {

            BufferedReader in = null;
            try {
                in = new BufferedReader(new InputStreamReader(new URL("http://openjdk.java.net/").openStream()));
            } catch (IOException ex) {
                Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
            }

            in.lines().forEach(e -> {
                queue_openJDK.offer(e);
            });

            return null;

        }

    }

    public void setStage(Stage stage) {
        stage.setOnCloseRequest((WindowEvent event) -> {
            executor.shutdown();
        });
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        cancelButton.setDisable(true);
    }

}