package com.cgvsu;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.model.Model;
import com.cgvsu.objreader.ObjReader;
import com.cgvsu.render_engine.Camera;
import com.cgvsu.render_engine.GraphicConveyor;
import com.cgvsu.render_engine.RenderEngine;
import com.cgvsu.render_engine.TransformationParameters;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelBuffer;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.nio.file.Files;
import java.nio.file.Path;

public class GuiController {

    final private float TRANSLATION = 0.5F;

    @FXML
    AnchorPane anchorPane;

    @FXML
    private ImageView imageView;
    @FXML
    private VBox modelsManager;
    @FXML
    private TextField textFieldScaleX;
    @FXML
    private TextField textFieldScaleY;
    @FXML
    private TextField textFieldScaleZ;
    @FXML
    private TextField textFieldRotationX;
    @FXML
    private TextField textFieldRotationY;
    @FXML
    private TextField textFieldRotationZ;
    @FXML
    private TextField textFieldTranslationX;
    @FXML
    private TextField textFieldTranslationY;
    @FXML
    private TextField textFieldTranslationZ;
    @FXML
    private VBox camerasManager;
    @FXML
    private TextField textFieldCameraPositionX;
    @FXML
    private TextField textFieldCameraPositionY;
    @FXML
    private TextField textFieldCameraPositionZ;
    @FXML
    private TextField textFieldCameraPointOfDirectionX;
    @FXML
    private TextField textFieldCameraPointOfDirectionY;
    @FXML
    private TextField textFieldCameraPointOfDirectionZ;
    @FXML
    private VBox lightingModelsManager;

    @FXML
    private CheckBox checkBoxDrawPolygonalGrid;
    @FXML
    private CheckBox checkBoxUseTexture;
    @FXML
    private CheckBox checkBoxUseLighting;

    @FXML
    private TextField textFieldLightingModelDirectionX;
    @FXML
    private TextField textFieldLightingModelDirectionY;
    @FXML
    private TextField textFieldLightingModelDirectionZ;

    private Model mesh = null;

    private int counterModels = 0;
    private int counterCameras = 0;
    private int counterLightingModels = 0;
     private int width;
    private int height;


    private Camera camera = new Camera(
            new Vector3f(0, 0, 100),
            new Vector3f(0, 0, 0),
            1.0F, 1, 0.01F, 100);

    private Timeline timeline;


    private PixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();
    private PixelBuffer<IntBuffer> pixelBuffer;
    private WritableImage writableImage;
    private IntBuffer pixelBufferData;


    @FXML
    private void initialize() {
        anchorPane.widthProperty().addListener((obs, oldVal, newVal) -> {
            width = newVal.intValue();
            setupPixelBuffer();
        });
        anchorPane.heightProperty().addListener((obs, oldVal, newVal) -> {
            height = newVal.intValue();
            setupPixelBuffer();
        });

        setupPixelBuffer();

        timeline = new Timeline();
        timeline.setCycleCount(Animation.INDEFINITE);

        KeyFrame frame = new KeyFrame(Duration.millis(15), event -> {
            renderFrame();
        });

        timeline.getKeyFrames().add(frame);
        timeline.play();
    }

    private void setupPixelBuffer() {
        if (width <= 0 || height <= 0) return;

        pixelBufferData = IntBuffer.allocate(width * height);
        pixelBuffer = new PixelBuffer<>(width, height, pixelBufferData, pixelFormat);
        writableImage = new WritableImage(pixelBuffer);
        imageView.setImage(writableImage);
    }



    private void renderFrame() {
        if (mesh == null || pixelBufferData == null) return;

        pixelBufferData.clear();
        for (int i = 0; i < width * height; i++) {
            pixelBufferData.put(0xFF000000); // ARGB: непрозрачный черный
        }
        pixelBufferData.rewind();

        RenderEngine renderEngine = new RenderEngine(true, width,height);

        renderEngine.render(camera, mesh);

        writableImage.getPixelWriter().setPixels(0, 0, width, height, pixelFormat, pixelBufferData.array(), 0, width);
    }

