
#include "despair.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "vga.h"

#include "base.h"
#include "llkey.h"
#include "llscreen.h"


typedef struct {
    int   NumObject;
    float T;
} TData, *PData;

PData DataBuf;


#define VEC_Dot(a, b) ((a)[0] * (b)[0] + (a)[1] * (b)[1] + (a)[2] * (b)[2])

extern void AADump(void *);
//#pragma aux AADump parm [ESI]
void DrawScene(void);

typedef float REAL;

float g_t1, g_t2;
bool t1valid, t2valid;

void VEC_Norm_f(float *vec)
{
    float mod;

    mod = vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2];
    mod = (float)sqrt(mod);

    vec[0] /= mod;
    vec[1] /= mod;
    vec[2] /= mod;
}
#define INF 1E35

typedef void (*IntFunc)(float *ray, struct SSphere *sphere);

typedef struct SSphere
{
    union {
        struct {
            REAL center[3];
            REAL r, r2;
            REAL c2_r2;
        };
        struct {
            REAL a, b, c, d;
        };
    };
    IntFunc f;
    bool neg;
    dword magicnumba;
} TSphere;

void IntSphere(float *ray, TSphere *psphere) {
    REAL disc, qp, qp2;

    qp = VEC_Dot(ray, psphere->center);
    qp2 = qp * qp;
    disc = qp2 - psphere->c2_r2;

    if (disc > 0.0)
    {
        t1valid = TRUE;
        t2valid = TRUE;
        disc = (float)sqrt(disc);
        g_t1 = qp - disc;
        g_t2 = qp + disc;
    }
    else
    {
        t1valid = FALSE;
        t2valid = FALSE;
    }
}

void IntPlane(float *ray, TSphere *psphere) {
    REAL disc;

    t1valid = FALSE;
    t2valid = FALSE;

    disc = VEC_Dot(ray, &psphere->a);
    if (disc != 0.0)
    {
        if (disc < 0)
        {
            t1valid = TRUE;
            t2valid = TRUE;
            g_t1 = -psphere->d/disc;
            g_t2 = g_t1+5E6f;
        } else {
            t2valid = TRUE;
            t1valid = TRUE;
            g_t2 = -psphere->d/disc;
            g_t1 = g_t2-5E6f;
        }
    }
}


typedef byte TPixel;
#undef DSCR_MAKE_JCAB
#define DSCR_MAKE_JCAB(r, g, b) (r) //(r+g+b)/3)

dword magic_now = 0;
REAL *rays;
REAL dpcx, dpcy;
REAL light[][3] = {
    { 0, 0, 5 },
    { 10, -3, 2 },
//    { -3, 0, 0 },
//    { 0, 20, 20.0 },
};

static int NLIGHT = 1;
#define MAX_LIGHT (sizeof(light)/sizeof(light[0]))


TSphere spheres[] =
{
    { {{1.0f, 0, 7.0f, 1.0f }}, IntSphere, 0},
    { {{0, 0.5f, 6.0f, 1.2f }}, IntSphere, 1},
    { {{0, -1.f, 0, 1.f }}, IntPlane, 0},
    { {{-0.41f, -0.41f, 0, 1.3f }}, IntPlane, 0},
    { {{0, 0.5f, 6.0f, 1.0f }}, IntSphere, 0},

    { {{0, 0.5f, 6.0f, 0.5f }}, IntSphere, 0},
    { {{0, 0.5f, 6.0f, 0.7f }}, IntSphere, 1},
    { {{0, 0.5f, 6.0f, 1.0f }}, IntSphere, 0},
};

int NUM_SPHERES = 5;

#define MAX_SPHERES ((sizeof(spheres)/sizeof(spheres[0])))

void SetPal(void)
{
    int i;

/*
    for (i = 0; i < 256; i++)
    {
        DestPal[3*i+0] = i/4;
        DestPal[3*i+1] = i/8;
        DestPal[3*i+2] = i/12;
        VGA_PutColor(i, DestPal[3*i+0], DestPal[3*i+1], DestPal[3*i+2]);
    }
*/
    float r = 63, g = 27, b = 7;
    float dr, dg, db;
    float cr, cg, cb;

#define pal DestPal
    cr = cg = cb = 0;
    dr = r / 127.0f;
    dg = g / 127.0f;
    db = b / 127.0f;

    for (i = 0; i < 128; i++)
    {
        pal[i*3+0] = (byte)cr;
        pal[i*3+1] = (byte)cg;
        pal[i*3+2] = (byte)cb;
        cr += dr;
        cg += dg;
        cb += db;
    }

    cr = r;
    cg = g;
    cb = b;
    dr = (63 - r) / 127.0f;
    dg = (63 - g) / 127.0f;
    db = (63 - b) / 127.0f;

    for (; i < 256; i++)
    {
        pal[i*3+0] = (byte)cr;
        pal[i*3+1] = (byte)cg;
        pal[i*3+2] = (byte)cb;
        cr += dr;
        cg += dg;
        cb += db;
    }
#undef pal

}

