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

#include <stdint.h>
#include <stdbool.h>

#include <3dengine.h>
#include <display.h>
#include <triangle.h>
#include <math.h>

void drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, colour_t colour)
{
    drawLine(x0, y0, x1, y1, colour);
    drawLine(x1, y1, x2, y2, colour);
    drawLine(x2, y2, x0, y0, colour);
}

/* ----------------------------------- Filled triangles ----------------------------------- */
void drawFilledTriangle(struct triangle_t *t)
{
    int32_t x, y;

    if (t->points[0].y > t->points[1].y)
    {
        doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y);
        doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w);
    }
    if (t->points[1].y > t->points[2].y)
    {
        doubleSwap(&t->points[1].x, &t->points[2].x); doubleSwap(&t->points[1].y, &t->points[2].y);
        doubleSwap(&t->points[1].z, &t->points[2].z); doubleSwap(&t->points[1].w, &t->points[2].w);
    }
    if (t->points[0].y > t->points[1].y)
    {
        doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y);
        doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w);
    }

    vec4_t a = t->points[0];
    vec4_t b = t->points[1];
    vec4_t c = t->points[2];

    /* Render the top part */
    double inverseSlope1 = 0;
    double inverseSlope2 = 0;
    int32_t x0 = a.x, y0 = a.y;
    int32_t x1 = b.x, y1 = b.y;
    int32_t x2 = c.x, y2 = c.y;

    if ((y1 - y0) != 0) { inverseSlope1 = (double)(x1 - x0) / abs(y1 - y0); }
    if ((y2 - y0) != 0) { inverseSlope2 = (double)(x2 - x0) / abs(y2 - y0); }

    if ((y1 - y0) != 0)
    {
        for (y = y0 ; y <= y1 ; y++)
        {
            int32_t xStart = x1 + (y - y1) * inverseSlope1;
            int32_t xEnd = x0 + (y - y0) * inverseSlope2;

            if (doZBuffer)
            {
                if (xEnd < xStart)
                {
                    intSwap(&xStart, &xEnd);
                }
                for (x = xStart ; x <= xEnd ; x++)
                {
                    drawZPixel(x, y, a, b, c,t->colour);
                }
            }
            else
            {
                drawHLine(xStart, y, xEnd, t->colour);
            }
        }
    }

    /* Render the bottom part */
    inverseSlope1 = 0;
    if ((y2 - y1) != 0) { inverseSlope1 = (double)(x2 - x1) / abs(y2 - y1); }

    if ((y2 - y1) != 0)
    {
        for (y = y1 ; y <= y2 ; y++)
        {
            int32_t xStart = x2 + (y - y2) * inverseSlope1;
            int32_t xEnd = x0 + (y - y0) * inverseSlope2;

            if (doZBuffer)
            {
                if (xEnd < xStart)
                {
                    intSwap(&xStart, &xEnd);
                }
                for (x = xStart ; x <= xEnd ; x++)
                {
                    drawZPixel(x, y, a, b, c,t->colour);
                }
            }
            else
            {
                drawHLine(xStart, y, xEnd, t->colour);
            }
        }
    }

}

/* ----------------------------------- Textured triangles ----------------------------------- */
void drawTextureTriangle(struct triangle_t *t)
{
    int32_t x, y;

    if (t->points[0].y > t->points[1].y)
    {
        doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y);
        doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w);
        doubleSwap(&t->textureCoordinates[0].u, &t->textureCoordinates[1].u);
        doubleSwap(&t->textureCoordinates[0].v, &t->textureCoordinates[1].v);
    }
    if (t->points[1].y > t->points[2].y)
    {
        doubleSwap(&t->points[1].x, &t->points[2].x); doubleSwap(&t->points[1].y, &t->points[2].y);
        doubleSwap(&t->points[1].z, &t->points[2].z); doubleSwap(&t->points[1].w, &t->points[2].w);
        doubleSwap(&t->textureCoordinates[1].u, &t->textureCoordinates[2].u);
        doubleSwap(&t->textureCoordinates[1].v, &t->textureCoordinates[2].v);
    }
    if (t->points[0].y > t->points[1].y)
    {
        doubleSwap(&t->points[0].x, &t->points[1].x); doubleSwap(&t->points[0].y, &t->points[1].y);
        doubleSwap(&t->points[0].z, &t->points[1].z); doubleSwap(&t->points[0].w, &t->points[1].w);
        doubleSwap(&t->textureCoordinates[0].u, &t->textureCoordinates[1].u);
        doubleSwap(&t->textureCoordinates[0].v, &t->textureCoordinates[1].v);
    }

    vec4_t a = t->points[0];
    vec4_t b = t->points[1];
    vec4_t c = t->points[2];

    /* Render the top part */
    double inverseSlope1 = 0;
    double inverseSlope2 = 0;
    int32_t x0 = a.x, y0 = a.y;
    int32_t x1 = b.x, y1 = b.y;
    int32_t x2 = c.x, y2 = c.y;

    if ((y1 - y0) != 0) { inverseSlope1 = (double)(x1 - x0) / abs(y1 - y0); }
    if ((y2 - y0) != 0) { inverseSlope2 = (double)(x2 - x0) / abs(y2 - y0); }

    if ((y1 - y0) != 0)
    {
        for (y = y0 ; y <= y1 ; y++)
        {
            int32_t xStart = x1 + (y - y1) * inverseSlope1;
            int32_t xEnd = x0 + (y - y0) * inverseSlope2;

            if (xEnd < xStart)
            {
                intSwap(&xStart, &xEnd);
            }

            for (x = xStart ; x <= xEnd ; x++)
            {
                drawTexel(x, y, a, b, c,
                          t->textureCoordinates[0],
                          t->textureCoordinates[1],
                          t->textureCoordinates[2],
                          t->texture);
            }
        }
    }

    /* Render the bottom part */
    inverseSlope1 = 0;
    if ((y2 - y1) != 0) { inverseSlope1 = (double)(x2 - x1) / abs(y2 - y1); }

    if ((y2 - y1) != 0)
    {
        for (y = y1 ; y <= y2 ; y++)
        {
            int32_t xStart = x2 + (y - y2) * inverseSlope1;
            int32_t xEnd = x0 + (y - y0) * inverseSlope2;

            if (xEnd < xStart)
            {
                intSwap(&xStart, &xEnd);
            }

            for (x = xStart ; x <= xEnd ; x++)
            {
                drawTexel(x, y, a, b, c,
                          t->textureCoordinates[0],
                          t->textureCoordinates[1],
                          t->textureCoordinates[2],
                          t->texture);
            }
        }
    }

}

/* ---- Utility ---- */

int compareTrianglesZOrder(const void *p1, const void *p2)
{
    triangle_t *t1 = (struct triangle_t *)p1;
    triangle_t *t2 = (struct triangle_t *)p2;

    if (t1->averageDepth > t2->averageDepth)
    {
        return -1;
    }
    else if (t1->averageDepth < t2->averageDepth)
    {
        return  1;
    }

    return 0;
}