    @FXML
    private void loadModelFromFile() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.obj)", "*.obj"));
        fileChooser.setTitle("Load Model");

        File initialDirectory = new File("3DModels");
        if (initialDirectory.exists()) {
            fileChooser.setInitialDirectory(initialDirectory);
        }

        File file = fileChooser.showOpenDialog((Stage) imageView.getScene().getWindow());
        if (file == null) {
            return;
        }

        Path fileName = Path.of(file.getAbsolutePath());

        try {
            String fileContent = Files.readString(fileName);
            mesh = ObjReader.read(fileContent);
            mesh.triangulate();
            mesh.loadTexture("3DModels\\CaracalCube\\caracal_texture.png");

            // Обработка ошибок (например, показать alert)
        } catch (IOException exception) {
            showErrorAlert("Ошибка загрузки модели", "Не удалось загрузить модель.",
                    exception.getMessage());
        }
    }

    @FXML
    private void saveModelFromFile() {
        // objWriter
    }

    @FXML
    public void handleCameraForward(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, 0, -TRANSLATION));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraBackward(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, 0, TRANSLATION));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraLeft(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(TRANSLATION, 0, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraRight(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(-TRANSLATION, 0, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraUp(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, TRANSLATION, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraDown(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, -TRANSLATION, 0));
        camera.setPosition(newPosition);
    }


    private TransformationParameters parseParameters() throws NumberFormatException {
        TransformationParameters params = new TransformationParameters();
        params.setAlpha(Double.parseDouble(textFieldRotationX.getText()));
        params.setBeta(Double.parseDouble(textFieldRotationY.getText()));
        params.setGamma(Double.parseDouble(textFieldRotationZ.getText()));

        params.setTranslationX(Double.parseDouble(textFieldTranslationX.getText()));
        params.setTranslationY(Double.parseDouble(textFieldTranslationY.getText()));
        params.setTranslationZ(Double.parseDouble(textFieldTranslationZ.getText()));

        params.setScaleX(Double.parseDouble(textFieldScaleX.getText()));
        params.setScaleY(Double.parseDouble(textFieldScaleY.getText()));
        params.setScaleZ(Double.parseDouble(textFieldScaleZ.getText()));

        return params;
    }

    private void showErrorAlert(String title, String header, String content) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }


    @FXML
    private void recalculationOfNormals() {
        if (mesh != null) {
            TransformationParameters params;
            try {
                params = parseParameters();
            } catch (NumberFormatException e) {
                showErrorAlert("Ошибка ввода", "Некорректные данные",
                        "Пожалуйста, убедитесь, что все поля содержат числовые значения.");
                return;
            }

            Vector3f rota = new Vector3f((float) params.getAlpha(), (float) params.getBeta(), (float) params.getGamma());
            Vector3f scale = new Vector3f((float) params.getScaleX(), (float) params.getScaleY(), (float) params.getScaleZ());
            Vector3f trans = new Vector3f((float) params.getTranslationX(), (float) params.getTranslationY(), (float) params.getTranslationZ());


            Matrix4f MAT = GraphicConveyor.scaleRotateTranslate(rota, scale, trans);


            mesh.getVertices().replaceAll(vector3f -> GraphicConveyor.multiplyMatrix4ByVector3(MAT, vector3f));
            mesh.calculateNormals();
        }


    }

    @FXML
    private void addNewCamera() {
        // для кнопки
    }

    @FXML
    private void addNewLightingModel() {
        // для кнопки
    }

    private void addModelControlButtons() {
        counterModels++;

        Button modelButton = new Button("Model " + counterModels);
        // здесь же наверное действие выбора активной модели (Это вроде мой блок)
        Button addTextureButton = new Button("Add texture");
        // здесь связать кнопку с методом для добавления текстуры
        Button removeTextureButton = new Button("Remove texture");
        // здесь связать кнопку с методом для добавления текстуры
        Button deleteModelButton = new Button("Delete");
        // здесь связать удаление модели с кнопкой (и это вроде мой блок)
        HBox model = new HBox(5, modelButton, addTextureButton, removeTextureButton, deleteModelButton);

        modelsManager.getChildren().add(model);
    }

    private void addCameraControlButtons() {
        counterCameras++;

        Button cameraButton = new Button("Camera " + counterCameras);
        // здесь возможность выбирать активную камеру, чтобы для нее вводить в поле настройки
        Button deleteCamera = new Button("Delete");
        // кнопку привязать к удалению
        HBox camera = new HBox(5, cameraButton, deleteCamera);

        camerasManager.getChildren().add(camera);
    }

    private void addLightModelControlButtons() {
        counterLightingModels++;

        Button lightModelButton = new Button("Light model " + counterLightingModels);
        // аналогично камере
        Button deleteLightButton = new Button("Delete");
        // кнопку к удалению
        HBox lightModel = new HBox(lightModelButton, deleteLightButton);

        lightingModelsManager.getChildren().add(lightModel);
    }

    @FXML
    public void onClickCanvas() {
        imageView.requestFocus();
    }

    @FXML
    private void returnToTheStartingPosition() {

    }

    @FXML
    private void moveToTheModel() {

    }
}
at com.cgvsu/com.cgvsu.GuiController.renderFrame(GuiController.java:168)
	at com.cgvsu/com.cgvsu.GuiController.lambda$initialize$2(GuiController.java:137)
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.*?>
<?import javafx.scene.input.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.image.ImageView?>
<AnchorPane fx:id="anchorPane" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1"
            fx:controller="com.cgvsu.GuiController">
    <MenuBar>
        <Menu mnemonicParsing="false" text="File">
            <MenuItem mnemonicParsing="false" onAction="#loadModelFromFile" text="Load Model">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="F" control="DOWN" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#saveModelFromFile" text="Save Model">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="S" control="DOWN" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
        </Menu>
        <Menu mnemonicParsing="false" text="Camera Options">
            <MenuItem mnemonicParsing="false" onAction="#handleCameraForward" text="Forward">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="UP" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#handleCameraBackward" text="Backward">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="DOWN" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#handleCameraLeft" text="Left">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="LEFT" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#handleCameraRight" text="Right">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="RIGHT" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#handleCameraUp" text="Up">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="W" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
            <MenuItem mnemonicParsing="false" onAction="#handleCameraDown" text="Down">
                <accelerator>
                    <KeyCodeCombination alt="UP" code="S" control="UP" meta="UP" shift="UP" shortcut="UP"/>
                </accelerator>
            </MenuItem>
        </Menu>
    </MenuBar>

    <ImageView fx:id="imageView" fitHeight="870" onMouseClicked="#onClickCanvas" fitWidth="1600" AnchorPane.rightAnchor="420.0"
               AnchorPane.topAnchor="30.0"/>
    <ScrollPane layoutX="1186.0" prefHeight="900.0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0"
                AnchorPane.topAnchor="0.0">
        <content>
            <VBox prefHeight="884.0" prefWidth="412.0">
                <children>
                    <Label text="Objects">
                        <VBox.margin>
                            <Insets top="5.0"/>
                        </VBox.margin>
                    </Label>
                    <TitledPane animated="false" expanded="false" text="Models">
                        <VBox.margin>
                            <Insets top="10.0"/>
                        </VBox.margin>
                        <content>
                            <VBox fx:id="modelsManager" prefHeight="200.0" prefWidth="100.0"/>
                        </content>
                    </TitledPane>
                    <HBox prefHeight="100.0" prefWidth="200.0">
                        <children>
                            <VBox prefHeight="100.0" prefWidth="140.0">
                                <children>
                                    <Label text="Scale"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldScaleX" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="1"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldScaleY" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="1"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldScaleZ" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="1"/>
                                        </children>
                                    </HBox>
                                </children>
                                <HBox.margin>
                                    <Insets/>
                                </HBox.margin>
                            </VBox>
                            <VBox prefHeight="200.0" prefWidth="140.0">
                                <children>
                                    <Label text="Rotation"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldRotationX" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldRotationY" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldRotationZ" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                </children>
                            </VBox>
                            <VBox prefHeight="200.0" prefWidth="140.0">
                                <children>
                                    <Label text="Translation"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldTranslationX" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldTranslationY" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldTranslationZ" prefHeight="25.0" prefWidth="100.0"
                                                       promptText="0.0" text="0"/>
                                        </children>
                                    </HBox>
                                </children>
                            </VBox>
                        </children>
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                    </HBox>
                    <Button mnemonicParsing="false" onAction="#recalculationOfNormals" text="Recalculate the normals">
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                    </Button>
                    <Button mnemonicParsing="false" onAction="#returnToTheStartingPosition"
                            text="Return to the starting position">
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                    </Button>
                    <VBox prefHeight="200.0" prefWidth="100.0">
                        <children>
                            <Label text="Rendering mode">
                                <VBox.margin>
                                    <Insets top="10.0"/>
                                </VBox.margin>
                            </Label>
                            <CheckBox fx:id="checkBoxDrawPolygonalGrid" mnemonicParsing="false"
                                      text="Draw a polygonal grid">
                                <VBox.margin>
                                    <Insets top="10.0"/>
                                </VBox.margin>
                            </CheckBox>
                            <CheckBox fx:id="checkBoxUseTexture" mnemonicParsing="false" text="Use texture">
                                <VBox.margin>
                                    <Insets top="10.0"/>
                                </VBox.margin>
                            </CheckBox>
                            <CheckBox fx:id="checkBoxUseLighting" mnemonicParsing="false" text="Use lighting">
                                <VBox.margin>
                                    <Insets top="10.0"/>
                                </VBox.margin>
                            </CheckBox>
                        </children>
                        <VBox.margin>
                            <Insets left="5.0"/>
                        </VBox.margin>
                    </VBox>
                    <TitledPane animated="false" text="Cameras">
                        <VBox.margin>
                            <Insets top="10.0"/>
                        </VBox.margin>
                        <content>
                            <VBox fx:id="camerasManager" prefHeight="200.0" prefWidth="100.0">
                                <children>
                                    <Button mnemonicParsing="false" onAction="#addNewCamera" text="Add a new camera"/>
                                </children>
                            </VBox>
                        </content>
                    </TitledPane>
                    <HBox prefHeight="100.0" prefWidth="200.0">
                        <children>
                            <VBox prefHeight="200.0" prefWidth="200.0">
                                <children>
                                    <Label text="Position"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldCameraPositionX" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldCameraPositionY" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldCameraPositionZ" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                </children>
                            </VBox>
                            <VBox prefHeight="100.0" prefWidth="200.0">
                                <children>
                                    <Label prefHeight="17.0" prefWidth="181.0" text="The point of direction"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldCameraPointOfDirectionX" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldCameraPointOfDirectionY" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldCameraPointOfDirectionZ" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                </children>
                            </VBox>
                        </children>
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                    </HBox>
                    <Button mnemonicParsing="false" onAction="#moveToTheModel" text="Move to the model">
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                    </Button>
                    <TitledPane animated="false" text="Lighting Models ">
                        <VBox.margin>
                            <Insets top="10.0"/>
                        </VBox.margin>
                        <content>
                            <VBox fx:id="lightingModelsManager" prefHeight="200.0" prefWidth="100.0">
                                <children>
                                    <Button mnemonicParsing="false" onAction="#addNewLightingModel"
                                            text="Add a new lighting model"/>
                                </children>
                            </VBox>
                        </content>
                    </TitledPane>
                    <HBox prefHeight="100.0" prefWidth="200.0">
                        <VBox.margin>
                            <Insets left="5.0" top="10.0"/>
                        </VBox.margin>
                        <children>
                            <VBox prefHeight="200.0" prefWidth="150.0">
                                <children>
                                    <Label text="Direction"/>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="X:  "/>
                                            <TextField fx:id="textFieldLightingModelDirectionX" prefHeight="25.0"
                                                       prefWidth="130.0" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Y:  "/>
                                            <TextField fx:id="textFieldLightingModelDirectionY" prefHeight="25.0"
                                                       prefWidth="130.0" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                    <HBox prefHeight="100.0" prefWidth="200.0">
                                        <children>
                                            <Label text="Z:  "/>
                                            <TextField fx:id="textFieldLightingModelDirectionZ" prefHeight="25.0"
                                                       prefWidth="130.0" promptText="0.0"/>
                                        </children>
                                    </HBox>
                                </children>
                            </VBox>
                        </children>
                    </HBox>
                </children>
            </VBox>
        </content>
    </ScrollPane>
</AnchorPane>
package com.cgvsu;

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

import java.io.IOException;
import java.util.Objects;

public class Simple3DViewer extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        // Загружаем FXML файл
        AnchorPane viewport = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("fxml/gui.fxml")));

        // Создаем сцену
        Scene scene = new Scene(viewport);
        viewport.prefWidthProperty().bind(scene.widthProperty());
        viewport.prefHeightProperty().bind(scene.heightProperty());

        // Настройки окна
        stage.setTitle("Simple3DViewer");
        stage.setScene(scene);

        // Установка минимальных размеров окна
        stage.setMinWidth(1080);
        stage.setMinHeight(720);

        // Установка размеров окна на весь экран
        stage.setWidth(java.awt.Toolkit.getDefaultToolkit().getScreenSize().getWidth());
        stage.setHeight(java.awt.Toolkit.getDefaultToolkit().getScreenSize().getHeight());

        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}
