Мне нужно наложить текстуру на 3д модельку. Текстура хранится в виде картинки пнг. Моделька хранится в формате obj. Так же добавь возможность выбирать как отрисовывать модель, через растеризацию или наложением текстур. Создай для этого булевый флаг в renderengine. Вот код который у меня есть на данный момент

package com.cgvsu.model;

import com.cgvsu.math.Vector2f;
import com.cgvsu.math.Vector3f;

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

/**
 * Представляет 3D модель, содержащую вершины, текстурные вершины, нормали и полигоны.
 */
public class Model {
    /**
     * Список текстурных вершин.
     */
    public ArrayList<Vector2f> textureVertices = new ArrayList<>();

    /**
     * Список 3D вершин.
     */
    public ArrayList<Vector3f> vertices = new ArrayList<>();

    /**
     * Список нормалей вершин.
     */
    public ArrayList<Vector3f> normals = new ArrayList<>();

    /**
     * Список полигонов, составляющих модель.
     */
    public ArrayList<Polygon> polygons = new ArrayList<>();

    /**
     * Вектор поворота.
     */
    public Vector3f rotate = new Vector3f(0.0f, 0.0f, 0.0f);

    /**
     * Вектор сжатия.
     */
     public Vector3f scale = new Vector3f(1.0f, 1.0f, 1.0f);
    /**
     * Вектор переноса.
     */
     public Vector3f translate = new Vector3f(0.0f, 0.0f, 0.0f);

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

        for (Polygon polygon : polygons) {
            if (polygon.isTriangle()) {
                triangulatedPolygons.add(polygon);
            } else {
                List<Integer> vertexIndices = polygon.getVertexIndices();
                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 (!polygon.getTextureVertexIndices().isEmpty()) {
                        triangle.addTextureVertexIndex(polygon.getTextureVertexIndices().get(0));
                        triangle.addTextureVertexIndex(polygon.getTextureVertexIndices().get(i));
                        triangle.addTextureVertexIndex(polygon.getTextureVertexIndices().get(i + 1));
                    }

                    if (!polygon.getNormalIndices().isEmpty()) {
                        triangle.addNormalIndex(polygon.getNormalIndices().get(0));
                        triangle.addNormalIndex(polygon.getNormalIndices().get(i));
                        triangle.addNormalIndex(polygon.getNormalIndices().get(i + 1));
                    }

                    triangulatedPolygons.add(triangle);
                }
            }
        }
        polygons = triangulatedPolygons;
    }

    /**
     * Вычисляет нормали для каждого полигона и усредняет их для каждой вершины.
     */
    public void calculateNormals() {
        normals = new ArrayList<>(vertices.size());
        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.cross(edge2);
            faceNormal.normalize();

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

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

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.render_engine;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Point2f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.model.Model;
import com.cgvsu.model.Polygon;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

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

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

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

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

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

    /**
     * Инициализирует Z-буфер заданными размерами.
     *
     * @param width  Ширина экрана.
     * @param height Высота экрана.
     * @return Двумерный массив Z-буфера, заполненный максимальными значениями глубины.
     */
    private static 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 mesh            Модель для рендеринга.
     * @param width           Ширина области рендеринга.
     * @param height          Высота области рендеринга.
     */
    public static void render(
            final GraphicsContext graphicsContext,
            final Camera camera,
            final Model mesh,
            final int width,
            final int height) {

        float[][] zBuffer = initializeZBuffer(width, height);
        Matrix4f modelViewProjectionMatrix = calculateModelViewProjectionMatrix(camera, mesh.rotate, mesh.scale,mesh.translate);

        for (Polygon polygon : mesh.polygons) {
            renderPolygon(graphicsContext, polygon, modelViewProjectionMatrix, width, height, zBuffer, mesh);
        }
    }

    /**
     * Рендерит отдельный многоугольник на графическом контексте.
     *
     * @param graphicsContext         Графический контекст для рисования.
     * @param polygon                 Многоугольник для рендеринга.
     * @param modelViewProjectionMatrix Матрица модель-представление-проекция.
     * @param width                   Ширина области рендеринга.
     * @param height                  Высота области рендеринга.
     * @param zBuffer                 Z-буфер для отслеживания глубины пикселей.
     * @param mesh                    Модель, содержащая вершины.
     */
    private static void renderPolygon(
            GraphicsContext graphicsContext,
            Polygon polygon,
            Matrix4f modelViewProjectionMatrix,
            int width,
            int height,
            float[][] zBuffer,
            Model mesh) {

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

        for (int i = 0; i < 3; i++) {
            Vector3f transformedVertex = transformVertex(mesh, polygon, i, modelViewProjectionMatrix);
            screenPoints[i] = vertexToPoint(transformedVertex, width, height);
            zValues[i] = transformedVertex.getZ();
        }

        rasterizeTriangle(screenPoints, zValues, zBuffer, graphicsContext);
    }

    /**
     * Применяет трансформацию к вершине многоугольника.
     *
     * @param mesh                Модель, содержащая вершины.
     * @param polygon             Многоугольник, содержащий индексы вершин.
     * @param vertexIndex         Индекс вершины в многоугольнике.
     * @param transformationMatrix Матрица трансформации.
     * @return Трансформированная вершина.
     */
    private static Vector3f transformVertex(Model mesh, Polygon polygon, int vertexIndex, Matrix4f transformationMatrix) {
        Vector3f vertex = mesh.vertices.get(polygon.getVertexIndices().get(vertexIndex));
        Vector3f vertexVecmath = new Vector3f(vertex.getX(), vertex.getY(), vertex.getZ());
        return multiplyMatrix4ByVector3(transformationMatrix, vertexVecmath);
    }

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

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

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

                if (isPointInsideTriangle(lambda0, lambda1, lambda2)) {
                    float depth = interpolateDepth(lambda0, lambda1, lambda2, zValues);

                    if (depth < zBuffer[x][y]) {
                        zBuffer[x][y] = depth;
                        Color color = interpolateColor(lambda0, lambda1, lambda2, colorA, colorB, colorC);
                        graphicsContext.getPixelWriter().setColor(x, y, color);
                    }
                }
            }
        }
    }

    /**
     * Вычисляет барицентрические координаты точки относительно треугольника.
     *
     * @param points Координаты вершин треугольника.
     * @param x      Координата X точки.
     * @param y      Координата Y точки.
     * @return Массив из трех барицентрических координат.
     */
    private static float[] calculateBarycentricCoordinates(Point2f[] points, int x, int y) {
        float denominator = ((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())) / denominator;

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

        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 static boolean isPointInsideTriangle(float lambda0, float lambda1, float lambda2) {
        return lambda0 >= 0 && lambda1 >= 0 && lambda2 >= 0;
    }

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

    /**
     * Интерполирует цвет точки внутри треугольника на основе барицентрических координат.
     *
     * @param lambda0 Барицентрическая координата λ0.
     * @param lambda1 Барицентрическая координата λ1.
     * @param lambda2 Барицентрическая координата λ2.
     * @param colorA  Цвет первой вершины треугольника.
     * @param colorB  Цвет второй вершины треугольника.
     * @param colorC  Цвет третьей вершины треугольника.
     * @return Интерполированный цвет.
     */
    private static 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 static double clamp(double value) {
        return Math.max(0.0, Math.min(1.0, value));
    }
}

Причем обрати внимание, что модельку я триангулирую те разбиваю на треугольники, однако я никак не меняю текстурные вершины. обрати на это внимание и напиши весь необходимый код