/*
 Author: Yao Feng
 Modified by Jianzhu Guo

 functions that can not be optimazed by vertorization in python.
 1. rasterization.(need process each triangle)
 2. normal of each vertex.(use one-ring, need process each vertex)
 3. write obj(seems that it can be verctorized? anyway, writing it in c++ is simple, so also add function here. --> however, why writting in c++ is still slow?)



*/

#include "rasterize.h"


/* Judge whether the Point is in the triangle
Method:
    http://blackpawn.com/texts/pointinpoly/
Args:
    Point: [x, y]
    tri_points: three vertices(2d points) of a triangle. 2 coords x 3 vertices
Returns:
    bool: true for in triangle
*/
bool is_point_in_tri(Point p, Point p0, Point p1, Point p2) {
    // vectors
    Point v0, v1, v2;
    v0 = p2 - p0;
    v1 = p1 - p0;
    v2 = p - p0;

    // dot products
    float dot00 = v0.dot(v0); //v0.x * v0.x + v0.y * v0.y //np.dot(v0.T, v0)
    float dot01 = v0.dot(v1); //v0.x * v1.x + v0.y * v1.y //np.dot(v0.T, v1)
    float dot02 = v0.dot(v2); //v0.x * v2.x + v0.y * v2.y //np.dot(v0.T, v2)
    float dot11 = v1.dot(v1); //v1.x * v1.x + v1.y * v1.y //np.dot(v1.T, v1)
    float dot12 = v1.dot(v2); //v1.x * v2.x + v1.y * v2.y//np.dot(v1.T, v2)

    // barycentric coordinates
    float inverDeno;
    if (dot00 * dot11 - dot01 * dot01 == 0)
        inverDeno = 0;
    else
        inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);

    float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
    float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;

    // check if Point in triangle
    return (u >= 0) && (v >= 0) && (u + v < 1);
}

void get_point_weight(float *weight, Point p, Point p0, Point p1, Point p2) {
    // vectors
    Point v0, v1, v2;
    v0 = p2 - p0;
    v1 = p1 - p0;
    v2 = p - p0;

    // dot products
    float dot00 = v0.dot(v0); //v0.x * v0.x + v0.y * v0.y //np.dot(v0.T, v0)
    float dot01 = v0.dot(v1); //v0.x * v1.x + v0.y * v1.y //np.dot(v0.T, v1)
    float dot02 = v0.dot(v2); //v0.x * v2.x + v0.y * v2.y //np.dot(v0.T, v2)
    float dot11 = v1.dot(v1); //v1.x * v1.x + v1.y * v1.y //np.dot(v1.T, v1)
    float dot12 = v1.dot(v2); //v1.x * v2.x + v1.y * v2.y//np.dot(v1.T, v2)

    // barycentric coordinates
    float inverDeno;
    if (dot00 * dot11 - dot01 * dot01 == 0)
        inverDeno = 0;
    else
        inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);

    float u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
    float v = (dot00 * dot12 - dot01 * dot02) * inverDeno;

    // weight
    weight[0] = 1 - u - v;
    weight[1] = v;
    weight[2] = u;
}

/*
 * Get normals of triangles.
 */