package com.cgvsu.render_engine;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Point2f;
import com.cgvsu.math.Vector2f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.model.Model;
import com.cgvsu.model.Polygon;
import javafx.scene.image.*;
import javafx.scene.paint.Color;

import java.nio.IntBuffer;
import java.util.Arrays;

import static com.cgvsu.render_engine.GraphicConveyor.*;

/**
 * Класс для рендеринга 3D моделей с использованием PixelBuffer CPU в JavaFX.
 */
public class RenderEngine {

    /**
     * Флаг, определяющий режим рендеринга.
     * true - с наложением текстур,
     * false - только растеризация с использованием цветов.
     */
    private boolean useTexture;

    /**
     * Буфер для хранения пиксельных данных (ARGB).
     */
    private int[] pixels;

    /**
     * Размер изображения.
     */
    private int width;
    private int height;

    /**
     * Информация о глубине для каждого пикселя.
     */
    private float[][] zBuffer;

    /**
     * PixelBuffer для связывания с WritableImage.
     */
    private PixelBuffer<IntBuffer> pixelBuffer;

    /**
     * WritableImage для отображения результатирующего изображения.
     */
    private WritableImage writableImage;

    /**
     * Конструктор RenderEngine.
     *
     * @param useTexture Флаг для выбора режима рендеринга.
     * @param width      Ширина области рендеринга.
     * @param height     Высота области рендеринга.
     */
    public RenderEngine(boolean useTexture, int width, int height) {
        this.useTexture = useTexture;
        this.width = width;
        this.height = height;
        initializeBuffers();
    }