void CalcSphereStuff()
{
    int i;
    TSphere *p = spheres;

    for (i = 0; i < NUM_SPHERES; i++, p++)
    {
        if (p->f == IntSphere) {
            p->r2 = p->r * p->r;
            p->c2_r2 = VEC_Dot(p->center, p->center) - p->r2;
        }
    }
}

static byte *DSCR_VirtScreen;

void PrecalcRays(void)
{
    int i, j;
    float *p = rays;
    REAL x, y;

    for (i = 0; i < LLS_SizeY; i++)
    {
        y = (float)(i - LLS_SizeY/2);
        y = y / dpcy;

        for (j = 0; j < LLS_SizeX; j++)
        {

            x = (float)(j - LLS_SizeX/2);
            x = x / dpcx;

            p[0] = x;
            p[1] = y;
            p[2] = 1;
            VEC_Norm_f(p);
            p += 3;
        }
    }
}

bool DoRT(dword time);

static float ang = -4;
static dword inittime;

static void SetPositions(dword time) {
    ang = (float)(time - inittime)*0.005f;

    spheres[0].center[0] = (float)(1.2f*cos(ang));
    spheres[0].center[1] = (float)(1.2f*sin(ang));

    spheres[1].center[0] = (float)(0.5f*sin(2*ang));
    spheres[1].center[1] = (float)(0.5f*cos(2*ang));

    spheres[2].d = (float)(1.25+0.25*cos(2*ang));

    spheres[3].a = (float)cos(ang);
    spheres[3].b = (float)sin(ang);

    spheres[4].center[0] = (float) (1.1*cos(1.1*ang));
    spheres[4].center[1] = (float) (1.5*sin(1.1*ang));

    spheres[5].center[0] = (float) (-0.5+0.8*cos(-1.7*ang + 2.6));
    spheres[5].center[1] = (float) (-0.3+0.8*sin(-1.8*ang + 2.9));
    spheres[5].center[2] = (float) (6.0 + 0.5*sin(1.9*ang + 4.3));

    spheres[6].center[0] = (float) (1.1*cos(-1.8*ang + 1.6));
    spheres[6].center[1] = (float) (1.2*sin(-1.9*ang + 5.9));
    spheres[6].center[2] = (float) (5.0 + 0.5*sin(2.2*ang + 2.3));

    spheres[7].center[0] = (float) (0.9*cos(-1.6*ang + 1.6));
    spheres[7].center[1] = (float) (1.0*sin(-1.5*ang + 4.9));
    spheres[7].center[2] = (float) (6.0 + 1.0*cos(2.6*ang + 3.3));

    light[0][0] = (float) (1.5*cos(3*ang));
    light[0][2] = (float) (5+1.5*sin(3*ang));

}


bool InitRT(dword time)
{
    inittime = time;

    SetPal();
    EnhancePal();
    DSCR_VirtScreen = (void *)LLS_Screen[0];

    memset(DSCR_VirtScreen, 0, 320*200);
    LLS_Update();

    dpcy = 400.f * LLS_SizeY / 240.f;
    dpcx = 400.f * LLS_SizeX / 320.f;

    REQUIRE( rays = malloc(LLS_SizeX * LLS_SizeY * 12) );
    REQUIRE( DataBuf = malloc(LLS_SizeX * LLS_SizeY * sizeof(*DataBuf)) );

    PrecalcRays();

    SetPositions(time);
    CurFunction = DoRT;

    return TRUE;
}

bool EndRT(dword time)
{
    CurFunction = NULL;
    free(rays);
    free(DataBuf);
    return TRUE;
}



bool DoRT(dword time)
{

    DrawScene();
    memset(LLS_Screen[195], 0, 5*320);
    AADump(LLS_Screen[0]);
    memset(Mode13+195*320, 0, 5*320);

    SetPositions(time);

    ang += 0.02f;

    return TRUE;
}

void TraceRay(float *ray, TPixel *pixel, TData *data);

void DrawScene1(void)
{
    int i, j;
    static int which;

    for (i = (which&1); i < LLS_SizeY; i+=8)
    {
        TPixel *pixel = ((TPixel *)DSCR_VirtScreen) + i*LLS_SizeX;
        TData  *data  = DataBuf + i*LLS_SizeX;
        float *pray = rays + 3*i*LLS_SizeX;

        for (j = ((which)&2)/2, pixel += j, data += j, pray += 3*j; j < LLS_SizeX; j += 8)
        {
            TraceRay(pray, pixel, data);
            pray += 3*8;
            pixel += 8;
            data  += 8;
        }
    }
    which++;
}

