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.canvas.Canvas;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
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.file.Files;
import java.nio.file.Path;

public class GuiController {

    final private float TRANSLATION = 0.5F;

    @FXML
    AnchorPane anchorPane;

    @FXML
    private Canvas canvas;
    @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 Camera camera = new Camera(
            new Vector3f(0, 0, 100),
            new Vector3f(0, 0, 0),
            1.0F, 1, 0.01F, 100);

    private Timeline timeline;



/*//    @FXML
//    private void initialize() {
//        anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> canvas.setWidth(newValue.doubleValue()));
//        anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> canvas.setHeight(newValue.doubleValue()));
//
//        // Инициализация камеры и модели
//        camera = new Camera();
//        mesh = new Model();
//        // Загрузите модель и текстуру, например:
//        // mesh.loadFromOBJ("path/to/model.obj");
//        // mesh.loadTexture("path/to/texture.png");
//        mesh.triangulate();
//        mesh.calculateNormals();
//
//        // Создание экземпляра RenderEngine
//        RenderEngine renderEngine = new RenderEngine(true); // true для использования текстур
//
//        timeline = new Timeline();
//        timeline.setCycleCount(Timeline.INDEFINITE);
//
//        KeyFrame frame = new KeyFrame(Duration.millis(15), event -> {
//            double width = canvas.getWidth();
//            double height = canvas.getHeight();
//
//            var gc = canvas.getGraphicsContext2D();
//            gc.clearRect(0, 0, width, height);
//            camera.setAspectRatio((float) (width / height));
//
//            if (mesh != null) {
//                renderEngine.render(gc, camera, mesh, (int) width, (int) height);
//            }
//        });
//
//        timeline.getKeyFrames().add(frame);
//        timeline.play();
//    }*/

    @FXML
    private void initialize() {
        anchorPane.prefWidthProperty().addListener((ov, oldValue, newValue) -> canvas.setWidth(newValue.doubleValue()));
        anchorPane.prefHeightProperty().addListener((ov, oldValue, newValue) -> canvas.setHeight(newValue.doubleValue()));

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

        KeyFrame frame = new KeyFrame(Duration.millis(15), event -> {
            double width = canvas.getWidth();
            double height = canvas.getHeight();

            canvas.getGraphicsContext2D().clearRect(0, 0, width, height);
            camera.setAspectRatio((float) (width / height));

            if (mesh != null) {
                RenderEngine renderEngine = new RenderEngine(true);
                renderEngine.render(canvas.getGraphicsContext2D(), camera, mesh, (int) width, (int) height);
            }
        });

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

    @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) canvas.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");


            // todo: обработка ошибок
        } catch (IOException exception) {

        }
    }

    @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() {
        canvas.requestFocus();
    }

    @FXML
    private void returnToTheStartingPosition() {

    }

    @FXML
    private void moveToTheModel() {

    }
}
package com.cgvsu.render_engine;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Point2f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.math.Vector4f;

import static java.lang.Math.*;


public class GraphicConveyor {

    /**
     * Аффинные преобразования
     * Ненавижу матричную логику...
     * @return {@code Matrix4f} - матрица из локальной системы координат в мировую
     */
    public static Matrix4f scaleRotateTranslate(Vector3f rotate, Vector3f scale, Vector3f translate) {
        Matrix4f r = calculateRotate(rotate.getX(), rotate.getY(), rotate.getZ());
        Matrix4f s = calculateScale(scale.getX(), scale.getY(), scale.getZ());
        Matrix4f t = calculateTranslate(new Vector3f(translate.getX(), translate.getY(), translate.getZ()));

        t.multiply(r);
        t.multiply(s);

        return new Matrix4f(t);
    }

    /**
     * Аффинные преобразования - увеличение/уменьшение объекта
     *
     * @param vector3f вектор переноса
     * @return {@code Matrix4f} - матрица переноса
     */

    public static Matrix4f calculateTranslate(Vector3f vector3f) {
        return calculateTranslate(new Vector4f(vector3f.getX(), vector3f.getY(), vector3f.getZ(), 1));
    }

    private static Matrix4f calculateTranslate(Vector4f vector) {
        Matrix4f res = new Matrix4f(1);
        for (int i = 0; i < res.getElements().length - 1; i++) {
            res.setElement(i, 3, vector.getNum(i));
        }
        return res;
    }