    /**
     * Инициализирует пиксельные и Z-буфера.
     */
    private void initializeBuffers() {
        pixels = new int[width * height];
        Arrays.fill(pixels, 0xFF000000); // Заполнение черным цветом с полной непрозрачностью
        zBuffer = new float[width][height];
        for (int x = 0; x < width; x++) {
            Arrays.fill(zBuffer[x], Float.MAX_VALUE);
        }

        // Создание IntBuffer, обернутого вокруг массива пикселей
        IntBuffer intBuffer = IntBuffer.wrap(pixels);

        // Создание PixelBuffer с использованием IntBuffer и формата ARGB_PRE
        pixelBuffer = new PixelBuffer<>(width, height, intBuffer, PixelFormat.getIntArgbPreInstance());

        // Создание WritableImage, связанного с PixelBuffer
        writableImage = new WritableImage(pixelBuffer);
    }

    /**
     * Устанавливает режим рендеринга.
     *
     * @param useTexture true для наложения текстур, false для растеризации.
     */
    public void setUseTexture(boolean useTexture) {
        this.useTexture = useTexture;
    }

    /**
     * Получает WritableImage для отображения.
     *
     * @return WritableImage связанное с PixelBuffer.
     */
    public WritableImage getWritableImage() {
        return writableImage;
    }

