package com.cgvsu.render_engine;
import com.cgvsu.math.Vector3f;
import com.cgvsu.math.Matrix4f;

public class Camera {


public Camera(
        final Vector3f position,
        final Vector3f target,
        final float fov,
        final float aspectRatio,
        final float nearPlane,
        final float farPlane) {
    this.position = position;
    this.target = target;
    this.fov = fov;
    this.aspectRatio = aspectRatio;
    this.nearPlane = nearPlane;
    this.farPlane = farPlane;
}

public void setPosition(final Vector3f position) {
    this.position = position;
}

public void setTarget(final Vector3f target) {
    this.target = target;
}

public void setAspectRatio(final float aspectRatio) {
    this.aspectRatio = aspectRatio;
}

public Vector3f getPosition() {
    return position;
}

public Vector3f getTarget() {
    return target;
}

public void movePosition(final Vector3f translation) {
    this.position.add(translation);
}

public void moveTarget(final Vector3f translation) {
    this.target.add(target);
}

Matrix4f getViewMatrix() {
    System.out.println();
    return GraphicConveyor.lookAt(position, target);
}

Matrix4f getProjectionMatrix() {
    return GraphicConveyor.perspective(fov, aspectRatio, nearPlane, farPlane);
}

private Vector3f position;
private Vector3f target;
private float fov;
private float aspectRatio;
private float nearPlane;
private float farPlane;
}
package com.cgvsu;

import com.cgvsu.render_engine.RenderEngine;
import javafx.fxml.FXML;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javafx.stage.FileChooser;
import javafx.util.Duration;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.io.File;
import com.cgvsu.math.Vector3f;

import com.cgvsu.model.Model;
import com.cgvsu.objreader.ObjReader;
import com.cgvsu.render_engine.Camera;

public class GuiController {

final private float TRANSLATION = 0.5F;

@FXML
AnchorPane anchorPane;

@FXML
private Canvas canvas;

private Model mesh = null;

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


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.render(canvas.getGraphicsContext2D(), camera, mesh, (int) width, (int) height);
    }
});

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

@FXML
private void onOpenModelMenuItemClick() {
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (.obj)", ".obj"));
fileChooser.setTitle("Load Model");


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

}
}

@FXML
public void handleCameraForward(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(0, 0, -TRANSLATION));
}

@FXML
public void handleCameraBackward(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(0, 0, TRANSLATION));
}

@FXML
public void handleCameraLeft(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(TRANSLATION, 0, 0));
}

@FXML
public void handleCameraRight(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(-TRANSLATION, 0, 0));
}

@FXML
public void handleCameraUp(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(0, TRANSLATION, 0));
}

@FXML
public void handleCameraDown(ActionEvent actionEvent) {
camera.movePosition(new Vector3f(0, -TRANSLATION, 0));
}
}package com.cgvsu.render_engine;

import java.util.ArrayList;

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

import javafx.scene.canvas.GraphicsContext;
import com.cgvsu.model.Model;
import static com.cgvsu.render_engine.GraphicConveyor.*;
import com.cgvsu.math.Matrix4f;