    public static Matrix4f Rotate(float alpha, float beta, float gamma) {
        return calculateRotate(alpha, beta, gamma);
    }

    // мб потом почитаю что такое кватернион и немного переделаю код, пока пусть так будет
    private static Matrix4f calculateRotate(float alpha, float beta, float gamma) {
        // вокруг x
        float[] rotateX = new float[]{
                1, 0, 0, 0,
                0, (float) cos(Math.toRadians(alpha)), (float) sin(Math.toRadians(alpha)), 0,
                0, (float) -sin(Math.toRadians(alpha)), (float) cos(Math.toRadians(alpha)), 0,
                0, 0, 0, 1
        };
        // вокруг y
        float[] rotateY = new float[]{
                (float) cos(Math.toRadians(beta)), 0, (float) sin(Math.toRadians(beta)), 0,
                0, 1, 0, 0,
                (float) -sin(Math.toRadians(beta)), 0, (float) cos(Math.toRadians(beta)), 0,
                0, 0, 0, 1
        };
        // вокруг z
        float[] rotateZ = new float[]{
                (float) cos(Math.toRadians(gamma)), (float) sin(Math.toRadians(gamma)), 0, 0,
                (float) -sin(Math.toRadians(gamma)), (float) cos(Math.toRadians(gamma)), 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
        };

        Matrix4f rotateAboutX = new Matrix4f(rotateX);
        Matrix4f rotateAboutY = new Matrix4f(rotateY);
        Matrix4f rotateAboutZ = new Matrix4f(rotateZ);

        // сначала крутим вокруг x, потом y, потом z (да, в реале умножаем сначала Z, потом ыгрик, потом х (а) - плохо)
        rotateAboutZ.multiply(rotateAboutY);
        rotateAboutZ.multiply(rotateAboutX);

        return rotateAboutZ;
    }

    /**
     * Аффинные преобразования - увеличение/уменьшение объекта
     *
     * @param scaleX растяжение по x
     * @param scaleY растяжение по y
     * @param scaleZ растяжение по z
     * @return {@code Matrix4f} - матрица растяжения (по факту просто по главной диагонали будут стоять не 1, а растяжение наше)
     */

    public static Matrix4f calculateScale(float scaleX, float scaleY, float scaleZ) {
        float[] scale = new float[]{scaleX, scaleY, scaleZ};
        return calculateScale(scale);
    }

    // надо еще продумать, как обновлять это все... наверное, потом придумаю... надеюсь, молюсь...

    private static Matrix4f calculateScale(float[] scale) {
        Matrix4f result = new Matrix4f(1);
        for (int i = 0; i < scale.length; i++) {
            result.setElement(i, i, scale[i]);
        }
        return result;
    }

    public static Matrix4f lookAt(Vector3f eye, Vector3f target) {
        return lookAt(eye, target, new Vector3f(0F, 1.0F, 0F));
    }

    /**
     * Система корд камеры +перевод всех объектов сцены в систему координат камеры
     *
     * @param eye    ее позиция в мировых координатах
     * @param target куда смотрит наша камера
     * @param up     единичный вектор для y - основа будущих корд камеры
     * @return {@code Matrix4f} - матрица прямоугольной системы координат камеры, записанная в мировых координатах сцены.
     */

    private static Matrix4f lookAt(Vector3f eye, Vector3f target, Vector3f up) {
        Vector3f resultX = new Vector3f();
        Vector3f resultY = new Vector3f();
        Vector3f resultZ = new Vector3f();

        resultZ.sub(target, eye);
        resultX = up.cross(resultZ);
        resultY = resultZ.cross(resultX);

        resultX.normalize();
        resultY.normalize();
        resultZ.normalize();

        float[] matrix = new float[]{
                resultX.getX(), resultY.getX(), resultZ.getX(), 0,
                resultX.getY(), resultY.getY(), resultZ.getY(), 0,
                resultX.getZ(), resultY.getZ(), resultZ.getZ(), 0,
                -resultX.dot(eye), -resultY.dot(eye), -resultZ.dot(eye), 1};
        return new Matrix4f(matrix);
    }