void _get_tri_normal(float *tri_normal, float *vertices, int *triangles, int ntri, bool norm_flg) {
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;
    float v1x, v1y, v1z, v2x, v2y, v2z;

    for (int i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        // counter clockwise order
        v1x = vertices[3 * tri_p1_ind] - vertices[3 * tri_p0_ind];
        v1y = vertices[3 * tri_p1_ind + 1] - vertices[3 * tri_p0_ind + 1];
        v1z = vertices[3 * tri_p1_ind + 2] - vertices[3 * tri_p0_ind + 2];

        v2x = vertices[3 * tri_p2_ind] - vertices[3 * tri_p0_ind];
        v2y = vertices[3 * tri_p2_ind + 1] - vertices[3 * tri_p0_ind + 1];
        v2z = vertices[3 * tri_p2_ind + 2] - vertices[3 * tri_p0_ind + 2];

        if (norm_flg) {
            float c1 = v1y * v2z - v1z * v2y;
            float c2 = v1z * v2x - v1x * v2z;
            float c3 = v1x * v2y - v1y * v2x;
            float det = sqrt(c1 * c1 + c2 * c2 + c3 * c3);
            if (det <= 0) det = 1e-6;
            tri_normal[3 * i] = c1 / det;
            tri_normal[3 * i + 1] = c2 / det;
            tri_normal[3 * i + 2] = c3 / det;
        } else {
            tri_normal[3 * i] = v1y * v2z - v1z * v2y;
            tri_normal[3 * i + 1] = v1z * v2x - v1x * v2z;
            tri_normal[3 * i + 2] = v1x * v2y - v1y * v2x;
        }
    }
}

/*
 * Get normal vector of vertices using triangle normals
 */
void _get_ver_normal(float *ver_normal, float *tri_normal, int *triangles, int nver, int ntri) {
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;

    for (int i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        for (int j = 0; j < 3; j++) {
            ver_normal[3 * tri_p0_ind + j] += tri_normal[3 * i + j];
            ver_normal[3 * tri_p1_ind + j] += tri_normal[3 * i + j];
            ver_normal[3 * tri_p2_ind + j] += tri_normal[3 * i + j];
        }
    }

    // normalizing
    float nx, ny, nz, det;
    for (int i = 0; i < nver; ++i) {
        nx = ver_normal[3 * i];
        ny = ver_normal[3 * i + 1];
        nz = ver_normal[3 * i + 2];

        det = sqrt(nx * nx + ny * ny + nz * nz);
        if (det <= 0) det = 1e-6;
        ver_normal[3 * i] = nx / det;
        ver_normal[3 * i + 1] = ny / det;
        ver_normal[3 * i + 2] = nz / det;
    }
}

/*
 * Directly get normal of vertices, which can be regraded as a combination of _get_tri_normal and _get_ver_normal
 */
void _get_normal(float *ver_normal, float *vertices, int *triangles, int nver, int ntri) {
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;
    float v1x, v1y, v1z, v2x, v2y, v2z;

    // get tri_normal
//    float tri_normal[3 * ntri];
    float *tri_normal;
    tri_normal = new float[3 * ntri];
    for (int i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        // counter clockwise order
        v1x = vertices[3 * tri_p1_ind] - vertices[3 * tri_p0_ind];
        v1y = vertices[3 * tri_p1_ind + 1] - vertices[3 * tri_p0_ind + 1];
        v1z = vertices[3 * tri_p1_ind + 2] - vertices[3 * tri_p0_ind + 2];

        v2x = vertices[3 * tri_p2_ind] - vertices[3 * tri_p0_ind];
        v2y = vertices[3 * tri_p2_ind + 1] - vertices[3 * tri_p0_ind + 1];
        v2z = vertices[3 * tri_p2_ind + 2] - vertices[3 * tri_p0_ind + 2];


        tri_normal[3 * i] = v1y * v2z - v1z * v2y;
        tri_normal[3 * i + 1] = v1z * v2x - v1x * v2z;
        tri_normal[3 * i + 2] = v1x * v2y - v1y * v2x;

    }

    // get ver_normal
    for (int i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        for (int j = 0; j < 3; j++) {
            ver_normal[3 * tri_p0_ind + j] += tri_normal[3 * i + j];
            ver_normal[3 * tri_p1_ind + j] += tri_normal[3 * i + j];
            ver_normal[3 * tri_p2_ind + j] += tri_normal[3 * i + j];
        }
    }

    // normalizing
    float nx, ny, nz, det;
    for (int i = 0; i < nver; ++i) {
        nx = ver_normal[3 * i];
        ny = ver_normal[3 * i + 1];
        nz = ver_normal[3 * i + 2];

        det = sqrt(nx * nx + ny * ny + nz * nz);
        if (det <= 0) det = 1e-6;
        ver_normal[3 * i] = nx / det;
        ver_normal[3 * i + 1] = ny / det;
        ver_normal[3 * i + 2] = nz / det;
    }

    delete[] tri_normal;
}