public class RenderEngine {

public static void render(
final GraphicsContext graphicsContext,
final Camera camera,
final Model mesh,
final int width,
final int height)
{
Matrix4f modelMatrix = rotateScaleTranslate();
Matrix4f viewMatrix = camera.getViewMatrix();
Matrix4f projectionMatrix = camera.getProjectionMatrix();


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

final int nPolygons = mesh.polygons.size();
for (int polygonInd = 0; polygonInd < nPolygons; ++polygonInd) {
    final int nVerticesInPolygon = mesh.polygons.get(polygonInd).getVertexIndices().size();

    ArrayList<Vector2f> resultPoints = new ArrayList<>();
    for (int vertexInPolygonInd = 0; vertexInPolygonInd < nVerticesInPolygon; ++vertexInPolygonInd) {
        Vector3f vertex = mesh.vertices.get(mesh.polygons.get(polygonInd).getVertexIndices().get(vertexInPolygonInd));

        Vector3f vertexVecmath = new Vector3f(vertex.getX(), vertex.getY(), vertex.getZ());

        Vector2f resultPoint = vertexToPoint(multiplyMatrix4ByVector3(modelViewProjectionMatrix, vertexVecmath), width, height);
        resultPoints.add(resultPoint);
    }

    for (int vertexInPolygonInd = 1; vertexInPolygonInd < nVerticesInPolygon; ++vertexInPolygonInd) {
        graphicsContext.strokeLine(
                resultPoints.get(vertexInPolygonInd - 1).getX(),
                resultPoints.get(vertexInPolygonInd - 1).getY(),
                resultPoints.get(vertexInPolygonInd).getX(),
                resultPoints.get(vertexInPolygonInd).getY());
    }

    if (nVerticesInPolygon > 0)
        graphicsContext.strokeLine(
                resultPoints.get(nVerticesInPolygon - 1).getX(),
                resultPoints.get(nVerticesInPolygon - 1).getY(),
                resultPoints.get(0).getX(),
                resultPoints.get(0).getY());
}
}
} у меня камера перестала двигаться при нажатии на клавиши. нажатие на клавиши она видит
package com.cgvsu.render_engine;

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

public class GraphicConveyor {

public static Matrix4f rotateScaleTranslate() {
return new Matrix4f(1);
}

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

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

public static Matrix4f perspective(
final float fov,
final float aspectRatio,
final float nearPlane,
final float farPlane) {
float tangentMinusOnDegree = (float) (1.0F / (Math.tan(fov * 0.5F)));
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);
}

public static Vector3f multiplyMatrix4ByVector3(final Matrix4f matrix, final Vector3f vertex) {
final float x = (vertex.getX() * matrix.getElement(0, 0)) + (vertex.getY() * matrix.getElement(1, 0)) + (vertex.getZ() * matrix.getElement(2, 0)) + matrix.getElement(3, 0);
final float y = (vertex.getX() * matrix.getElement(0, 1)) + (vertex.getY() * matrix.getElement(1, 1)) + (vertex.getZ() * matrix.getElement(2, 1)) + matrix.getElement(3, 1);
final float z = (vertex.getX() * matrix.getElement(0, 2)) + (vertex.getY() * matrix.getElement(1, 2)) + (vertex.getZ() * matrix.getElement(2, 2)) + matrix.getElement(3, 2);
final float w = (vertex.getX() * matrix.getElement(0, 3)) + (vertex.getY() * matrix.getElement(1, 3)) + (vertex.getZ() * matrix.getElement(2, 3)) + matrix.getElement(3, 3);


return new Vector3f(x / w, y / w, z / w);
}

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

import com.cgvsu.render_engine.RenderEngine;
import javafx.fxml.FXML;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.scene.canvas.Canvas;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import javafx.stage.FileChooser;
import javafx.util.Duration;
import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.io.File;
import com.cgvsu.math.Vector3f;

import com.cgvsu.model.Model;
import com.cgvsu.objreader.ObjReader;
import com.cgvsu.render_engine.Camera;

public class GuiController {


final private float TRANSLATION = 0.5F;

@FXML
AnchorPane anchorPane;

@FXML
private Canvas canvas;

private Model mesh = null;

private Camera camera = new Camera(
        new Vector3f(0, 00, 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()));

    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.render(canvas.getGraphicsContext2D(), camera, mesh, (int) width, (int) height);
        }
    });

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

@FXML
private void onOpenModelMenuItemClick() {
    FileChooser fileChooser = new FileChooser();
    fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.obj)", "*.obj"));
    fileChooser.setTitle("Load Model");

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

    }
}

@FXML
public void handleCameraForward(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(0, 0, -TRANSLATION));
}

@FXML
public void handleCameraBackward(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(0, 0, TRANSLATION));
}

@FXML
public void handleCameraLeft(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(TRANSLATION, 0, 0));
}

@FXML
public void handleCameraRight(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(-TRANSLATION, 0, 0));
}