    /**
     * Матрица перспективы.
     *
     * @param fov         угол обзора
     * @param aspectRatio соотношение сторон экрана
     * @param nearPlane   ближний план
     * @param farPlane    дальний план
     * @return {@code Matrix4f} - матрица перспективы.
     */

    public static Matrix4f perspective(
            final float fov,
            final float aspectRatio,
            final float nearPlane,
            final float farPlane) {
        float tangentMinusOnDegree = (float) (1.0F / (tan(fov * 0.5F))); // fov задается как половина от всего угла
        float[] res = {
                tangentMinusOnDegree / aspectRatio, 0, 0, 0,
                0, tangentMinusOnDegree, 0, 0,
                0, 0, (farPlane + nearPlane) / (farPlane - nearPlane), 1.0F,
                0, 0, 2 * (nearPlane * farPlane) / (nearPlane - farPlane), 0
        };
        return new Matrix4f(res);
    }

    /**
     * Получаем последний вектор нормализованный вектор v для трансформации вершины и отображения последней на экране
     *
     * @param matrix P*V*M
     * @param vertex корды точки?
     * @return {@code Vector3f} - нормализованный конечный вектор (скорее всего, нормаль к вершине).
     */
    public static Vector3f multiplyMatrix4ByVector3(final Matrix4f matrix, final Vector3f vertex) {
        Vector4f res = matrix.multiply(new Vector4f(vertex.getX(), vertex.getY(), vertex.getZ(), 1));
        float w = res.getW();
        return new Vector3f(res.getX() / w, res.getY() / w, res.getZ() / w);
    }

    /**
     * Получаем последний вектор нормализованный вектор v для трансформации вершины и отображения последней на экране
     *
     * @param width  ширина
     * @param height высота
     * @param vertex нормаль вершины
     * @return {@code Point2f} - получаем корды точки на экране.
     */

    public static Point2f vertexToPoint(final Vector3f vertex, final int width, final int height) {
        return new Point2f(vertex.getX() * width + width / 2.0F, -vertex.getY() * height + height / 2.0F);
    }
}
package com.cgvsu.model;

import com.cgvsu.math.Vector2f;
import com.cgvsu.math.Vector3f;
import javafx.scene.image.Image;

import java.util.ArrayList;
import java.util.List;

/**
 * Класс, представляющий 3D модель с вершинами, текстурными вершинами, нормалями и полигонами.
 */
public class Model {

    private Image texture;

    private final ArrayList<Vector2f> textureVertices = new ArrayList<>();
    private final ArrayList<Vector3f> vertices = new ArrayList<>();
    private final ArrayList<Vector3f> normals = new ArrayList<>();
    private final ArrayList<Polygon> polygons = new ArrayList<>();

    private final Vector3f rotate = new Vector3f(0.0f, 0.0f, 0.0f);
    private final Vector3f scale = new Vector3f(1.0f, 1.0f, 1.0f);
    private final Vector3f translate = new Vector3f(0.0f, 0.0f, 0.0f);

    /**
     * Загружает текстуру модели из указанного файла.
     *
     * @param texturePath Путь к PNG-файлу текстуры.
     */
    public void loadTexture(String texturePath) {
        this.texture = new Image("file:" + texturePath);
    }

    /**
     * Возвращает текстуру модели.
     *
     * @return Объект Image с текстурой, или {@code null}, если текстура не загружена.
     */
    public Image getTexture() {
        return texture;
    }

    /**
     * Возвращает список текстурных вершин модели.
     *
     * @return Список текстурных вершин.
     */
    public ArrayList<Vector2f> getTextureVertices() {
        return textureVertices;
    }

    /**
     * Возвращает список 3D вершин модели.
     *
     * @return Список 3D вершин.
     */
    public ArrayList<Vector3f> getVertices() {
        return vertices;
    }

    /**
     * Возвращает список нормалей вершин модели.
     *
     * @return Список нормалей вершин.
     */
    public ArrayList<Vector3f> getNormals() {
        return normals;
    }

    /**
     * Возвращает список полигонов модели.
     *
     * @return Список полигонов.
     */
    public ArrayList<Polygon> getPolygons() {
        return polygons;
    }