// rasterization by Z-Buffer with optimization
// Complexity: < ntri * h * w * c
void _rasterize(
        unsigned char *image, float *vertices, int *triangles, float *colors, float *depth_buffer,
        int ntri, int h, int w, int c, float alpha, bool reverse) {
    int x, y, k;
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;
    Point p0, p1, p2, p;
    int x_min, x_max, y_min, y_max;
    float p_depth, p0_depth, p1_depth, p2_depth;
    float p_color, p0_color, p1_color, p2_color;
    float weight[3];

    for (int i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        p0.x = vertices[3 * tri_p0_ind];
        p0.y = vertices[3 * tri_p0_ind + 1];
        p0_depth = vertices[3 * tri_p0_ind + 2];
        p1.x = vertices[3 * tri_p1_ind];
        p1.y = vertices[3 * tri_p1_ind + 1];
        p1_depth = vertices[3 * tri_p1_ind + 2];
        p2.x = vertices[3 * tri_p2_ind];
        p2.y = vertices[3 * tri_p2_ind + 1];
        p2_depth = vertices[3 * tri_p2_ind + 2];

        x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
        x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);

        y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
        y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);

        if (x_max < x_min || y_max < y_min) {
            continue;
        }

        for (y = y_min; y <= y_max; y++) {
            for (x = x_min; x <= x_max; x++) {
                p.x = float(x);
                p.y = float(y);

                // call get_point_weight function once
                get_point_weight(weight, p, p0, p1, p2);

                // and judge is_point_in_tri by below line of code
                if (weight[2] >= 0 && weight[1] >= 0 && weight[0] > 0) {
                    get_point_weight(weight, p, p0, p1, p2);
                    p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;

                    if ((p_depth > depth_buffer[y * w + x])) {
                        for (k = 0; k < c; k++) {
                            p0_color = colors[c * tri_p0_ind + k];
                            p1_color = colors[c * tri_p1_ind + k];
                            p2_color = colors[c * tri_p2_ind + k];

                            p_color = weight[0] * p0_color + weight[1] * p1_color + weight[2] * p2_color;
                            if (reverse) {
                                image[(h - 1 - y) * w * c + x * c + k] = (unsigned char) (
                                        (1 - alpha) * image[(h - 1 - y) * w * c + x * c + k] + alpha * 255 * p_color);
//                                image[(h - 1 - y) * w * c + x * c + k] = (unsigned char) (255 * p_color);
                            } else {
                                image[y * w * c + x * c + k] = (unsigned char) (
                                        (1 - alpha) * image[y * w * c + x * c + k] + alpha * 255 * p_color);
//                                image[y * w * c + x * c + k] = (unsigned char) (255 * p_color);
                            }
                        }

                        depth_buffer[y * w + x] = p_depth;
                    }
                }
            }
        }
    }
}