@FXML
public void handleCameraUp(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(0, TRANSLATION, 0));
}

@FXML
public void handleCameraDown(ActionEvent actionEvent) {
    camera.movePosition(new Vector3f(0, -TRANSLATION, 0));
}
}package com.cgvsu.math;

public class Matrix3f extends AbstractMatrix {
private static final int SIZE = 3;
private float[][] elements;


// Конструкторы + getters and setters
public Matrix3f(float[] elements) {
    // Надо ли обрабатывать и отслеживать ошибки, по типу: ввели матрицу не 3на 3.... пупупу... ну... это наш модуль, так что, дума.ю нет
    // надо у Борисова узнать
    if (elements.length != SIZE * SIZE) {
        throw new IllegalArgumentException("Массив должен содержать ровно " + (SIZE * SIZE) + " элементов.");
    }
    this.elements = new float[SIZE][SIZE];
    int k = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            this.elements[i][j] = elements[k];
        }
    }
}

public Matrix3f(int one) {
    //Опять вопрос к Борисову: проверять ли это num на какое-то значение, отличное от нуля, или нет... а, хотя, я наебу систему
    this.elements = new float[SIZE][SIZE];
    for (int i = 0; i < SIZE; i++) {
        this.elements[i][i] = 1;
    }
}

public Matrix3f() {
    this.elements = new float[SIZE][SIZE];
}

public Matrix3f(Matrix3f elements) {
    float[][] el = elements.getElements();
    if (el.length != SIZE && el[0].length != SIZE) {
        throw new IllegalArgumentException("Массив должен содержать ровно " + (SIZE * SIZE) + " элементов.");
    }
    this.elements = new float[SIZE][SIZE];
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            this.elements[i][j] = elements.getElement(i, j);
        }
    }
}

@Override
public Matrix3f add(AbstractMatrix other) {
    float[] a = new float[9];
    int k = 0;
    if (!isMatrix3x3(other)) {
        throw new IllegalArgumentException("Invalid matrix type for dot product");
    }
    Matrix3f o = (Matrix3f) other;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            a[k] = this.elements[i][j] + o.elements[i][j];
        }
    }
    return new Matrix3f(a);
}

@Override
public Matrix3f sub(AbstractMatrix other) {
    float[] a = new float[9];
    int k = 0;
    if (!isMatrix3x3(other)) {
        throw new IllegalArgumentException("Invalid matrix type for dot product");
    }
    Matrix3f o = (Matrix3f) other;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            a[k] = this.elements[i][j] - o.elements[i][j];
        }
    }
    return new Matrix3f(a);
}

// умножение матрицы на столбец - получаем столбец
@Override
public Vector3f multiply(AbstractVector other) {
    if (!isVector3f(other)) {
        throw new IllegalArgumentException("Invalid vector type for dot product");
    }
    Vector3f o = (Vector3f) other;
    Vector3f result = new Vector3f();
    for (int i = 0; i < SIZE; i++) {
        float res = 0;
        for (int j = 0; j < SIZE; j++) {
            res += (elements[i][j] * o.getNum(j));
        }
        result.setNum(i, res);
    }
    return result;
}

@Override
public void multiply(AbstractMatrix other) {
    if (!isMatrix3x3(other)) {
        throw new IllegalArgumentException("Invalid vector type for dot product");
    }
    Matrix3f o = (Matrix3f) other;
    Matrix3f result = new Matrix3f();
    for (int i = 0; i < SIZE; i++) {
        for (int k = 0; k < SIZE; k++) {
            float res = 0;
            for (int j = 0; j < SIZE; j++) {
                res += this.elements[i][j] * o.elements[j][k];
            }
            result.elements[i][k] = res;
        }
    }
    this.elements = result.elements;
}

// транспонирование матрицы
@Override
public void transposition() {
    for (int i = 0; i < SIZE; i++) {
        for (int j = i + 1; j < SIZE; j++) {
            float temp = elements[i][j];
            elements[i][j] = elements[j][i];
            elements[j][i] = temp;
        }
    }
}