    /**
     * Вычисляет матрицу модель-представление-проекция с учетом камеры и трансформаций модели.
     *
     * @param camera    Объект камеры, содержащий матрицы вида и проекции.
     * @param rotate    Вектор поворота модели.
     * @param scale     Вектор сжатия модели.
     * @param translate Вектор переноса модели.
     * @return Матрица модель-представление-проекция.
     */
    private Matrix4f calculateModelViewProjectionMatrix(Camera camera, Vector3f rotate, Vector3f scale, Vector3f translate) {
        Matrix4f modelMatrix = scaleRotateTranslate(rotate, scale, translate);
        Matrix4f viewMatrix = camera.getViewMatrix();
        Matrix4f projectionMatrix = camera.getProjectionMatrix();

        modelMatrix.transposition();
        viewMatrix.transposition();
        projectionMatrix.transposition();

        Matrix4f mvpMatrix = new Matrix4f(projectionMatrix);
        mvpMatrix.multiply(viewMatrix);
        mvpMatrix.multiply(modelMatrix);
        return mvpMatrix;
    }

    /**
     * Выполняет рендеринг 3D модели и обновляет PixelBuffer.
     *
     * @param camera Объект камеры, определяющий вид и проекцию.
     * @param model  Модель для рендеринга.
     */
    public void render(Camera camera, Model model) {
        clearBuffers();
        Matrix4f mvpMatrix = calculateModelViewProjectionMatrix(camera, model.getRotate(), model.getScale(), model.getTranslate());

        Image texture = model.getTexture();

        for (Polygon polygon : model.getPolygons()) {
            renderPolygon(polygon, mvpMatrix, model, texture);
        }

        updatePixelBuffer();
    }

