/*
 * 3D Engine 
 * clipping.c: 
 * Based on pikuma.com 3D software renderer in C
 * Copyright (c) 2021 986-Studio. All rights reserved.
 *
 * Created by Manoël Trapier on 11/03/2021.
 */

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>

#include <display.h>
#include <clipping.h>

static plane_t frustum[TOTAL_PLANES];

void initFrustumPlanes(double fov, double aspectRatio, double zNear, double zFar)
{
    double radFOV_Y = (fov * M_PI) / 180.;
    double radFOV_X = atan(tan(radFOV_Y/2)* aspectRatio) * 2;

    frustum[LEFT_PLANE].point = vec3(0, 0, 0);
    frustum[LEFT_PLANE].normal = vec3( cos(radFOV_X / 2.0) , 0, sin(radFOV_X / 2.0));

    frustum[RIGHT_PLANE].point = vec3(0, 0, 0);
    frustum[RIGHT_PLANE].normal = vec3( -cos(radFOV_X / 2.0), 0, sin(radFOV_X / 2.0));

    frustum[TOP_PLANE].point = vec3(0, 0, 0);
    frustum[TOP_PLANE].normal = vec3(0, -cos(radFOV_Y / 2.0), sin(radFOV_Y / 2.0));

    frustum[BOTTOM_PLANE].point = vec3(0, 0, 0);
    frustum[BOTTOM_PLANE].normal = vec3(0, cos(radFOV_Y / 2.0), sin(radFOV_Y / 2.0));

    frustum[NEAR_PLANE].point = vec3(0, 0, zNear);
    frustum[NEAR_PLANE].normal = vec3(0, 0, 1);

    frustum[FAR_PLANE].point = vec3(0, 0, zFar);
    frustum[FAR_PLANE].normal = vec3(0, 0, -1);
}

void createTriangleFromPolygon(polygon_t *polygon, triangle_t *triangleList, uint32_t *outNumber)
{
    int i;
    *outNumber = 0;
    for(i = 1; i < (polygon->num_vertices - 1); i++)
    {
        triangleList[i - 1].points[0] = vec4FromVec3(polygon->vertices[0]);
        triangleList[i - 1].points[1] = vec4FromVec3(polygon->vertices[i]);
        triangleList[i - 1].points[2] = vec4FromVec3(polygon->vertices[i + 1]);

        triangleList[i - 1].textureCoordinates[0] = polygon->textureCoords[0];
        triangleList[i - 1].textureCoordinates[1] = polygon->textureCoords[i];
        triangleList[i - 1].textureCoordinates[2] = polygon->textureCoords[i + 1];

        (*outNumber)++;
        assert(*outNumber < MAX_NUM_POLYGON_TRIANGLES);
    }
}

/* Linear interpolation */
static inline double floatLerp(double a, double b, double t)
{
    return a + t * (b - a);
}

static void clipPolygonAgainstPlane(polygon_t *poly, faceenum_t plane)
{
    int i;
    vec3_t planePoint = frustum[plane].point;
    vec3_t planeNormal = frustum[plane].normal;
    vec3_t *currentVertex, *previousVertex;
    tex2_t *currentTexCoord, *previousTexCoord;
    double currentDot, previousDot;

    vec3_t newVertices[MAX_NUM_POLYGON_VERTICES];
    tex2_t newTexCoords[MAX_NUM_POLYGON_VERTICES];
    int newVerticesCount = 0;

    previousVertex = &poly->vertices[poly->num_vertices - 1];
    previousTexCoord = &poly->textureCoords[poly->num_vertices - 1];

    for(i = 0; i < poly->num_vertices; i++)
    {
        currentVertex = &poly->vertices[i];
        currentTexCoord = &poly->textureCoords[i];

        currentDot = vec3Dot(vec3SubVectors(*currentVertex, planePoint), planeNormal);
        previousDot = vec3Dot(vec3SubVectors(*previousVertex, planePoint), planeNormal);

        if ((currentDot * previousDot) < 0.0)
        {
            double t = previousDot / (previousDot - currentDot);
            /* we crossed the plane */
            vec3_t intersectionVertex =
            {
                .x = floatLerp(previousVertex->x, currentVertex->x, t),
                .y = floatLerp(previousVertex->y, currentVertex->y, t),
                .z = floatLerp(previousVertex->z, currentVertex->z, t),
            };
            newVertices[newVerticesCount] = intersectionVertex;

            tex2_t intersectionTexCoord =
            {
                .u = floatLerp(previousTexCoord->u, currentTexCoord->u, t),
                .v = floatLerp(previousTexCoord->v, currentTexCoord->v, t),
            };
            newTexCoords[newVerticesCount] = intersectionTexCoord;

            newVerticesCount++;
        }

        if (currentDot > 0.0)
        {
            newVertices[newVerticesCount] = *currentVertex;
            newTexCoords[newVerticesCount] = *currentTexCoord;
            newVerticesCount++;
        }

        /* Just to be sure, should never trigger  */
        assert(newVerticesCount < MAX_NUM_POLYGON_VERTICES);

        previousVertex = currentVertex;
        previousTexCoord = currentTexCoord;
    }

    for(i = 0; i < newVerticesCount; i++)
    {
        poly->vertices[i] = newVertices[i];
        poly->textureCoords[i] = newTexCoords[i];
    }
    poly->num_vertices = newVerticesCount;
}

void clipPolygon(polygon_t *poly)
{
    clipPolygonAgainstPlane(poly, LEFT_PLANE);
    clipPolygonAgainstPlane(poly, TOP_PLANE);
    clipPolygonAgainstPlane(poly, RIGHT_PLANE);
    clipPolygonAgainstPlane(poly, BOTTOM_PLANE);
    clipPolygonAgainstPlane(poly, NEAR_PLANE);
    clipPolygonAgainstPlane(poly, FAR_PLANE);
}