// определитель
@Override
public float determinant() {
    return elements[0][0] * (elements[1][1] * elements[2][2] - elements[1][2] * elements[2][1])
            - elements[0][1] * (elements[1][0] * elements[2][2] - elements[1][2] * elements[2][0])
            + elements[0][2] * (elements[1][0] * elements[2][1] - elements[1][1] * elements[2][0]);
}

/**
 * Вычисление обратной матрицы 3x3
 *
 * @return Обратная матрица 3x3
 * @throws ArithmeticException если матрица вырождена и не имеет обратной
 */
@Override
public Matrix3f inverse() {
    float det = determinant();
    if (det == 0) {
        throw new ArithmeticException("Матрица вырождена и не имеет обратной.");
    }

    Matrix3f tilda = computeTildaMatrix();
    float[] inverseElements = new float[9];
    int k = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            inverseElements[k] = tilda.elements[i][j] / det;
        }
    }

    return new Matrix3f(inverseElements);
}

// Вычисление матрицы алгебраических дополнений для матрицы 3x3
private float[][] computeDopMatrix() {
    float[][] dop = new float[SIZE][SIZE];

    dop[0][0] = (elements[1][1] * elements[2][2] - elements[1][2] * elements[2][1]);
    dop[0][1] = -(elements[1][0] * elements[2][2] - elements[1][2] * elements[2][0]);
    dop[0][2] = (elements[1][0] * elements[2][1] - elements[1][1] * elements[2][0]);

    dop[1][0] = -(elements[0][1] * elements[2][2] - elements[0][2] * elements[2][1]);
    dop[1][1] = (elements[0][0] * elements[2][2] - elements[0][2] * elements[2][0]);
    dop[1][2] = -(elements[0][0] * elements[2][1] - elements[0][1] * elements[2][0]);

    dop[2][0] = (elements[0][1] * elements[1][2] - elements[0][2] * elements[1][1]);
    dop[2][1] = -(elements[0][0] * elements[1][2] - elements[0][2] * elements[1][0]);
    dop[2][2] = (elements[0][0] * elements[1][1] - elements[0][1] * elements[1][0]);

    return dop;
}


private Matrix3f computeTildaMatrix() {
    float[][] cofactors = computeDopMatrix();
    Matrix3f adjugate = new Matrix3f();
    adjugate.elements = cofactors;

    // Транспонирование матрицы кофакторов
    adjugate.transposition();

    return adjugate;
}

@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    for (float[] row : elements) {
        for (float elem : row) {
            sb.append(String.format("%.2f\t", elem));
        }
        sb.append("\n");
    }
    boolean a = false;
    return sb.toString();
}

private boolean isMatrix3x3(AbstractMatrix other) {
    return other instanceof Matrix3f;
}

private boolean isVector3f(AbstractVector other) {
    return other instanceof Vector3f;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Matrix3f)) return false;
    Matrix3f other = (Matrix3f) obj;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (Math.abs(this.elements[i][j] - other.elements[i][j]) > 1e-6) {
                return false;
            }
        }
    }
    return true;
}

@Override
public int hashCode() {
    int result = 1;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            result = 31 * result + Float.hashCode(elements[i][j]);
        }
    }
    return result;
}

// Геттер для элементов матрицы
public float[][] getElements() {
    return elements;
}

@Override
public float getElement(int rows, int col) {
    return elements[rows][col];
}

@Override
public void setElement(int rows, int col, float result) {
    elements[rows][col] = result;
}
}
package com.cgvsu.math;

public class Matrix4f extends AbstractMatrix {
private static final int SIZE = 4;
private float[][] elements;


// Конструкторы + getters and setters
public Matrix4f(float[] elements) {
    if (elements.length != SIZE * SIZE) {
        throw new IllegalArgumentException("Массив должен содержать ровно " + (SIZE * SIZE) + " элементов.");
    }
    this.elements = new float[SIZE][SIZE];
    int k = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            this.elements[i][j] = elements[k];
        }
    }
}