    /**
     * Очищает пиксельный и Z-буфер.
     */
    private void clearBuffers() {
        Arrays.fill(pixels, 0xFF000000); // Черный цвет с полной непрозрачностью
        for (int x = 0; x < width; x++) {
            Arrays.fill(zBuffer[x], Float.MAX_VALUE);
        }
    }


    public void resize(int newWidth, int newHeight) {
        if (newWidth <= 0 || newHeight <= 0) {
            throw new IllegalArgumentException("Width and Height must be positive integers.");
        }
        this.width = newWidth;
        this.height = newHeight;
        initializeBuffers();
        // Optionally, clear the screen or adjust camera parameters if needed
    }

    /**
     * Рендерит отдельный многоугольник и записывает данные в пиксельный буфер.
     *
     * @param polygon   Многоугольник для рендеринга.
     * @param mvpMatrix Матрица модель-представление-проекция.
     * @param model     Модель, содержащая вершины.
     * @param texture   Текстура модели.
     */
    private void renderPolygon(Polygon polygon, Matrix4f mvpMatrix, Model model, Image texture) {

        int[] vertexIndices = polygon.getVertexIndices().stream().mapToInt(Integer::intValue).toArray();
        int[] textureIndices = polygon.getTextureVertexIndices().stream().mapToInt(Integer::intValue).toArray();

        Point2f[] screenPoints = new Point2f[3];
        float[] zValues = new float[3];
        Vector2f[] textureCoords = new Vector2f[3];

        for (int i = 0; i < 3; i++) {
            Vector3f transformedVertex = transformVertex(model, vertexIndices[i], mvpMatrix);
            screenPoints[i] = vertexToPoint(transformedVertex, width, height);
            zValues[i] = transformedVertex.getZ();

            if (useTexture && textureIndices.length == 3) {
                Vector2f texCoord = model.getTextureVertices().get(textureIndices[i]);
                textureCoords[i] = texCoord;
            }
        }

        if (useTexture && textureIndices.length == 3) {
            rasterizeTriangleWithTexture(screenPoints, zValues, textureCoords, texture);
        } else {
            rasterizeTriangle(screenPoints, zValues);
        }
    }

    /**
     * Растеризует треугольник с наложением текстуры и записывает данные в пиксельный буфер.
     *
     * @param points        Координаты точек треугольника на экране.
     * @param zValues       Значения глубины для каждой вершины.
     * @param textureCoords Текстурные координаты для каждой вершины.
     * @param texture       Текстура модели.
     */
    private void rasterizeTriangleWithTexture(Point2f[] points, float[] zValues, Vector2f[] textureCoords, Image texture) {
        PixelReader pixelReader = texture.getPixelReader();
        if (pixelReader == null) {
            System.err.println("Не удалось получить PixelReader из текстуры.");
            return;
        }

        int texWidth = (int) texture.getWidth();
        int texHeight = (int) texture.getHeight();

        int minX = clamp((int) Math.floor(Math.min(points[0].getX(), Math.min(points[1].getX(), points[2].getX()))), 0, width - 1);
        int maxX = clamp((int) Math.ceil(Math.max(points[0].getX(), Math.max(points[1].getX(), points[2].getX()))), 0, width - 1);
        int minY = clamp((int) Math.floor(Math.min(points[0].getY(), Math.min(points[1].getY(), points[2].getY()))), 0, height - 1);
        int maxY = clamp((int) Math.ceil(Math.max(points[0].getY(), Math.max(points[1].getY(), points[2].getY()))), 0, height - 1);

        for (int y = minY; y <= maxY; y++) {
            for (int x = minX; x <= maxX; x++) {
                float[] barycentric = calculateBarycentricCoordinates(points, x, y);
                float lambda0 = barycentric[0];
                float lambda1 = barycentric[1];
                float lambda2 = barycentric[2];

                if (isInsideTriangle(lambda0, lambda1, lambda2)) {
                    float depth = interpolate(lambda0, lambda1, lambda2, zValues);

                    if (depth < zBuffer[x][y]) {
                        zBuffer[x][y] = depth;

                        float u = interpolate(lambda0, lambda1, lambda2, new float[]{
                                textureCoords[0].getX(),
                                textureCoords[1].getX(),
                                textureCoords[2].getX()});
                        float v = interpolate(lambda0, lambda1, lambda2, new float[]{
                                textureCoords[0].getY(),
                                textureCoords[1].getY(),
                                textureCoords[2].getY()});

                        int texX = clamp((int) (u * (texWidth - 1)), 0, texWidth - 1);
                        int texY = clamp((int) ((1 - v) * (texHeight - 1)), 0, texHeight - 1);

                        Color color = ((PixelReader) pixelReader).getColor(texX, texY);
                        int argb = toARGBInt(color);

                        pixels[x + y * width] = argb;
                    }
                }
            }
        }
    }

