добавь поддержку нескольких источников освещения и возможности между ними переключаться
package com.cgvsu.render_engine;

import com.cgvsu.math.Vector3f;
import java.util.*;

public class LightingManager {
    private Map<Integer, Vector3f> lightingVectors;
    private int activeLightingVector;
    private int nextLightingId;
    private boolean isBoundToCamera;
    private Camera boundCamera;

    public LightingManager() {
        lightingVectors = new HashMap<>();
        nextLightingId = 0;
        isBoundToCamera = false;

        // Initialize with default lighting vector
        Vector3f defaultLighting = new Vector3f(0, 0, 1); // Default direction
        addLightingVector(defaultLighting);
        activeLightingVector = 0;
    }

    public int addLightingVector(Vector3f vector) {
        int id = nextLightingId++;
        lightingVectors.put(id, vector);
        return id;
    }

    public void removeLightingVector(int id) {
        if (lightingVectors.size() > 1) { // Keep at least one vector
            lightingVectors.remove(id);
            if (activeLightingVector == id) {
                activeLightingVector = lightingVectors.keySet().iterator().next();
            }
        }
    }

    public Vector3f getActiveLightingVector() {
        if (isBoundToCamera && boundCamera != null) {
            return boundCamera.getPosition();
        }
        return lightingVectors.get(activeLightingVector);
    }

    public void setActiveLightingVector(int id) {
        if (lightingVectors.containsKey(id)) {
            activeLightingVector = id;
            // When switching vectors, unbind from camera if bound
            unbindFromCamera();
        }
    }

    public void updateLightingVector(int id, Vector3f vector) {
        lightingVectors.put(id, vector);
    }

    public void bindToCamera(Camera camera) {
        this.boundCamera = camera;
        this.isBoundToCamera = true;
    }

    public void unbindFromCamera() {
        this.boundCamera = null;
        this.isBoundToCamera = false;
    }

    public boolean isBoundToCamera() {
        return isBoundToCamera;
    }

    public List<Integer> getLightingVectorIds() {
        return new ArrayList<>(lightingVectors.keySet());
    }
}

  private LightingManager lightingManager;

    public RenderEngine() {
        lightingManager = new LightingManager();
    }

    public LightingManager getLightingManager() {
        return lightingManager;
    }

    private boolean enableParallelization = true;

    private boolean enableWireframe = false; // Отрисовка каркаса модели (wireframe)
    private boolean enablePolygonalGrid = false; // Отрисовка полигональной сетки
    private boolean enableFillPolygon = false; // Заливка полигонов
    private boolean enableInterpolatedLighting = false; // Освещение с интерполяцией нормалей
    private boolean enableFlatLighting = false; // Плоское освещение
    private boolean enableTextureMapping = false; // Наложение текстур

    private float lightingCoefficient = 0.9f; // Коэффициент освещения (k)

    public void setLightDirection(Vector3f lightDirection) {
        this.lightDirection = lightDirection;
    }

    private Vector3f lightDirection = new Vector3f(1, 1, 1); // Направление света


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.*;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
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
    public CheckBox checkBoxUseFillPolygon;
    @FXML
    public CheckBox checkBoxUseIntrLighting;
    @FXML
    public CheckBox checkBoxUseFlatLighting;

    public CheckBox checkBoxWireframe;

    public CheckBox checkboxUsePolygonalGrid;

    @FXML
    private VBox lightingVectorsManager;

    @FXML
    private Button bindToCameraButton;

    @FXML
    private Button unbindFromCameraButton;

    @FXML
    private CheckBox checkBoxUseTexture;
    @FXML
    AnchorPane anchorPane;
    RenderEngine renderEngine = new RenderEngine();
    @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 TextField textFieldLightingModelDirectionX;
    @FXML
    private TextField textFieldLightingModelDirectionY;
    @FXML
    private TextField textFieldLightingModelDirectionZ;

    private CameraManager cameraManager = new CameraManager();

    private Model mesh = null;
    private int counterModels = 0;
    private int counterCameras = 0;
    private int counterLightingModels = 0;



    private Timeline timeline;

    private boolean isLeftButtonPressed = false;
    private double lastMouseX, lastMouseY;
    private boolean isCtrlPressed = false;
    private float speed = 0.01F;
    private float distance = 100.0f; // Расстояние от камеры до цели
    private float azimuth = 0.0f;    // Азимут (горизонтальный угол)
    private float elevation = 0.0f;  // Склонение (вертикальный угол)

    @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();
            // todo: обработка ошибок
        } catch (IOException exception) {

        }
    }

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

    @FXML
    public void initialize() {
        this.azimuth = 0.0f;
        this.elevation = 0.0f;
        this.distance = 100.0f;

        // Устанавливаем начальное положение камеры

        canvas.setOnScroll(event -> {
            double delta = event.getDeltaY();
            float zoomSensitivity = 0.1f;
            distance -= (float) (delta * zoomSensitivity);
            distance = Math.max(10.0f, distance); // Минимальное расстояние
            updateCameraPosition();
            render();
        });

        // Создаем группу
        ToggleGroup toggleGroup = new ToggleGroup();

        // Добавляем RadioMenuItem в группу
        withParallelizationMenuItem.setToggleGroup(toggleGroup);
        withoutParallelizationMenuItem.setToggleGroup(toggleGroup);

        // Устанавливаем начальное состояние (по желанию)
        withParallelizationMenuItem.setSelected(true);

        bindToCameraButton.setOnAction(event -> bindLightingToCamera());
        unbindFromCameraButton.setOnAction(event -> unbindLightingFromCamera());
        unbindFromCameraButton.setVisible(false);

        canvas.setOnMousePressed(this::handleMousePressed);
        canvas.setOnMouseReleased(this::handleMouseReleased);
        canvas.setOnMouseDragged(this::handleMouseDragged);

        updateCameraPosition();
        startRendering();
    }