public Matrix4f(Matrix4f elements) {
    float[][] el = elements.getElements();
    if (el.length != SIZE && el[0].length != SIZE) {
        throw new IllegalArgumentException("Массив должен содержать ровно " + (SIZE * SIZE) + " элементов.");
    }
    this.elements = new float[SIZE][SIZE];

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            this.elements[i][j] = elements.getElement(i, j);
        }
    }
}

public Matrix4f(int one) {
    //Опять вопрос к Борисову: проверять ли это num на какое-то значение, отличное от нуля, или нет... а, хотя, я наебу систему
    this.elements = new float[SIZE][SIZE];
    for (int i = 0; i < SIZE; i++) {
        this.elements[i][i] = 1;
    }
}

public Matrix4f() {
    this.elements = new float[SIZE][SIZE];
}

@Override
public Matrix4f add(AbstractMatrix other) {
    float[] a = new float[16];
    int k = 0;
    if (!isMatrix4x4(other)) {
        throw new IllegalArgumentException("Invalid matrix type for dot product");
    }
    Matrix4f o = (Matrix4f) other;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            a[k] = this.elements[i][j] + o.elements[i][j];
        }
    }
    return new Matrix4f(a);
}

@Override
public Matrix4f sub(AbstractMatrix other) {
    float[] a = new float[16];
    int k = 0;
    if (!isMatrix4x4(other)) {
        throw new IllegalArgumentException("Invalid matrix type for dot product");
    }
    Matrix4f o = (Matrix4f) other;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            a[k] = this.elements[i][j] - o.elements[i][j];
        }
    }
    return new Matrix4f(a);
}

@Override
public Vector4f multiply(AbstractVector other) {
    if (!isVector4f(other)) {
        throw new IllegalArgumentException("Invalid vector type for dot product");
    }
    Vector4f o = (Vector4f) other;
    Vector4f result = new Vector4f();
    for (int i = 0; i < SIZE; i++) {
        float res = 0;
        for (int j = 0; j < SIZE; j++) {
            res += (elements[i][j] * o.getNum(j));
        }
        result.setNum(i, res);
    }
    return result;
}

@Override
public void multiply(AbstractMatrix other) {
    if (!isMatrix4x4(other)) {
        throw new IllegalArgumentException("Invalid vector type for dot product");
    }
    Matrix4f o = (Matrix4f) other;
    Matrix4f result = new Matrix4f();
    for (int i = 0; i < SIZE; i++) {
        for (int k = 0; k < SIZE; k++) {
            float res = 0;
            for (int j = 0; j < SIZE; j++) {
                res += this.elements[i][j] * o.elements[j][k];
            }
            result.elements[i][k] = res;
        }
    }
    this.elements = result.getElements();

}

@Override
public void transposition() {
    for (int i = 0; i < SIZE; i++) {
        for (int j = i + 1; j < SIZE; j++) {
            float temp = elements[i][j];
            elements[i][j] = elements[j][i];
            elements[j][i] = temp;
        }
    }
}

@Override
// Вычисление определителя матрицы 4x4
public float determinant() {
    float det = 0;
    for (int col = 0; col < SIZE; col++) {
        det += (col % 2 == 0 ? 1 : -1) * elements[0][col] * getMinorMatrix(0, col).determinant();
    }
    return det;
}

/**
 * Вычисление матрицы алгебраических дополнений для матрицы 4x4
 *
 * @return Матрица алгебраических дополнений 4x4
 */
private float[][] computeDopMatrix() {
    float[][] cofactors = new float[SIZE][SIZE];
    for (int row = 0; row < SIZE; row++) {
        for (int col = 0; col < SIZE; col++) {
            Matrix3f minorMatrix = getMinorMatrix(row, col);
            float minorDeterminant = minorMatrix.determinant();
            cofactors[row][col] = ((row + col) % 2 == 0 ? 1 : -1) * minorDeterminant;

        }
    }
    return cofactors;
}

/**
 * Вычисление матрицы тильда для матрицы 4x4
 *
 * @return Матрица присоединённых 4x4
 */