    /**
     * Растеризует треугольник и записывает данные в пиксельный буфер.
     *
     * @param points  Координаты точек треугольника на экране.
     * @param zValues Значения глубины для каждой вершины.
     */
    private void rasterizeTriangle(Point2f[] points, float[] zValues) {
        Color colorA = Color.RED;
        Color colorB = Color.BLACK;
        Color colorC = Color.BLUE;

        int minX = clamp((int) Math.floor(Math.min(points[0].getX(), Math.min(points[1].getX(), points[2].getX()))), 0, width - 1);
        int maxX = clamp((int) Math.ceil(Math.max(points[0].getX(), Math.max(points[1].getX(), points[2].getX()))), 0, width - 1);
        int minY = clamp((int) Math.floor(Math.min(points[0].getY(), Math.min(points[1].getY(), points[2].getY()))), 0, height - 1);
        int maxY = clamp((int) Math.ceil(Math.max(points[0].getY(), Math.max(points[1].getY(), points[2].getY()))), 0, height - 1);

        for (int y = minY; y <= maxY; y++) {
            for (int x = minX; x <= maxX; x++) {
                float[] barycentric = calculateBarycentricCoordinates(points, x, y);
                float lambda0 = barycentric[0];
                float lambda1 = barycentric[1];
                float lambda2 = barycentric[2];

                if (isInsideTriangle(lambda0, lambda1, lambda2)) {
                    float depth = interpolate(lambda0, lambda1, lambda2, zValues);

                    if (depth < zBuffer[x][y]) {
                        zBuffer[x][y] = depth;
                        Color color = interpolateColor(lambda0, lambda1, lambda2, colorA, colorB, colorC);
                        int argb = toARGBInt(color);
                        pixels[x + y * width] = argb;
                    }
                }
            }
        }
    }

    /**
     * Преобразует Color в ARGB целочисленное значение.
     *
     * @param color Цвет JavaFX.
     * @return ARGB цвет в формате int.
     */
    private int toARGBInt(Color color) {
        int a = (int) (clamp(color.getOpacity()) * 255) & 0xFF;
        int r = (int) (clamp(color.getRed()) * 255) & 0xFF;
        int g = (int) (clamp(color.getGreen()) * 255) & 0xFF;
        int b = (int) (clamp(color.getBlue()) * 255) & 0xFF;
        return (a << 24) | (r << 16) | (g << 8) | b;
    }

    /**
     * Применяет трансформацию к вершине модели.
     *
     * @param model       Модель, содержащая вершины.
     * @param vertexIndex Индекс вершины в модели.
     * @param mvpMatrix   Матрица модель-представление-проекция.
     * @return Трансформированная вершина.
     */
    private Vector3f transformVertex(Model model, int vertexIndex, Matrix4f mvpMatrix) {
        Vector3f vertex = model.getVertices().get(vertexIndex);
        return multiplyMatrix4ByVector3(mvpMatrix, vertex);
    }

    /**
     * Вычисляет барицентрические координаты точки относительно треугольника.
     *
     * @param points Координаты вершин треугольника.
     * @param x      Координата X точки.
     * @param y      Координата Y точки.
     * @return Массив из трех барицентрических координат.
     */
    private float[] calculateBarycentricCoordinates(Point2f[] points, int x, int y) {
        float denom = ((points[1].getY() - points[2].getY()) * (points[0].getX() - points[2].getX()) +
                (points[2].getX() - points[1].getX()) * (points[0].getY() - points[2].getY()));

        if (denom == 0) {
            return new float[]{-1, -1, -1}; // Degenerate triangle
        }

        float lambda0 = ((points[1].getY() - points[2].getY()) * (x - points[2].getX()) +
                (points[2].getX() - points[1].getX()) * (y - points[2].getY())) / denom;

        float lambda1 = ((points[2].getY() - points[0].getY()) * (x - points[2].getX()) +
                (points[0].getX() - points[2].getX()) * (y - points[2].getY())) / denom;

        float lambda2 = 1.0f - lambda0 - lambda1;

        return new float[]{lambda0, lambda1, lambda2};
    }

