Мне нужно добавить наложение текстур на 3д модельку. модель хранится во в таком формате

o Cube
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
vn -1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -0.0000 1.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vt 0.657719 0.070615
vt 0.657719 0.288779
vt 0.343923 0.288779
vt 0.343923 0.070615
vt 0.982747 0.538081
vt 0.982747 0.758131
vt 0.677420 0.758131
vt 0.677420 0.538418
vt 0.654372 0.535544
vt 0.654372 0.758215
vt 0.344166 0.758215
vt 0.344166 0.535750
vt 0.327407 0.537083
vt 0.327407 0.759706
vt 0.015414 0.759706
vt 0.015414 0.536937
vt 0.656108 0.299368
vt 0.656108 0.524271
vt 0.344171 0.524271
vt 0.344171 0.299368
vt 0.655295 0.773650
vt 0.655295 0.993820
vt 0.346502 0.993820
vt 0.346502 0.773650
s 0
f 1/1/1 2/2/1 4/3/1 3/4/1
f 3/5/2 4/6/2 8/7/2 7/8/2
f 7/9/3 8/10/3 6/11/3 5/12/3
f 5/13/4 6/14/4 2/15/4 1/16/4
f 3/17/5 7/18/5 5/19/5 1/20/5
f 8/21/6 4/22/6 2/23/6 6/24/6

что у меня есть
триангуляция модели
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 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.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) {
        Matrix4f modelMatrix = rotateScaleTranslate();
        Matrix4f viewMatrix = camera.getViewMatrix();
        Matrix4f projectionMatrix = camera.getProjectionMatrix();

        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);

        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));
    }
}