мой код слишком медленный, его нужно оптимизировать
/**
 * Класс для рендеринга 3D моделей с использованием графического контекста JavaFX.
 */
public class RenderEngine {

    private boolean enableWireframe = false; // Отрисовка каркаса модели с использованием Z-буфера для линий
    private boolean enableFillPolygon = true; // Заливка полигонов
    private boolean enableInterpolatedLighting = false; // Освещение с интерполяцией нормалей
    private boolean enableFlatLighting = false; // Освещение без интерполяции нормалей
    private boolean enableTextureMapping = true; // Наложение текстур

    private float lightingCoefficient = 0.9f; // Коэффициент освещения (k)
    private Vector3f lightDirection = new Vector3f(1, 1, 1); // Направление света

    /**
     * Устанавливает режим отрисовки каркаса.
     *
     * @param enableWireframe true для включения, false для отключения.
     */
    public void setEnableWireframe(boolean enableWireframe) {
        this.enableWireframe = enableWireframe;
    }

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

    /**
     * Устанавливает режим освещения с интерполяцией нормалей.
     *
     * @param enableInterpolatedLighting true для включения, false для отключения.
     */
    public void setEnableInterpolatedLighting(boolean enableInterpolatedLighting) {
        this.enableInterpolatedLighting = enableInterpolatedLighting;
    }

    /**
     * Устанавливает режим плоского освещения без интерполяции нормалей.
     *
     * @param enableFlatLighting true для включения, false для отключения.
     */
    public void setEnableFlatLighting(boolean enableFlatLighting) {
        this.enableFlatLighting = enableFlatLighting;
    }

    /**
     * Устанавливает режим наложения текстур.
     *
     * @param enableTextureMapping true для включения, false для отключения.
     */
    public void setEnableTextureMapping(boolean enableTextureMapping) {
        this.enableTextureMapping = enableTextureMapping;
    }

    // Остальные методы установки параметров освещения и т.д.