    /**
     * Возвращает вектор поворота модели.
     *
     * @return Вектор поворота.
     */
    public Vector3f getRotate() {
        return rotate;
    }

    /**
     * Возвращает вектор сжатия модели.
     *
     * @return Вектор сжатия.
     */
    public Vector3f getScale() {
        return scale;
    }

    /**
     * Возвращает вектор переноса модели.
     *
     * @return Вектор переноса.
     */
    public Vector3f getTranslate() {
        return translate;
    }

    /**
     * Разбивает полигоны модели на треугольники.
     * Если у полигона больше трех вершин, применяется алгоритм триангуляции.
     */
    public void triangulate() {
        ArrayList<Polygon> triangulatedPolygons = new ArrayList<>();

        for (Polygon polygon : polygons) {
            if (polygon.isTriangle()) {
                triangulatedPolygons.add(polygon);
            } else {
                List<Integer> vertexIndices = polygon.getVertexIndices();
                List<Integer> textureIndices = polygon.getTextureVertexIndices();
                List<Integer> normalIndices = polygon.getNormalIndices();

                for (int i = 1; i < vertexIndices.size() - 1; i++) {
                    Polygon triangle = new Polygon();
                    // Вершины
                    triangle.addVertexIndex(vertexIndices.get(0));
                    triangle.addVertexIndex(vertexIndices.get(i));
                    triangle.addVertexIndex(vertexIndices.get(i + 1));

                    // Текстурные вершины
                    if (!textureIndices.isEmpty()) {
                        if (i + 1 < textureIndices.size()) {
                            triangle.addTextureVertexIndex(textureIndices.get(0));
                            triangle.addTextureVertexIndex(textureIndices.get(i));
                            triangle.addTextureVertexIndex(textureIndices.get(i + 1));
                        } else {
                            triangle.addTextureVertexIndex(textureIndices.get(0));
                            triangle.addTextureVertexIndex(textureIndices.get(i % textureIndices.size()));
                            triangle.addTextureVertexIndex(textureIndices.get((i + 1) % textureIndices.size()));
                        }
                    }

                    // Нормали
                    if (!normalIndices.isEmpty()) {
                        if (i + 1 < normalIndices.size()) {
                            triangle.addNormalIndex(normalIndices.get(0));
                            triangle.addNormalIndex(normalIndices.get(i));
                            triangle.addNormalIndex(normalIndices.get(i + 1));
                        } else {
                            triangle.addNormalIndex(normalIndices.get(0));
                            triangle.addNormalIndex(normalIndices.get(i % normalIndices.size()));
                            triangle.addNormalIndex(normalIndices.get((i + 1) % normalIndices.size()));
                        }
                    }

                    triangulatedPolygons.add(triangle);
                }
            }
        }
        polygons.clear();
        polygons.addAll(triangulatedPolygons);
    }

    /**
     * Вычисляет нормали для каждого полигона и усредняет их для каждой вершины.
     */
    public void calculateNormals() {
        normals.clear();
        for (int i = 0; i < vertices.size(); i++) {
            normals.add(new Vector3f(0, 0, 0));
        }

        for (Polygon polygon : polygons) {
            List<Integer> vertexIndices = polygon.getVertexIndices();

            Vector3f v0 = vertices.get(vertexIndices.get(0));
            Vector3f v1 = vertices.get(vertexIndices.get(1));
            Vector3f v2 = vertices.get(vertexIndices.get(2));

            Vector3f edge1 = v1.sub(v0);
            Vector3f edge2 = v2.sub(v0);

            Vector3f faceNormal = edge1;
            faceNormal.cross(edge2).normalize();

            for (int index : vertexIndices) {
                normals.set(index, normals.get(index).add(faceNormal));
            }
        }

        for (int i = 0; i < normals.size(); i++) {

            Vector3f normal = normals.get(i);
            normal.normalize();
            normals.set(i, normal);
        }
    }
}package com.cgvsu.model;

import java.util.ArrayList;
import java.util.List;

/**
 * Представляет многоугольник в 3D модели, содержащий индексы вершин, текстурных вершин и нормалей.
 */
public class Polygon {
    /**
     * Список индексов вершин многоугольника.
     */
    private List<Integer> vertexIndices;

    /**
     * Список индексов текстурных вершин многоугольника.
     */
    private List<Integer> textureVertexIndices;