    /**
     * Проверяет, находится ли точка внутри треугольника на основе барицентрических координат.
     *
     * @param lambda0 Барицентрическая координата λ0.
     * @param lambda1 Барицентрическая координата λ1.
     * @param lambda2 Барицентрическая координата λ2.
     * @return {@code true}, если точка внутри треугольника, иначе {@code false}.
     */
    private boolean isInsideTriangle(float lambda0, float lambda1, float lambda2) {
        return lambda0 >= 0 && lambda1 >= 0 && lambda2 >= 0;
    }

    /**
     * Интерполирует значение по заданным весам.
     *
     * @param lambda0 Барицентрическая координата λ0.
     * @param lambda1 Барицентрическая координата λ1.
     * @param lambda2 Барицентрическая координата λ2.
     * @param values  Массив значений для интерполяции.
     * @return Интерполированное значение.
     */
    private float interpolate(float lambda0, float lambda1, float lambda2, float[] values) {
        return lambda0 * values[0] + lambda1 * values[1] + lambda2 * values[2];
    }

    /**
     * Интерполирует цвет точки внутри треугольника на основе барицентрических координат.
     *
     * @param lambda0 Барицентрическая координата λ0.
     * @param lambda1 Барицентрическая координата λ1.
     * @param lambda2 Барицентрическая координата λ2.
     * @param colorA  Цвет первой вершины треугольника.
     * @param colorB  Цвет второй вершины треугольника.
     * @param colorC  Цвет третьей вершины треугольника.
     * @return Интерполированный цвет.
     */
    private Color interpolateColor(float lambda0, float lambda1, float lambda2, Color colorA, Color colorB, Color colorC) {
        double red = lambda0 * colorA.getRed() + lambda1 * colorB.getRed() + lambda2 * colorC.getRed();
        double green = lambda0 * colorA.getGreen() + lambda1 * colorB.getGreen() + lambda2 * colorC.getGreen();
        double blue = lambda0 * colorA.getBlue() + lambda1 * colorB.getBlue() + lambda2 * colorC.getBlue();
        return new Color(clamp(red), clamp(green), clamp(blue), 1.0);
    }

    /**
     * Ограничивает значение в диапазоне [0, 1].
     *
     * @param value Исходное значение.
     * @return Ограниченное значение.
     */
    private double clamp(double value) {
        return Math.max(0.0, Math.min(1.0, value));
    }

    /**
     * Ограничивает целочисленное значение в заданном диапазоне.
     *
     * @param value Исходное значение.
     * @param min   Минимальное значение.
     * @param max   Максимальное значение.
     * @return Ограниченное значение.
     */
    private int clamp(int value, int min, int max) {
        return Math.max(min, Math.min(max, value));
    }

    /**
     * Ограничивает целочисленное значение в диапазоне [0, max].
     *
     * @param value Исходное значение.
     * @param max   Максимальное значение.
     * @return Ограниченное значение.
     */
    private int clamp(int value, int max) {
        return clamp(value, 0, max);
    }

    /**
     * Ограничивает значение в диапазоне [0, 255] и преобразует его в целое число.
     *
     * @param value Исходное значение.
     * @return Ограниченное значение.
     */
    private int clampToByte(float value) {
        return (int) (Math.max(0.0f, Math.min(1.0f, value)) * 255.0f);
    }

    /**
     * Обновляет PixelBuffer с текущими пиксельными данными.
     */
    private void updatePixelBuffer() {
        // Создание нового IntBuffer, обернутого вокруг массива пикселей
        IntBuffer intBuffer = IntBuffer.wrap(pixels);

        // Обновление PixelBuffer с новым буфером
        pixelBuffer.getBuffer().clear();
        pixelBuffer.getBuffer().put(intBuffer);
        pixelBuffer.getBuffer().flip();
    }
}
исправь ошибки. Пиши на русском. Ошибки при загрузке модели