void _rasterize_triangles(
        float *vertices, int *triangles, float *depth_buffer, int *triangle_buffer, float *barycentric_weight,
        int ntri, int h, int w) {
    int i;
    int x, y, k;
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;
    Point p0, p1, p2, p;
    int x_min, x_max, y_min, y_max;
    float p_depth, p0_depth, p1_depth, p2_depth;
    float weight[3];

    for (i = 0; i < ntri; i++) {
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        p0.x = vertices[3 * tri_p0_ind];
        p0.y = vertices[3 * tri_p0_ind + 1];
        p0_depth = vertices[3 * tri_p0_ind + 2];
        p1.x = vertices[3 * tri_p1_ind];
        p1.y = vertices[3 * tri_p1_ind + 1];
        p1_depth = vertices[3 * tri_p1_ind + 2];
        p2.x = vertices[3 * tri_p2_ind];
        p2.y = vertices[3 * tri_p2_ind + 1];
        p2_depth = vertices[3 * tri_p2_ind + 2];

        x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
        x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);

        y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
        y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);

        if (x_max < x_min || y_max < y_min) {
            continue;
        }

        for (y = y_min; y <= y_max; y++) //h
        {
            for (x = x_min; x <= x_max; x++) //w
            {
                p.x = x;
                p.y = y;
//                if (p.x < 2 || p.x > w - 3 || p.y < 2 || p.y > h - 3 || is_point_in_tri(p, p0, p1, p2)) {
                if (is_point_in_tri(p, p0, p1, p2)) {
                    get_point_weight(weight, p, p0, p1, p2);
                    p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;

                    if ((p_depth > depth_buffer[y * w + x])) {
                        depth_buffer[y * w + x] = p_depth;
                        triangle_buffer[y * w + x] = i;
                        for (k = 0; k < 3; k++) {
                            barycentric_weight[y * w * 3 + x * 3 + k] = weight[k];
                        }
                    }
                }
            }
        }
    }
}


// Depth-Buffer 算法
// https://blog.csdn.net/Jurbo/article/details/75007260
void _render_texture_core(
        float *image, float *vertices, int *triangles,
        float *texture, float *tex_coords, int *tex_triangles,
        float *depth_buffer,
        int nver, int tex_nver, int ntri,
        int h, int w, int c,
        int tex_h, int tex_w, int tex_c,
        int mapping_type) {
    int i;
    int x, y, k;
    int tri_p0_ind, tri_p1_ind, tri_p2_ind;
    int tex_tri_p0_ind, tex_tri_p1_ind, tex_tri_p2_ind;
    Point p0, p1, p2, p;
    Point tex_p0, tex_p1, tex_p2, tex_p;
    int x_min, x_max, y_min, y_max;
    float weight[3];
    float p_depth, p0_depth, p1_depth, p2_depth;
    float xd, yd;
    float ul, ur, dl, dr;
    for (i = 0; i < ntri; i++) {
        // mesh
        tri_p0_ind = triangles[3 * i];
        tri_p1_ind = triangles[3 * i + 1];
        tri_p2_ind = triangles[3 * i + 2];

        p0.x = vertices[3 * tri_p0_ind];
        p0.y = vertices[3 * tri_p0_ind + 1];
        p0_depth = vertices[3 * tri_p0_ind + 2];
        p1.x = vertices[3 * tri_p1_ind];
        p1.y = vertices[3 * tri_p1_ind + 1];
        p1_depth = vertices[3 * tri_p1_ind + 2];
        p2.x = vertices[3 * tri_p2_ind];
        p2.y = vertices[3 * tri_p2_ind + 1];
        p2_depth = vertices[3 * tri_p2_ind + 2];

        // texture
        tex_tri_p0_ind = tex_triangles[3 * i];
        tex_tri_p1_ind = tex_triangles[3 * i + 1];
        tex_tri_p2_ind = tex_triangles[3 * i + 2];

        tex_p0.x = tex_coords[3 * tex_tri_p0_ind];
        tex_p0.y = tex_coords[3 * tri_p0_ind + 1];
        tex_p1.x = tex_coords[3 * tex_tri_p1_ind];
        tex_p1.y = tex_coords[3 * tri_p1_ind + 1];
        tex_p2.x = tex_coords[3 * tex_tri_p2_ind];
        tex_p2.y = tex_coords[3 * tri_p2_ind + 1];


        x_min = max((int) ceil(min(p0.x, min(p1.x, p2.x))), 0);
        x_max = min((int) floor(max(p0.x, max(p1.x, p2.x))), w - 1);

        y_min = max((int) ceil(min(p0.y, min(p1.y, p2.y))), 0);
        y_max = min((int) floor(max(p0.y, max(p1.y, p2.y))), h - 1);


        if (x_max < x_min || y_max < y_min) {
            continue;
        }

        for (y = y_min; y <= y_max; y++) //h
        {
            for (x = x_min; x <= x_max; x++) //w
            {
                p.x = x;
                p.y = y;
                if (p.x < 2 || p.x > w - 3 || p.y < 2 || p.y > h - 3 || is_point_in_tri(p, p0, p1, p2)) {
                    get_point_weight(weight, p, p0, p1, p2);
                    p_depth = weight[0] * p0_depth + weight[1] * p1_depth + weight[2] * p2_depth;

                    if ((p_depth > depth_buffer[y * w + x])) {
                        // -- color from texture
                        // cal weight in mesh tri
                        get_point_weight(weight, p, p0, p1, p2);
                        // cal coord in texture
                        tex_p = tex_p0 * weight[0] + tex_p1 * weight[1] + tex_p2 * weight[2];
                        tex_p.x = max(min(tex_p.x, float(tex_w - 1)), float(0));
                        tex_p.y = max(min(tex_p.y, float(tex_h - 1)), float(0));

                        yd = tex_p.y - floor(tex_p.y);
                        xd = tex_p.x - floor(tex_p.x);
                        for (k = 0; k < c; k++) {
                            if (mapping_type == 0)// nearest
                            {
                                image[y * w * c + x * c + k] = texture[int(round(tex_p.y)) * tex_w * tex_c +
                                                                       int(round(tex_p.x)) * tex_c + k];
                            } else//bilinear interp
                            {
                                ul = texture[(int) floor(tex_p.y) * tex_w * tex_c + (int) floor(tex_p.x) * tex_c + k];
                                ur = texture[(int) floor(tex_p.y) * tex_w * tex_c + (int) ceil(tex_p.x) * tex_c + k];
                                dl = texture[(int) ceil(tex_p.y) * tex_w * tex_c + (int) floor(tex_p.x) * tex_c + k];
                                dr = texture[(int) ceil(tex_p.y) * tex_w * tex_c + (int) ceil(tex_p.x) * tex_c + k];

                                image[y * w * c + x * c + k] =
                                        ul * (1 - xd) * (1 - yd) + ur * xd * (1 - yd) + dl * (1 - xd) * yd +
                                        dr * xd * yd;
                            }

                        }

                        depth_buffer[y * w + x] = p_depth;
                    }
                }
            }
        }
    }
}


