import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class Mesh3DBug extends Application {

    private static final int BOX_COUNT = 64;

    private static final int SCENE_WIDTH = 800;
    private static final int SCENE_HEIGHT = 600;
    private static final double ANIM_SPEED = 0.1;

    private final List<OrbitingShape> orbitingShapes = new ArrayList<>();
    private double animationAngle = 0;
    private SubScene subScene;

    private final Random rand = new Random();

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        rand.setSeed(1);

        Group root3D = new Group();
        subScene = new SubScene(root3D, SCENE_WIDTH, SCENE_HEIGHT, true, SceneAntialiasing.BALANCED);
        subScene.setFill(Color.DARKSLATEGRAY);

        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setTranslateZ(-800);
        camera.setNearClip(0.1);
        camera.setFarClip(2000);
        camera.setFieldOfView(45);
        subScene.setCamera(camera);

        addBoxes(root3D, BOX_COUNT);

        PointLight light = new PointLight(Color.WHITE);
        light.getTransforms().add(new Translate(0, -300, -400));
        root3D.getChildren().add(light);

        Group root = new Group(subScene);
        Scene scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
        stage.setScene(scene);
        stage.setTitle("Mesh 3D Bug D3D12");
        stage.show();

        startAnimation();
    }

    private void addBoxes(Group root, int count) {
        for (int i = 0; i < count; i++) {
            Box box = new Box(50, 50, 50);
            box.setMaterial(makeRandomMaterial());
            root.getChildren().add(box);
            orbitingShapes.add(new OrbitingShape(box, 150 + i * 15, i * 20));
        }
    }

    private float randomFloat() {
        return (float) ((rand.nextDouble() - 0.5) * 300);
    }

    private float clamp(float val, float min, float max) {
        return Math.max(min, Math.min(max, val));
    }

    private PhongMaterial makeRandomMaterial() {
        Color color = Color.color(rand.nextDouble(), rand.nextDouble(), rand.nextDouble());
        PhongMaterial material = new PhongMaterial();
        material.setDiffuseColor(color);
        return material;
    }

    private void startAnimation() {
        Timeline timeline = new Timeline(new KeyFrame(Duration.millis(16), event -> {
            animationAngle += 0.5 * ANIM_SPEED;
            for (OrbitingShape sw : orbitingShapes) {
                double angleRad = Math.toRadians(animationAngle + sw.phase);
                double x = Math.cos(angleRad) * sw.radius;
                double z = Math.sin(angleRad) * sw.radius;
                double y = Math.sin(angleRad * 0.5) * 40;

                sw.node.setTranslateX(x);
                sw.node.setTranslateZ(z);
                sw.node.setTranslateY(y);
                sw.node.setRotationAxis(sw.rotationAxis.getAxis());
                sw.node.setRotate(animationAngle);
            }
        }));
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
    }

    private static class OrbitingShape {
        public final Node node;
        public final double radius;
        public final double phase;
        public Rotate rotationAxis = new Rotate(0, Rotate.Y_AXIS);

        public OrbitingShape(Node node, double radius, double phase) {
            this.node = node;
            this.radius = radius;
            this.phase = phase;
        }
    }
}