private Matrix4f computeTildaMatrix() {
    float[][] cofactors = computeDopMatrix();
    Matrix4f tilda = new Matrix4f();
    tilda.elements = cofactors;

    // Транспонирование матрицы кофакторов
    tilda.transposition();

    return tilda;
}

/**
 * Получение минорной матрицы 3x3, исключая указанную строку и столбец
 *
 * @param rowToExclude Строка для исключения
 * @param colToExclude Столбец для исключения
 * @return Минорная матрица 3x3
 */
private Matrix3f getMinorMatrix(int rowToExclude, int colToExclude) {
    float[] minorElements = new float[9];
    int index = 0;
    for (int i = 0; i < SIZE; i++) {
        if (i == rowToExclude) continue;
        for (int j = 0; j < SIZE; j++) {
            if (j == colToExclude) continue;
            minorElements[index++] = elements[i][j];
        }
    }
    return new Matrix3f(minorElements);
}


/**
 * Вычисление обратной матрицы 4x4
 *
 * @return Обратная матрица 4x4
 * @throws ArithmeticException если матрица вырождена и не имеет обратной
 */
@Override
public Matrix4f inverse() {
    float det = determinant();
    if (det == 0) {
        throw new ArithmeticException("Матрица вырождена и не имеет обратной.");
    }
    Matrix4f tilda = computeTildaMatrix();
    float[] inverseElements = new float[16];
    int k = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++, k++) {
            inverseElements[k] = tilda.elements[i][j] / det;
        }
    }
    return new Matrix4f(inverseElements);
}

@Override
public float getElement(int rows, int col) {
    return elements[rows][col];
}

@Override
public void setElement(int rows, int col, float result) {
    elements[rows][col] = result;
}

@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    for (float[] row : elements) {
        for (float elem : row) {
            sb.append(String.format("%.2f\t", elem));
        }
        sb.append("\n");
    }
    boolean a = false;
    return sb.toString();
}

private boolean isMatrix4x4(AbstractMatrix other) {
    return other instanceof Matrix4f;
}

private boolean isVector4f(AbstractVector other) {
    return other instanceof Vector4f;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Matrix4f)) return false;
    Matrix4f other = (Matrix4f) obj;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (Float.compare(this.elements[i][j], other.elements[i][j]) != 0) {
                return false;
            }
        }
    }
    return true;
}

@Override
public int hashCode() {
    int result = 1;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            result = 31 * result + Float.hashCode(elements[i][j]);
        }
    }
    return result;
}

// Геттер для элементов матрицы
public float[][] getElements() {
    return elements;
}
}

package com.cgvsu.math;

import java.util.Objects;

