import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.AmbientLight;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class Hello3d extends Application {
    private static final double FAR_ORBITAL_RADIUS = 240d;
    private static final double NEAR_ORBITAL_RADIUS = 160d;

    private final Image earthImage = new Image(getClass().getResourceAsStream("earth_1000x500.png"));

    private long previousHandledTime = Long.MAX_VALUE;
    private double azimuth;
    private double range = FAR_ORBITAL_RADIUS;
    private AnimationTimer timer;

    private final Rotate cameraRotateX = new Rotate(0d, Rotate.X_AXIS);
    private final Rotate cameraRotateY = new Rotate(0d, Rotate.Y_AXIS);
    private final Rotate cameraRotateZ = new Rotate(0d, Rotate.Z_AXIS);
    private final Translate cameraTranslate = new Translate(0d, -30d, -1 * FAR_ORBITAL_RADIUS);

    @Override
    public void start(final Stage stage) {
	final Group root = new Group();

	// 球体の定義
	final Sphere earth = new Sphere(100d);
	root.getChildren().add(earth);

	// 材質の定義
	final PhongMaterial material = new PhongMaterial();
	material.setDiffuseMap(earthImage);
	earth.setMaterial(material);

	// カメラの定義
	final PerspectiveCamera camera = new PerspectiveCamera(true);
	camera.setFieldOfView(45d);
	camera.setFarClip(1000d);
	camera.getTransforms().addAll(
            cameraTranslate,
            cameraRotateX,
            cameraRotateY,
            cameraRotateZ
	);

	// 点光源の定義
	final PointLight pointLight = new PointLight(Color.WHITE);
	pointLight.setTranslateX(-80d);
	pointLight.setTranslateY(0d);
	pointLight.setTranslateZ(220d);
	root.getChildren().add(pointLight);

	// 環境光の定義
	final AmbientLight ambientLight = new AmbientLight(Color.rgb(100, 100, 100, 0.7));
	root.getChildren().add(ambientLight);

	final Scene scene = new Scene(root, 800, 600, Color.BLACK);
	scene.setCamera(camera);

	stage.setScene(scene);
	stage.setTitle("Hello JavaFX 3D World");
	stage.show();

        timer = new AnimationTimer() {
                @Override
                public void handle(long now) {
                    update(now);
                }
            };

        root.setOnMousePressed(ev -> timer.start());
    }

    /**
     * カメラ位置を更新する。
     *
     * カメラは、Y=0 の平面(X-Z平面)で原点を中心にした円軌道を移動する。
     *
     * - フェーズ1
     *   経度0度から140度EまでFAR_ORBITAL_RADIUS を半径とした円軌道を移動する。
     * - フェーズ2
     *   経度140度Eで、半径をFAR_ORBITAL_RADIUSからNEAR_ORBITAL_RADIUSに移動する。
     * - フェーズ3
     *   経度140度Eから122度WまでFAR_ORBITAL_RADIUS を半径とした円軌道を移動する。
     *
     * また、カメラの角度は常に球体を向くように円軌道の移動に合わせて設定する。
     */
    private void update(long now) {
        // フェーズ1
        if (azimuth < 140d) {
            azimuth += 1;
            cameraTranslate.setX(Math.sin(Math.toRadians(azimuth)) * FAR_ORBITAL_RADIUS);
            cameraTranslate.setZ(-1 * Math.cos(Math.toRadians(azimuth)) * FAR_ORBITAL_RADIUS);
            cameraRotateY.setAngle(-1 * azimuth);
            return;
        }
        // フェーズ2
        if (range > NEAR_ORBITAL_RADIUS) {
            range -= 0.5d;
            cameraTranslate.setX(Math.sin(Math.toRadians(azimuth)) * range);
            cameraTranslate.setZ(-1 * Math.cos(Math.toRadians(azimuth)) * range);
            return;
        }
        // フェーズ3
        if (azimuth < 238d) {
            azimuth += 0.5;
            cameraTranslate.setX(Math.sin(Math.toRadians(azimuth)) * NEAR_ORBITAL_RADIUS);
            cameraTranslate.setZ(-1 * Math.cos(Math.toRadians(azimuth)) * NEAR_ORBITAL_RADIUS);
            cameraRotateY.setAngle(-1 * azimuth);
            return;
        }
        timer.stop();
    }

    public static void main(final String... args) {
	launch(args);
    }
}