// вообще, по красоте, это все надо в камеру, наверное всунуть... раз уж мы будем делать несколько камер

    private void updateCameraPosition() {
        // Преобразуем сферические координаты в декартовы

        double radAzimuth = Math.toRadians(azimuth);
        double radElevation = Math.toRadians(elevation);


        float x = (float) (distance * Math.cos(radElevation) * Math.sin(radAzimuth));
        float y = (float) (distance * Math.sin(radElevation));
        float z = (float) (distance * Math.cos(radElevation) * Math.cos(radAzimuth));

        Vector3f newPosition = new Vector3f(x, y, z).add(cameraManager.getActiveCamera().getTarget());
        cameraManager.getActiveCamera().setPosition(newPosition);
    }

    private void handleMousePressed(MouseEvent event) {
        if (event.isPrimaryButtonDown()) {
            isLeftButtonPressed = true;
            lastMouseX = event.getX();
            lastMouseY = event.getY();
            isCtrlPressed = event.isControlDown();
        }
    }

    private void handleMouseReleased(MouseEvent event) {
        if (!event.isPrimaryButtonDown()) {
            isLeftButtonPressed = false;
        }
    }

    private void handleMouseDragged(MouseEvent event) {

        if (isLeftButtonPressed) {
            double deltaX = event.getX() - lastMouseX;
            double deltaY = event.getY() - lastMouseY;

            if (!isCtrlPressed) {
                float sensitivity = 0.5f; // Чувствительность вращения
                azimuth += (float) (deltaX * sensitivity);
                elevation += (float) (deltaY * sensitivity);


                // я манал, он зеркалит, а с этой фигней не вращает до конца... .... ... ... .. . . .. ..
                elevation = Math.max(-87, Math.min(87, elevation));

                azimuth = azimuth % 360;
                if (azimuth < 0) {
                    azimuth += 360;
                }
                updateCameraPosition();
            } else {

                // перемещение цели камеры
                float panSensitivity = 0.05f;
                Vector3f right = cameraManager.getActiveCamera().getTarget().sub(cameraManager.getActiveCamera().getPosition());
                right.normalize();

                Vector3f up = new Vector3f(0, 1, 0);
                right = right.cross(up);
                right.normalize();


                right.multiply((float) deltaX * panSensitivity);
                up.multiply((float) deltaY * panSensitivity);
                right = right.add(up);

                cameraManager.getActiveCamera().setTarget(cameraManager.getActiveCamera().getTarget().add(right));
                updateCameraPosition();
            }

            lastMouseX = event.getX();
            lastMouseY = event.getY();

            render();
        }
    }

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

    @FXML
    public void handleCameraBackward(ActionEvent actionEvent) {

        Vector3f currentPosition = cameraManager.getActiveCamera().getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, 0, TRANSLATION));
        cameraManager.getActiveCamera().setPosition(newPosition);
    }

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

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

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

    @FXML
    public void handleCameraDown(ActionEvent actionEvent) {
        Vector3f currentPosition = cameraManager.getActiveCamera().getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, -TRANSLATION, 0));
        cameraManager.getActiveCamera().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 addNewLightingVector() {
        // Create new lighting vector with default direction
        Vector3f newLightingVector = new Vector3f(0, 0, 1);
        int lightingId = renderEngine.getLightingManager().addLightingVector(newLightingVector);

        Button lightingButton = new Button("Light " + lightingId);
        lightingButton.setOnAction(event -> switchToLighting(lightingId));

        Button deleteButton = new Button("Delete");
        deleteButton.setOnAction(event -> deleteLightingVector(lightingId));

        // Create input fields for vector components
        TextField vectorX = new TextField("0");
        TextField vectorY = new TextField("0");
        TextField vectorZ = new TextField("1");

        // Add listeners to update vector when values change
        EventHandler<ActionEvent> updateVector = e -> {
            try {
                float x = Float.parseFloat(vectorX.getText());
                float y = Float.parseFloat(vectorY.getText());
                float z = Float.parseFloat(vectorZ.getText());
                Vector3f updatedVector = new Vector3f(x, y, z);
                renderEngine.getLightingManager().updateLightingVector(lightingId, updatedVector);
                render();
            } catch (NumberFormatException ex) {
                showErrorAlert("Invalid Input", "Please enter valid numbers for vector components","hz");
            }
        };

        vectorX.setOnAction(updateVector);
        vectorY.setOnAction(updateVector);
        vectorZ.setOnAction(updateVector);

        HBox controls = new HBox(5, lightingButton, vectorX, vectorY, vectorZ, deleteButton);
        lightingVectorsManager.getChildren().add(controls);
    }

    private void switchToLighting(int lightingId) {
        renderEngine.getLightingManager().setActiveLightingVector(lightingId);
        render();
    }

    private void deleteLightingVector(int lightingId) {
        renderEngine.getLightingManager().removeLightingVector(lightingId);
        lightingVectorsManager.getChildren().removeIf(node -> {
            if (node instanceof HBox) {
                return ((HBox) node).getChildren().stream()
                        .anyMatch(child -> child instanceof Button &&
                                ((Button) child).getText().equals("Light " + lightingId));
            }
            return false;
        });
        render();
    }

    private void bindLightingToCamera() {
        renderEngine.getLightingManager().bindToCamera(cameraManager.getActiveCamera());
        bindToCameraButton.setVisible(false);
        unbindFromCameraButton.setVisible(true);
        render();
    }
    private void unbindLightingFromCamera() {
        renderEngine.getLightingManager().unbindFromCamera();
        bindToCameraButton.setVisible(true);
        unbindFromCameraButton.setVisible(false);
        render();
    }



    private void startRendering() {
        if (timeline == null) {
            timeline = new Timeline(new KeyFrame(Duration.millis(20), event -> render()));
            timeline.setCycleCount(Animation.INDEFINITE);
            timeline.play();
        }
    }

    private void render() {
        double width = canvas.getWidth();
        double height = canvas.getHeight();

        canvas.getGraphicsContext2D().clearRect(0, 0, width, height);
        Camera activeCamera = cameraManager.getActiveCamera();
        activeCamera.setAspectRatio((float) (width / height));

        if (mesh != null) {
            renderEngine.render(canvas.getGraphicsContext2D(), activeCamera, mesh, (int) width, (int) height);
        }
    }


    private void addModelControlButtons() {
        counterModels++;

        Button modelButton = new Button("Model " + counterModels);
        modelButton.setOnAction(event -> selectActiveModel(counterModels));


        Button addTextureButton = new Button("Add Texture");
        addTextureButton.setOnAction(event -> addTexture(counterModels));


        Button removeTextureButton = new Button("Remove Texture");
        removeTextureButton.setOnAction(event -> removeTexture(counterModels));


        Button deleteModelButton = new Button("Delete");
        deleteModelButton.setOnAction(event -> deleteModel(counterModels, modelButton));


        HBox modelControls = new HBox(5, modelButton, addTextureButton, removeTextureButton, deleteModelButton);


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

    private void selectActiveModel(int modelId) {

    }

    private void addTexture(int modelId) {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.png)", "*.png"));
        fileChooser.setTitle("Load Textrure");

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

        File file = fileChooser.showOpenDialog((Stage) canvas.getScene().getWindow());
        if (file == null) {
            return;
        }
        String filePath = file.getAbsolutePath();

        mesh.loadTexture(filePath);

    }

    public void loadModel() {
        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;
        }

        try {
            String fileContent = Files.readString(Path.of(file.getAbsolutePath()));
            mesh = ObjReader.read(fileContent);
            mesh.calculateNormals();
            mesh.triangulate();

            addModelControlButtons();
            startRendering();
        } catch (IOException e) {
            showErrorAlert("Ошибка загрузки модели", null, "Не удалось загрузить модель: " + e.getMessage());
        }
    }

    private void removeTexture(int modelId) {
        mesh.clearTexture();
    }

    private void deleteModel(int modelId, Button modelButton) {
        // Удаление модели и её контролов
        mesh = null; // Убираем текущую модель
        modelsManager.getChildren().removeIf(node -> node instanceof HBox hBox && hBox.getChildren().contains(modelButton));
    }




    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() {

    }


    @FXML
    private void handleCheckBoxDrawWireframe() {
        renderEngine.setEnableWireframe(checkBoxWireframe.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseTexture() {
        renderEngine.setEnableTextureMapping(checkBoxUseTexture.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseIntrLighting() {
        renderEngine.setEnableInterpolatedLighting(checkBoxUseIntrLighting.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseFlatLighting() {
        renderEngine.setEnableFlatLighting(checkBoxUseFlatLighting.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxFillPolygon() {
        renderEngine.setEnableFillPolygon(checkBoxUseFillPolygon.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxDrawPolygonalGrid() {
        renderEngine.setEnablePolygonalGrid(checkboxUsePolygonalGrid.isSelected());
    }


    @FXML
    private RadioMenuItem withParallelizationMenuItem;

    @FXML
    private RadioMenuItem withoutParallelizationMenuItem;

    @FXML
    private void toggleParallelization() {

        boolean isParallelizationEnabled = withParallelizationMenuItem.isSelected();
        renderEngine.setEnableParallelization(isParallelizationEnabled);
    }

    @FXML
    private void addNewCamera() {
        // Create new camera with default parameters
        Camera newCamera = new Camera(
                new Vector3f(0, 0, 100),
                new Vector3f(0, 0, 0),
                0.3F, 1, 25F, 1000
        );

        int cameraId = cameraManager.addCamera(newCamera);

        Button cameraButton = new Button("Camera " + cameraId);
        cameraButton.setOnAction(event -> switchToCamera(cameraId));

        Button deleteCamera = new Button("Delete");
        deleteCamera.setOnAction(event -> deleteCamera(cameraId));

        HBox camera = new HBox(5, cameraButton, deleteCamera);
        camerasManager.getChildren().add(camera);
    }

    private void switchToCamera(int cameraId) {
        cameraManager.setActiveCamera(cameraId);

        // Update UI fields with new camera parameters
        Camera activeCamera = cameraManager.getActiveCamera();
        textFieldCameraPositionX.setText(String.valueOf(activeCamera.getPosition().getX()));
        textFieldCameraPositionY.setText(String.valueOf(activeCamera.getPosition().getY()));
        textFieldCameraPositionZ.setText(String.valueOf(activeCamera.getPosition().getZ()));

        Vector3f target = activeCamera.getTarget();
        textFieldCameraPointOfDirectionX.setText(String.valueOf(target.getX()));
        textFieldCameraPointOfDirectionY.setText(String.valueOf(target.getY()));
        textFieldCameraPointOfDirectionZ.setText(String.valueOf(target.getZ()));

        render();
    }

    private void deleteCamera(int cameraId) {
        cameraManager.removeCamera(cameraId);
        camerasManager.getChildren().removeIf(node -> {
            if (node instanceof HBox) {
                return ((HBox) node).getChildren().stream()
                        .anyMatch(child -> child instanceof Button &&
                                ((Button) child).getText().equals("Camera " + cameraId));
            }
            return false;
        });
        render();
    }
}