// Это заготовка для собственной библиотеки для работы с линейной алгеброй
public class Vector3f extends AbstractVector {
private float[] components = new float[SIZE];
private static final int SIZE = 3;


private float length;

// Конструкторы + getters and setters
public Vector3f(float x, float y, float z) {
    this.components[0] = x;
    this.components[1] = y;
    this.components[2] = z;
    calcLength();
}
public Vector3f() {
    this.components[0] = 0;
    this.components[1] = 0;
    this.components[2] = 0;
    calcLength();
}

public float getX() {
    return components[0];
}

public void setX(float x) {
    components[0] = x;
    calcLength();
}

public float getY() {
    return components[1];
}

public void setY(float y) {
    components[1] = y;
    calcLength();
}

public float getZ() {
    return components[2];
}

public void setZ(float z) {
    components[2] = z;
    calcLength();
}

/**
 * Возвращает компонент вектора по индексу.
 *
 * @param a Индекс компонента (0 для x, 1 для y, 2 для z).
 * @return Значение компонента.
 * @throws IndexOutOfBoundsException если индекс не равен 0, 1 или 2.
 */
public float getNum(int a) {
    if (a < 0 || a >= components.length) {
        throw new IndexOutOfBoundsException("Invalid index: " + a);
    }
    return components[a];
}

/**
 * Заменяет значения для x,y,z по индексу.
 *
 * @param a Индекс компонента (0 для x, 1 для y, 2 для z).
 * @param num Новое число для переменной
 * @throws IndexOutOfBoundsException если индекс не равен 0, 1 или 2.
 */
public void setNum(int a, float num) {
    if (a < 0 || a >= components.length) {
        throw new IndexOutOfBoundsException("Invalid index: " + a);
    }
    components[a] = num;
}

// Метод для вычисления длины вектора
private void calcLength() {
    length = (float) Math.sqrt(components[0] * components[0]
            + components[1] * components[1]
            + components[2] * components[2]);
}

@Override
public float length() {
    return length;
}

// Метод для нормализации вектора
private void calcNormalize() {
    calcLength();
    if (length == 0) {
        return;
    }
    divide(length);
}

@Override
public void normalize() {
    calcNormalize();
}

// Метод сложения векторов
@Override
public Vector3f add(AbstractVector other) {
    if (!isVector3f(other)) {
        throw new IllegalArgumentException("Неверный тип вектора для сложения");
    }
    Vector3f o = (Vector3f) other;
    return new Vector3f(
            this.components[0] + o.components[0],
            this.components[1] + o.components[1],
            this.components[2] + o.components[2]
    );
}

// Метод вычитания векторов
@Override
public Vector3f sub(AbstractVector other) {
    if (!isVector3f(other)) {
        throw new IllegalArgumentException("Неверный тип вектора для вычитания");
    }
    Vector3f o = (Vector3f) other;
    return new Vector3f(
            this.components[0] - o.components[0],
            this.components[1] - o.components[1],
            this.components[2] - o.components[2]
    );
}

@Override
public void sub(AbstractVector other, AbstractVector second) {
    if (!(isVector3f(other) && isVector3f(second))) {
        new Exception("не правильный тип векторов");
    }
    Vector3f o = (Vector3f) other;
    Vector3f s = (Vector3f) second;
    this.components[0] = o.getX() - s.getX();
    this.components[1] = o.getY() - s.getY();
    this.components[2] = o.getZ() - s.getZ();
}

// Метод умножения вектора на скаляр
@Override
public void multiply(float scalar) {
    for (int i = 0; i < components.length; i++) {
        this.components[i] *= scalar;
    }
    calcLength();
}

// Метод деления вектора на скаляр
@Override
public void divide(float scalar) {
    if (scalar == 0) throw new ArithmeticException("Деление на ноль");
    for (int i = 0; i < components.length; i++) {
        this.components[i] /= scalar;
    }
    calcLength();
}

// Метод скалярного произведения
@Override
public float dot(AbstractVector other) {
    if (!isVector3f(other)) {
        throw new IllegalArgumentException("Неверный тип вектора для скалярного произведения");
    }
    Vector3f o = (Vector3f) other;
    return this.components[0] * o.components[0]
            + this.components[1] * o.components[1]
            + this.components[2] * o.components[2];
}

// Метод векторного произведения
public Vector3f cross(Vector3f other) {
    float x = this.components[1] * other.components[2] - this.components[2] * other.components[1];
    float y = this.components[2] * other.components[0] - this.components[0] * other.components[2];
    float z = this.components[0] * other.components[1] - this.components[1] * other.components[0];
    return new Vector3f(x, y, z);
}

    // Проверка типа вектора
private boolean isVector3f(AbstractVector other) {
    return other instanceof Vector3f;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Vector3f)) return false;
    Vector3f other = (Vector3f) obj;
    return Float.compare(components[0], other.components[0]) == 0 &&
            Float.compare(components[1], other.components[1]) == 0 &&
            Float.compare(components[2], other.components[2]) == 0;
}

@Override
public int hashCode() {
    return Objects.hash(components[0], components[1], components[2]);
}

@Override
public String toString() {
    return "Vector3f{" +
            "x=" + components[0] +
            ", y=" + components[1] +
            ", z=" + components[2] +
            '}';
}
}
движение камеры осуществляется лишь формально. на экране ничего не меняяется. исправь