    /**
     * Список индексов нормалей многоугольника.
     */
    private List<Integer> normalIndices;

    /**
     * Конструктор для инициализации списков индексов вершин, текстурных вершин и нормалей.
     */
    public Polygon() {
        this.vertexIndices = new ArrayList<>();
        this.textureVertexIndices = new ArrayList<>();
        this.normalIndices = new ArrayList<>();
    }

    /**
     * Устанавливает список индексов вершин многоугольника.
     *
     * @param vertexIndices Список индексов вершин. Должен содержать не менее трех элементов.
     * @throws IllegalArgumentException если количество вершин меньше трех.
     */
    public void setVertexIndices(List<Integer> vertexIndices) {
        if (vertexIndices.size() < 3) {
            throw new IllegalArgumentException("Многоугольник должен содержать не менее трех вершин.");
        }
        this.vertexIndices = new ArrayList<>(vertexIndices);
    }

    /**
     * Устанавливает список индексов текстурных вершин многоугольника.
     *
     * @param textureVertexIndices Список индексов текстурных вершин.
     */
    public void setTextureVertexIndices(List<Integer> textureVertexIndices) {
        this.textureVertexIndices = new ArrayList<>(textureVertexIndices);
    }

    /**
     * Устанавливает список индексов нормалей многоугольника.
     *
     * @param normalIndices Список индексов нормалей.
     */
    public void setNormalIndices(List<Integer> normalIndices) {
        this.normalIndices = new ArrayList<>(normalIndices);
    }

    /**
     * Возвращает список индексов вершин многоугольника.
     *
     * @return Список индексов вершин.
     */
    public List<Integer> getVertexIndices() {
        return vertexIndices;
    }

    /**
     * Возвращает список индексов текстурных вершин многоугольника.
     *
     * @return Список индексов текстурных вершин.
     */
    public List<Integer> getTextureVertexIndices() {
        return textureVertexIndices;
    }

    /**
     * Возвращает список индексов нормалей многоугольника.
     *
     * @return Список индексов нормалей.
     */
    public List<Integer> getNormalIndices() {
        return normalIndices;
    }

    /**
     * Добавляет индекс вершины к многоугольнику.
     *
     * @param index Индекс вершины.
     */
    public void addVertexIndex(int index) {
        vertexIndices.add(index);
    }

    /**
     * Добавляет индекс текстурной вершины к многоугольнику.
     *
     * @param index Индекс текстурной вершины.
     */
    public void addTextureVertexIndex(int index) {
        textureVertexIndices.add(index);
    }

    /**
     * Добавляет индекс нормали к многоугольнику.
     *
     * @param index Индекс нормали.
     */
    public void addNormalIndex(int index) {
        normalIndices.add(index);
    }

    /**
     * Проверяет, является ли многоугольник треугольником.
     *
     * @return {@code true}, если многоугольник имеет три вершины, иначе {@code false}.
     */
    public boolean isTriangle() {
        return vertexIndices.size() == 3;
    }
}package com.cgvsu.model;

import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.paint.Color;

public class Texture {
    private Image image;
    private PixelReader pixelReader;
    private int width;
    private int height;

    public Texture(String filePath) {
        image = new Image(filePath);
        pixelReader = image.getPixelReader();
        width = (int) image.getWidth();
        height = (int) image.getHeight();
    }

    /**
     * Получает цвет по текстурным координатам (u, v).
     *
     * @param u Координата U (от 0 до 1).
     * @param v Координата V (от 0 до 1).
     * @return Цвет пикселя.
     */
    public Color getColor(float u, float v) {
        // Убедимся, что координаты находятся в диапазоне [0,1]
        u = clamp(u, 0.0f, 1.0f);
        v = clamp(v, 0.0f, 1.0f);

        // Преобразуем UV в координаты пикселя
        int x = Math.min((int) (u * (width - 1)), width - 1);
        int y = Math.min((int) ((1 - v) * (height - 1)), height - 1); // Инвертируем V

        return pixelReader.getColor(x, y);
    }

    private float clamp(float value, float min, float max) {
        return Math.max(min, Math.min(max, value));
    }
}
мне надо измменить canvas на PixelBuffer CPU