// ------------------------------------------------- write
// obj write
// Ref: https://github.com/patrikhuber/eos/blob/master/include/eos/core/Mesh.hpp
void _write_obj_with_colors_texture(string filename, string mtl_name,
                                    float *vertices, int *triangles, float *colors, float *uv_coords,
                                    int nver, int ntri, int ntexver) {
    int i;

    ofstream obj_file(filename);

    // first line of the obj file: the mtl name
    obj_file << "mtllib " << mtl_name << endl;

    // write vertices
    for (i = 0; i < nver; ++i) {
        obj_file << "v " << vertices[3 * i] << " " << vertices[3 * i + 1] << " " << vertices[3 * i + 2] << colors[3 * i]
                 << " " << colors[3 * i + 1] << " " << colors[3 * i + 2] << endl;
    }

    // write uv coordinates
    for (i = 0; i < ntexver; ++i) {
        //obj_file << "vt " << uv_coords[2*i] << " " << (1 - uv_coords[2*i + 1]) << endl;
        obj_file << "vt " << uv_coords[2 * i] << " " << uv_coords[2 * i + 1] << endl;
    }

    obj_file << "usemtl FaceTexture" << endl;
    // write triangles
    for (i = 0; i < ntri; ++i) {
        // obj_file << "f " << triangles[3*i] << "/" << triangles[3*i] << " " << triangles[3*i + 1] << "/" << triangles[3*i + 1] << " " << triangles[3*i + 2] << "/" << triangles[3*i + 2] << endl;
        obj_file << "f " << triangles[3 * i + 2] << "/" << triangles[3 * i + 2] << " " << triangles[3 * i + 1] << "/"
                 << triangles[3 * i + 1] << " " << triangles[3 * i] << "/" << triangles[3 * i] << endl;
    }

}