#define UMBRAL 20

bool CheckForInterpol(int v1, int v2, PData d1, PData d2)
{
    if (d1->NumObject != d2->NumObject)
        return FALSE;

    if (Abs32(v1-v2) > UMBRAL)
        return FALSE;

    return Abs32((sint) (d1->T-d2->T)) < 2;
}

void InterpolRay(TPixel *pixel, TData *data, int v1, int v2, TData *d1, TData *d2)
{
    *pixel          = (v1 + v2) / 2;
    data->T         = (d1->T + d2->T) * 0.5f;
    data->NumObject = d1->NumObject;
}

void DrawScene2(void)
{
    int i, j;
    static int which;

    for (i = (which&1); i < LLS_SizeY; i+=8)
    {
        TPixel *pixel = ((TPixel *)DSCR_VirtScreen) + i*LLS_SizeX;
        TData  *data  = DataBuf + i*LLS_SizeX;
        float *pray = rays + 3*i*LLS_SizeX;

        for (j = ((which)&2)/2 + 4, pixel += j, data += j, pray += 3*j; j < LLS_SizeX; j += 8)
        {
            int v1 = pixel[-4];
            int v2 = pixel[4];
            if (CheckForInterpol(v1, v2, data-4, data+4))
                InterpolRay(pixel, data, v1, v2, data-4, data+4);
            else
                TraceRay(pray, pixel, data);
            pray += 3*8;
            pixel += 8;
            data  += 8;
        }
    }
    which++;
}

void DrawScene3(void)
{
    int i, j;
    static int which;

    for (i = (which&1) + 4; i < LLS_SizeY; i+=8)
    {
        TPixel *pixel = ((TPixel *)DSCR_VirtScreen) + i*LLS_SizeX;
        TData  *data  = DataBuf + i*LLS_SizeX;
        float *pray = rays + 3*i*LLS_SizeX;

        for (j = ((which)&2)/2, pixel += j, data += j, pray += 3*j; j < LLS_SizeX; j += 4)
        {
            int v1 = pixel[-4*(int)LLS_SizeX];
            int v2;

            if (i+4 >= LLS_SizeY)
                TraceRay(pray, pixel, data);
            else {
                v2 = pixel[ 4*(int)LLS_SizeX];
                if (CheckForInterpol(v1, v2, data-4*(int)LLS_SizeX, data+4*(int)LLS_SizeX))
                    InterpolRay(pixel, data, v1, v2, data-4*(int)LLS_SizeX, data+4*(int)LLS_SizeX);
                else
                    TraceRay(pray, pixel, data);
            }
            pray += 3*4;
            pixel += 4;
            data  += 4;
        }
    }
    which++;
}

void DrawScene4(void)
{
    int i, j;
    static int which;

    for (i = (which&1); i < LLS_SizeY; i+=4)
    {
        TPixel *pixel = ((TPixel *)DSCR_VirtScreen) + i*LLS_SizeX;
        TData  *data  = DataBuf + i*LLS_SizeX;
        float *pray = rays + 3*i*LLS_SizeX;

        for (j = ((which)&2)/2 + 2, pixel += j, data += j, pray += 3*j; j < LLS_SizeX; j += 4)
        {
            int v1 = pixel[-2];
            int v2;

            if (i == LLS_SizeY-2 && j+2 >= LLS_SizeY)
                TraceRay(pray, pixel, data);
            else {
                v2 = pixel[2];
                if (CheckForInterpol(v1, v2, data-2, data+2))
                    InterpolRay(pixel, data, v1, v2, data-2, data+2);
                else
                    TraceRay(pray, pixel, data);
            }
            pray += 3*4;
            pixel += 4;
            data  += 4;
        }
    }
    which++;
}

void DrawScene5(void)
{
    int i, j;
    static int which;

    for (i = (which&1) + 2; i < LLS_SizeY; i+=4)
    {
        TPixel *pixel = ((TPixel *)DSCR_VirtScreen) + i*LLS_SizeX;
        TData  *data  = DataBuf + i*LLS_SizeX;
        float *pray = rays + 3*i*LLS_SizeX;

        for (j = ((which)&2)/2, pixel += j, data += j, pray += 3*j; j < LLS_SizeX; j += 2)
        {
            int v1 = pixel[-2*(int)LLS_SizeX];
            int v2;

            if (i+2 >= LLS_SizeY)
                TraceRay(pray, pixel, data);
            else {
                v2 = pixel[ 2*(int)LLS_SizeX];
                if (CheckForInterpol(v1, v2, data-2*(int)LLS_SizeX, data+2*(int)LLS_SizeX))
                    InterpolRay(pixel, data, v1, v2, data-2*(int)LLS_SizeX, data+2*(int)LLS_SizeX);
                else
                    TraceRay(pray, pixel, data);
            }
            pray += 3*2;
            pixel += 2;
            data  += 2;
        }
    }
    which++;
}