    /**
     * Вычисляет матрицу модель-представление-проекция с учетом камеры и трансформаций модели.
     *
     * @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;
    }

    /**
     * Инициализирует Z-буфер заданными размерами.
     *
     * @param width  Ширина экрана.
     * @param height Высота экрана.
     * @return Двумерный массив Z-буфера, заполненный максимальными значениями глубины.
     */
    private float[][] initializeZBuffer(int width, int height) {
        float[][] zBuffer = new float[width][height];
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                zBuffer[x][y] = Float.MAX_VALUE;
            }
        }
        return zBuffer;
    }

    /**
     * Выполняет рендеринг 3D модели на графическом контексте.
     *
     * @param graphicsContext Графический контекст JavaFX для рисования.
     * @param camera          Объект камеры, определяющий вид и проекцию.
     * @param model           Модель для рендеринга.
     * @param width           Ширина области рендеринга.
     * @param height          Высота области рендеринга.
     */
    public void render(GraphicsContext graphicsContext, Camera camera, Model model, int width, int height) {
        float[][] zBuffer = initializeZBuffer(width, height);
        Matrix4f mvpMatrix = calculateModelViewProjectionMatrix(camera, model.getRotate(), model.getScale(), model.getTranslate());

        Texture texture = model.getTexture();

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

        if (enableWireframe) {
            renderWireframe(graphicsContext, model, mvpMatrix, width, height);
        }
    }

    /**
     * Рендерит отдельный многоугольник на графическом контексте.
     *
     * @param graphicsContext Графический контекст для рисования.
     * @param polygon         Многоугольник для рендеринга.
     * @param mvpMatrix       Матрица модель-представление-проекция.
     * @param width           Ширина области рендеринга.
     * @param height          Высота области рендеринга.
     * @param zBuffer         Z-буфер для отслеживания глубины пикселей.
     * @param model           Модель, содержащая вершины.
     * @param texture         Текстура модели.
     */
    private void renderPolygon(GraphicsContext graphicsContext, Polygon polygon, Matrix4f mvpMatrix,
                               int width, int height, float[][] zBuffer, Model model, Texture 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 = null;
        Vector3f[] vertexNormals = null;

        boolean needNormals = enableInterpolatedLighting || enableFlatLighting;
        boolean needTexture = enableTextureMapping && texture != null && textureIndices.length == 3;

        if (needNormals) {
            vertexNormals = new Vector3f[3];
        }
        if (needTexture) {
            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 (needNormals) {
                vertexNormals[i] = model.getNormals().get(vertexIndices[i]);
            }

            if (needTexture) {
                Vector2f texCoord = model.getTextureVertices().get(textureIndices[i]);
                textureCoords[i] = texCoord;
            }
        }

        if (enableFillPolygon) {
            rasterizeTriangle(screenPoints, vertexNormals, zValues, textureCoords, zBuffer, graphicsContext, texture);
        }
    }

    /**
     * Выполняет растеризацию треугольника с учетом всех настроек и обновляет графический контекст.
     *
     * @param points          Координаты точек треугольника на экране.
     * @param normals         Нормали вершин (или null, если не нужны).
     * @param zValues         Значения глубины для каждой вершины.
     * @param textureCoords   Текстурные координаты для каждой вершины (или null, если не используются).
     * @param zBuffer         Z-буфер для отслеживания глубины пикселей.
     * @param graphicsContext Графический контекст для рисования.
     * @param texture         Текстура модели (или null, если не используется).
     */
    private void rasterizeTriangle(Point2f[] points, Vector3f[] normals, float[] zValues,
                                   Vector2f[] textureCoords, float[][] zBuffer, GraphicsContext graphicsContext, Texture texture) {
        int minX = clamp((int) Math.floor(Math.min(points[0].getX(), Math.min(points[1].getX(), points[2].getX()))), 0, zBuffer.length - 1);
        int maxX = clamp((int) Math.ceil(Math.max(points[0].getX(), Math.max(points[1].getX(), points[2].getX()))), 0, zBuffer.length - 1);
        int minY = clamp((int) Math.floor(Math.min(points[0].getY(), Math.min(points[1].getY(), points[2].getY()))), 0, zBuffer[0].length - 1);
        int maxY = clamp((int) Math.ceil(Math.max(points[0].getY(), Math.max(points[1].getY(), points[2].getY()))), 0, zBuffer[0].length - 1);

        lightDirection.normalize();

        for (int x = minX; x <= maxX; x++) {
            for (int y = minY; y <= maxY; y++) {
                float[] barycentric = calculateBarycentricCoordinates(points, x, y);

                if (isInsideTriangle(barycentric[0], barycentric[1], barycentric[2])) {
                    float depth = interpolate(barycentric[0], barycentric[1], barycentric[2], zValues);

                    if (depth < zBuffer[x][y]) {
                        Color finalColor = Color.AQUA; // Базовый цвет

                        // Текстурирование
                        if (enableTextureMapping && texture != null && textureCoords != null) {
                            float u = interpolate(barycentric[0], barycentric[1], barycentric[2], new float[]{
                                    textureCoords[0].getX(),
                                    textureCoords[1].getX(),
                                    textureCoords[2].getX()
                            });
                            float v = interpolate(barycentric[0], barycentric[1], barycentric[2], new float[]{
                                    textureCoords[0].getY(),
                                    textureCoords[1].getY(),
                                    textureCoords[2].getY()
                            });
                            finalColor = texture.getColor(u, v);
                        }

                        // Освещение
                        if ((enableInterpolatedLighting || enableFlatLighting) && normals != null) {
                            Vector3f normal;

                            if (enableInterpolatedLighting) {
                                normal = interpolateNormal(barycentric[0], barycentric[1], barycentric[2], normals);
                            } else {
                                normal = calculateFaceNormal(normals);
                            }

                            finalColor = applyLighting(finalColor, normal);
                        }

                        zBuffer[x][y] = depth;
                        graphicsContext.getPixelWriter().setColor(x, y, finalColor);
                    }
                }
            }
        }
    }

    /**
     * Вычисляет нормаль грани для плоского освещения.
     *
     * @param normals Нормали вершин.
     * @return Средняя нормаль грани.
     */
    private Vector3f calculateFaceNormal(Vector3f[] normals) {
        Vector3f averageNormal = new Vector3f(
                (normals[0].getX() + normals[1].getX() + normals[2].getX()) / 3,
                (normals[0].getY() + normals[1].getY() + normals[2].getY()) / 3,
                (normals[0].getZ() + normals[1].getZ() + normals[2].getZ()) / 3
        );
        averageNormal.normalize();
        return averageNormal;
    }

    /**
     * Применяет освещение к базовому цвету.
     *
     * @param baseColor Цвет до освещения.
     * @param normal    Нормаль в точке.
     * @return Цвет после применения освещения.
     */
    private Color applyLighting(Color baseColor, Vector3f normal) {
        float diffuse = Math.max(0, normal.dot(lightDirection));
        float brightness = lightingCoefficient * diffuse;

        return new Color(
                clamp(baseColor.getRed() * brightness),
                clamp(baseColor.getGreen() * brightness),
                clamp(baseColor.getBlue() * brightness),
                baseColor.getOpacity()
        );
    }

    /**
     * Интерполирует нормаль на основе барицентрических координат.
     *
     * @param lambda0 Барицентрическая координата λ0.
     * @param lambda1 Барицентрическая координата λ1.
     * @param lambda2 Барицентрическая координата λ2.
     * @param normals Набор нормалей вершин.
     * @return Интерполированная нормаль.
     */
    private Vector3f interpolateNormal(float lambda0, float lambda1, float lambda2, Vector3f[] normals) {
        Vector3f interpolatedNormal = new Vector3f(
                lambda0 * normals[0].getX() + lambda1 * normals[1].getX() + lambda2 * normals[2].getX(),
                lambda0 * normals[0].getY() + lambda1 * normals[1].getY() + lambda2 * normals[2].getY(),
                lambda0 * normals[0].getZ() + lambda1 * normals[1].getZ() + lambda2 * normals[2].getZ()
        );
        interpolatedNormal.normalize();
        return interpolatedNormal;
    }



    /**
     * Выполняет отрисовку каркаса модели (wireframe) с использованием встроенных методов JavaFX.
     *
     * @param graphicsContext Графический контекст для рисования.
     * @param model           Модель для рендеринга.
     * @param mvpMatrix       Матрица модель-представление-проекция.
     * @param width           Ширина области рендеринга.
     * @param height          Высота области рендеринга.
     */
    private void renderWireframe(GraphicsContext graphicsContext, Model model, Matrix4f mvpMatrix, int width, int height) {
        for (Polygon polygon : model.getPolygons()) {
            int[] vertexIndices = polygon.getVertexIndices().stream().mapToInt(Integer::intValue).toArray();

            Point2f[] screenPoints = new Point2f[vertexIndices.length];

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

            for (int i = 0; i < vertexIndices.length; i++) {
                int next = (i + 1) % vertexIndices.length;
                // Используем встроенный метод strokeLine для отрисовки линии между двумя точками
                graphicsContext.strokeLine(
                        screenPoints[i].getX(), screenPoints[i].getY(),
                        screenPoints[next].getX(), screenPoints[next].getY()
                );
            }
        }
    }


    /**
     * Преобразует вершину в экранные координаты.
     *
     * @param vertex Вершина в пространстве камеры.
     * @param width  Ширина экрана.
     * @param height Высота экрана.
     * @return Точка на экране.
     */
    private Point2f vertexToPoint(Vector3f vertex, int width, int height) {
        float x = (vertex.getX() + 1) * 0.5f * width;
        float y = (1 - (vertex.getY() + 1) * 0.5f) * height; // Инвертирование Y
        return new Point2f(x, y);
    }

    /**
     * Применяет трансформацию к вершине модели.
     *
     * @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()));

        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 baseColor Базовый цвет.
     * @param normals   Массив нормалей.
     * @return Цвет после применения освещения.
     */
    private Color applyFlatLighting(Color baseColor, Vector3f[] normals) {
        Vector3f averageNormal = new Vector3f(
                (normals[0].getX() + normals[1].getX() + normals[2].getX()) / 3,
                (normals[0].getY() + normals[1].getY() + normals[2].getY()) / 3,
                (normals[0].getZ() + normals[1].getZ() + normals[2].getZ()) / 3
        );

        averageNormal.normalize();

        float diffuse = Math.max(0, averageNormal.dot(lightDirection));
        float brightness = lightingCoefficient * diffuse;

        return new Color(
                clamp(baseColor.getRed() * brightness),
                clamp(baseColor.getGreen() * brightness),
                clamp(baseColor.getBlue() * brightness),
                baseColor.getOpacity()
        );
    }



    /**
     * Ограничивает значение в диапазоне [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));
    }
}