#pragma once
#include<cmath>
#include<cstring>
#include<iostream>

struct arr {
    float list[16];
};

const float PI = atan(1) * 4;

class MT3D
{
public:
	float matrix[4][4] = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
	float camera[4][4] = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };

    arr toList() {
        arr polje; 
        int k = 0;
        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++) {
                polje.list[k++] = matrix[j][i];
            }
        return polje;
    }

    void mult(float m[4][4]) {
        float m1[4][4] = { {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0} };
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 4; k++) {
                    m1[i][j] += matrix[i][k] * m[k][j];
                }
            }
        }
        memcpy(matrix, m1, 16 * sizeof(float));
    }

    void identity() {
        float m1[4][4] = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
        memcpy(matrix, m1, 16 * sizeof(float));
    }

    float* VP(float u[3], float v[3])
    {
        float vek[3] = { 0, 0, 0 };
        vek[0] = u[1] * v[2] - u[2] * v[1];
        vek[1] = u[2] * v[0] - u[0] * v[2];
        vek[2] = u[0] * v[1] - u[1] * v[0];
        return vek;
    }

    void setCamera(float x0, float y0, float z0, float x1, float y1, float z1, float Vx, float Vy, float Vz)
    {
        float N[3] = { x0 - x1, y0 - y1, z0 - z1 };
        float dN = sqrt(N[0] * N[0] + N[1] * N[1] + N[2] * N[2]);

        float xN = N[0] / dN;
        float yN = N[1] / dN;
        float zN = N[2] / dN;

        float uVP[3] = { Vx, Vy, Vz };
        float vVP[3] = { xN, yN, zN };
        float *uVektor = VP(uVP, vVP);
        float dU = sqrt(uVektor[0] * uVektor[0] + uVektor[1] * uVektor[1] + uVektor[2] * uVektor[2]);

        float xU = uVektor[0] / dU;
        float yU = uVektor[1] / dU;
        float zU = uVektor[2] / dU;

        float uVP1[3] = { xN, yN, zN };
        float vVP1[3] = { xU, yU, zU };
        float *v = VP(uVP1, vVP1);

        float k1[4][4] = {
                {xU, yU, zU, -xU * x0 - yU * y0 - zU * z0},
                {v[0], v[1], v[2], -v[0] * x0 - v[1] * y0 - v[2] * z0},
                {xN, yN, zN, -xN * x0 - yN * y0 - zN * z0},
                {0, 0, 0, 1}
        };
        memcpy(camera, k1, 16 * sizeof(float));

        mult(camera);
    }

    void persp(float xmin, float xmax, float ymin, float ymax, float zpr, float zst) {
        float sx = (2 * zpr) / (xmax - xmin);
        float sy = (2 * zpr) / (ymax - ymin);
        float sz = (zpr + zst) / (zpr - zst);
        float px = (xmax + xmin) / (xmax - xmin);
        float py = (ymax + ymin) / (ymax - ymin);
        float pz = (2 * zpr * zst) / (zpr - zst);
        float m[4][4] = {
                {sx, 0, px, 0},
                {0, sy, py, 0},
                {0, 0, sz, pz},
                {0, 0, -1, 0} 
        };

        mult(m);
    }

    void translate(float px, float py, float pz) {
        float m1[4][4] = { {1.0,0.0,0.0,px}, {0.0,1.0,0.0,py}, {0.0,0.0,1.0,pz}, {0.0,0.0,0.0,1.0} };
        mult(m1);
    }

    void scale(float sx, float sy, float sz) {
        float m1[4][4] = { {sx,0.0,0.0,0.0}, {0.0,sy,0.0,0.0}, {0.0,0.0,sz,0.0}, {0.0,0.0,0.0,1.0} };
        mult(m1);
    }

    float degToRad(float deg) {
        return deg * PI / 180;
    }

    void rotateX(float deg) {
        float rad = degToRad(deg);
        float m1[4][4] = { {1.0,0.0,0.0,0.0}, {0.0,cos(rad),-sin(rad),0.0}, {0.0,sin(rad),cos(rad),0.0}, {0.0,0.0,0.0,1.0}};
        mult(m1);
    }

    void rotateY(float deg) {
        float rad = degToRad(deg);
        float m1[4][4] = { {cos(rad),0.0,sin(rad),0.0}, {0.0,1.0,0,0.0}, {-sin(rad),0.0,cos(rad),0.0}, {0.0,0.0,0.0,1.0}};
        mult(m1);
    }

    void rotateZ(float deg) {
        float rad = degToRad(deg);
        float m1[4][4] = { {cos(rad),-sin(rad),0.0,0.0}, {sin(rad),cos(rad),0.0,0.0}, {0.0,0.0,1.0,0.0}, {0.0,0.0,0.0,1.0}};
        mult(m1);
    }
};