void DrawScene(void)
{
    CalcSphereStuff();

    DrawScene1();
    DrawScene2();
    DrawScene3();
    DrawScene4();
    DrawScene5();

}


#define MAX_HITS 20
struct hit_t
{
    REAL t;
    TSphere *pSphere;
    int NumObject;
    bool first;
    struct hit_t *next;
} hits[MAX_HITS];
struct hit_t *next_free_hit;
struct hit_t *hit_list;

void TraceRay(float *ray, TPixel *pixel, TData *data)
{
    int i, inneg, inpos;
    TSphere *psphere = spheres;
    TSphere *pintersec = NULL;
    REAL tmin = (REAL) INF;
    struct hit_t *phit, *p_real_hit;

    hit_list = NULL;
    next_free_hit = hits;

    for (i = 0; i < NUM_SPHERES; i++, psphere++)
    {
        psphere->f(ray, psphere);

        if (t1valid)
        {
            struct hit_t **p, *q;

            q = next_free_hit++;
            q->t = g_t1;
            q->pSphere = psphere;
            q->first = TRUE;
            q->NumObject = i;

            for (p = &hit_list; *p && (*p)->t < g_t1; p = &((*p)->next))
                ;

            q->next = *p;
            *p = q;
        }

        if (t2valid)
        {
            struct hit_t **p, *q;

            q = next_free_hit++;
            q->t = g_t2;
            q->pSphere = psphere;
            q->first = FALSE;
            q->NumObject = i;

            for (p = &hit_list; *p && (*p)->t < g_t2; p = &((*p)->next))
                ;

            q->next = *p;
            *p = q;
        }
    }

    // Analyze the hit list
    inneg = 0;
    inpos = 0;
    p_real_hit = NULL;
    for (phit = hit_list; phit; phit = phit->next)
    {
        if (phit->first)
            if (phit->pSphere->neg)
            {
                inneg++;
            }
            else
            {
                inpos++;
            }
        else
            if (phit->pSphere->neg)
            {
                inneg--;
            }
            else
            {
                inpos--;
            }

        if (phit->t > 0.0 && !inneg && inpos)
        {
            // Solid hit!
            p_real_hit = phit;
            break;
        }
    }

    if (!p_real_hit)
    {
        *pixel = DSCR_MAKE_JCAB(60,60,60);
        data->T         = (float)INF;
        data->NumObject = -1;
    }
    else
    {
        float ipoint[3], normal[3], val;
        int i;
        val = 0;

        tmin = p_real_hit->t;
        pintersec = p_real_hit->pSphere;

        data->T         = tmin;
        data->NumObject = p_real_hit->NumObject;

        ipoint[0] = ray[0] * tmin;
        ipoint[1] = ray[1] * tmin;
        ipoint[2] = ray[2] * tmin;
        if (pintersec->f == IntSphere) {
            normal[0] = ipoint[0] - pintersec->center[0];
            normal[1] = ipoint[1] - pintersec->center[1];
            normal[2] = ipoint[2] - pintersec->center[2];
            if (!p_real_hit->first)
            {
                normal[0] = -normal[0];
                normal[1] = -normal[1];
                normal[2] = -normal[2];
            }
            VEC_Norm_f(normal);
        } else {
            memcpy(normal, &pintersec->a, 12);
            if (ipoint[2] < 20) {
                val = 0.5f*(float)((((int)ipoint[0])^((int)ipoint[1])^((int)ipoint[2])) & 1);
                if (ipoint[2] > 10)
                    val -= val*(ipoint[2] - 10)/(20 - 10);
            }
        }
        for (i = 0; i < NLIGHT; i++) {
            float ilvec[3];
            float v;

#define MINV (0.0f)
            ilvec[0] = light[i][0] - ipoint[0];
            ilvec[1] = light[i][1] - ipoint[1];
            ilvec[2] = light[i][2] - ipoint[2];
            VEC_Norm_f(ilvec);
            v = VEC_Dot(ilvec, normal);

            if (v < MINV)
                v = 0;
            else {
                v = (v-MINV)/(1.0f-MINV);
                v = v*v;
            }
            val += v;
        }
        if (val > 1.0)
            val = 1.0;
        *pixel = (/*0*(int)*pixel+*/(int)(DSCR_MAKE_JCAB(255*val,255*val,255*val)));
    }
}

extern bool SetUseRT(dword time) {
    CurFunction = DoRT;
    return TRUE;
}

extern bool IncRTBalls(dword time) {
    if (NUM_SPHERES < MAX_SPHERES) {
        spheres[1].r *= 0.8f;
        NUM_SPHERES++;
    }
    if (NLIGHT < MAX_LIGHT) {
        NLIGHT++;
    }
    return TRUE;
}

