#define MODULE_LMD

#include  "graf.h"

extern FLOAT TRS_ViewpointX;
extern FLOAT TRS_ViewpointY;
extern FLOAT TRS_ViewpointZ;
extern FLOAT TRS_ViewpointW;

extern FLOAT LGT_AmbientColor[];

extern int LGT_Shadows;
extern int USR_BackFace;

void lmd_set_lmodel( int lmodel )
{
    if ( lmodel < 0 || lmodel >= NUMBER_OF_LMODELS )
    {
        fprintf( stderr, "lmd_set_lmodel: light model [%d] out of range.\n", lmodel );
        return;
    }

    LMD_Lmodel = lmodel;
}

void lmd_set_lmodel_interpolants( int n, int *C )
{
    int i;

    if ( n < 0 || n >= VERTEX_NCOMP )
    {
        fprintf( stderr, "lmd_set_lmodel_interpolants: number of interpolants [%d] out of range.\n", n );
        return;
    }

    for( i = 0; i < n; i++ )
        if ( C[i] < 0 || C[i] >= VERTEX_NCOMP )
        {
            fprintf( stderr, "lmd_set_lmodel_interpolants: no such interpolant [%d].\n", C[i] );
            return;
        }

    LMD_NtoInterpolate[LMD_Lmodel] = n;
    for( i = 0; i < n; i++ ) LMD_Interpolate[LMD_Lmodel][i] = C[i];
}

void lmd_add_lmodel_interpolant( int C )
{
    int i, n;

    if ( C < 0 || C >= VERTEX_NCOMP )
    {
        fprintf( stderr, "lmd_add_lmodel_interpolant: no such interpolant [%d].\n", C );
        return;
    }

    n = LMD_NtoInterpolate[LMD_Lmodel];

    for( i = n - 1; i >= 0; i-- ) 
        if ( LMD_Interpolate[LMD_Lmodel][i] == C ) return;

    LMD_Interpolate[LMD_Lmodel][n] = C;
    LMD_NtoInterpolate[LMD_Lmodel]++;
}

int lmd_backfaceing(int n, ZBF_VERTEX *polygon)
{
    FLOAT ax,ay,az;
    FLOAT bx,by,bz;

    FLOAT Nx,Ny,Nz;
    FLOAT Vx,Vy,Vz;

    FLOAT x,y,z;
    
    FLOAT r,VdotN;

    int i;

    Nx = Ny = Nz = 0.0;
    for( i=0; i<n; i++ )
    {
        Nx += polygon[i].NormalX;
        Ny += polygon[i].NormalY;
        Nz += polygon[i].NormalZ;
    }
    Nx /= n; Ny /= n; Nz /= n;

#ifdef NOTDEF
    ax = polygon[0].CoordX - polygon[1].CoordX;
    ay = polygon[0].CoordY - polygon[1].CoordY;
    az = polygon[0].CoordZ - polygon[1].CoordZ;

    bx = polygon[2].CoordX - polygon[1].CoordX;
    by = polygon[2].CoordY - polygon[1].CoordY;
    bz = polygon[2].CoordZ - polygon[1].CoordZ;

    Nx = -(ay*bz - az*by);
    Ny = -(az*bx - ax*bz);
    Nz = -(ax*by - ay*bx);
#endif

    if ( TRS_ViewpointW > 0.0 )
    {
        x = y = z = 0.0;
        for( i=0; i<n; i++ )
        {
            x += polygon[i].CoordX;
            y += polygon[i].CoordY;
            z += polygon[i].CoordZ;
        }

        x /= n;  y /= n;  z /= n;
        Vx = -x; Vy = -y; Vz = -z;
    } else
    {
        Vx = 0; Vy = 0; Vz = 1;
    }

    VdotN = Vx*Nx + Vy*Ny + Vz*Nz;

    return (VdotN < 0.0);
}

int lmd_in_shadow(LIGHT *CurLight,FLOAT *P)
{
     FLOAT R[VERTEX_NCOMP];

     int k;

     TRS_MATRIX SaveM;

     trs_matrix_mode(TRS_MATRIX_PROJECTION);
     trs_get_matrix(SaveM);
     trs_load_matrix(CurLight->PMatrix);
     trs_matrix_mode(TRS_MATRIX_VIEWING);

     trs_push_matrix();
     trs_mult_matrix_right(CurLight->VMatrix);
    
     R[VERTEX_COORD_X] = P[VERTEX_ORIG_COORD_X];
     R[VERTEX_COORD_Y] = P[VERTEX_ORIG_COORD_Y];
     R[VERTEX_COORD_Z] = P[VERTEX_ORIG_COORD_Z];

     trs_viewing_transform(1,(ZBF_VERTEX *)R);
     trs_projection_transform(1,(ZBF_VERTEX *)R);
     trs_scale_transform(1,(ZBF_VERTEX *)R);

     trs_pop_matrix();
    
     trs_matrix_mode(TRS_MATRIX_PROJECTION);
     trs_load_matrix(SaveM);
     trs_matrix_mode(TRS_MATRIX_VIEWING);

     if ( R[VERTEX_COORD_Y] < CurLight->SNY && R[VERTEX_COORD_Y] >= 0 &&
          R[VERTEX_COORD_X] < CurLight->SNX && R[VERTEX_COORD_X] >= 0 )
     {
         k = ((int)R[VERTEX_COORD_Y])*CurLight->SNX + R[VERTEX_COORD_X];
         if ( CurLight->shadow_map[k]<R[VERTEX_COORD_Z]-5.0e-3 ) return TRUE;
     }

     return FALSE;
}

void lmd_evaluate_lmodel( FLOAT *P )
{
    FLOAT Vx, Vy, Vz;
    FLOAT Lx, Ly, Lz;
    FLOAT Rx, Ry, Rz;
    FLOAT Nx, Ny, Nz;

    FLOAT LdotN, LdotP, VdotN, VdotR, LdotD;

    FLOAT x,y,z,r;

    FLOAT AmbientR = P[VERTEX_MATERIAL_AMBIENT+0];
    FLOAT AmbientG = P[VERTEX_MATERIAL_AMBIENT+1];
    FLOAT AmbientB = P[VERTEX_MATERIAL_AMBIENT+2];

    FLOAT DiffuseR = P[VERTEX_MATERIAL_DIFFUSE+0];
    FLOAT DiffuseG = P[VERTEX_MATERIAL_DIFFUSE+1];
    FLOAT DiffuseB = P[VERTEX_MATERIAL_DIFFUSE+2];

    FLOAT SpecularR = P[VERTEX_MATERIAL_SPECULAR+0];
    FLOAT SpecularG = P[VERTEX_MATERIAL_SPECULAR+1];
    FLOAT SpecularB = P[VERTEX_MATERIAL_SPECULAR+2];
    FLOAT Ks;

    FLOAT Lr, Lg, Lb;

    LIGHT *CurLight;

    int light;

    FLOAT R[VERTEX_NCOMP];

    x = P[VERTEX_COORD_X];
    y = P[VERTEX_COORD_Y];
    z = P[VERTEX_COORD_Z];

    if ( LMD_Lmodel == LMD_LMODEL_PHONG )
    {
        R[VERTEX_COORD_X] = P[VERTEX_ORIG_COORD_X];
        R[VERTEX_COORD_Y] = P[VERTEX_ORIG_COORD_Y];
        R[VERTEX_COORD_Z] = P[VERTEX_ORIG_COORD_Z];
        trs_viewing_transform(1,(ZBF_VERTEX *)R);

        x = R[VERTEX_COORD_X];
        y = R[VERTEX_COORD_Y];
        z = R[VERTEX_COORD_Z];
    }

    Nx = P[VERTEX_NORMAL_X];
    Ny = P[VERTEX_NORMAL_Y];
    Nz = P[VERTEX_NORMAL_Z];

    if ( TRS_ViewpointW > 0.0 )
    {
        Vx = -x; Vy = -y; Vz = -z;
        normalize( Vx,Vy,Vz );
    } else
    {
        Vx = 0.0; Vy = 0.0; Vz = 1.0;
    }

    VdotN = Vx*Nx + Vy*Ny + Vz*Nz;

    P[VERTEX_COLOR_R] = AmbientR*LGT_AmbientColor[0];
    P[VERTEX_COLOR_G] = AmbientG*LGT_AmbientColor[1];
    P[VERTEX_COLOR_B] = AmbientB*LGT_AmbientColor[2];

    if ( !USR_BackFace || VdotN > 0.0 )
    {
        for( light = 0; light < MAX_LIGHTS; light++ )
        {
            CurLight = &LGT_Lights[light];

            if ( CurLight->LightInUse == LGT_LIGHT_ON )
            {
                if ( CurLight->shadow_map && LGT_Shadows )
                {
                    if ( lmd_in_shadow(CurLight,P) ) continue;
                }

                if ( CurLight->PositionW > 0.0 )
                {
                    Lx = CurLight->PositionX - x;
                    Ly = CurLight->PositionY - y;
                    Lz = CurLight->PositionZ - z;
                    normalize(Lx,Ly,Lz);
                } else
                {
                    Lx = CurLight->PositionX;
                    Ly = CurLight->PositionY;
                    Lz = CurLight->PositionZ;
                }

                LdotN = Lx*Nx + Ly*Ny + Lz*Nz;

                if ( LdotN<0.0 )
                {
                    if ( USR_BackFace ) continue;

                    Nx = -Nx;
                    Ny = -Ny;
                    Nz = -Nz;
                    LdotN = -LdotN;
                    VdotN = -VdotN;
                }

                if ( VdotN > 0.0 )
                {
                    /*
                     * Check for spotlights.
                     */
                    LdotD = 1.0;
                    if ( CurLight->FieldAngle > 0.0 )
                    { 
                        LdotD  = CurLight->DirectionX*Lx;
                        LdotD += CurLight->DirectionY*Ly;
                        LdotD += CurLight->DirectionZ*Lz;
                        LdotD = -LdotD;

                        if ( LdotD < 0 || LdotD < CurLight->FieldAngle ) continue;

                        if ( CurLight->FieldN != 1.0 )
                        {
                             LdotD = pow(LdotD,CurLight->FieldN);
                        }
                    }

                    Lr = LdotD*CurLight->Color[0];
                    Lg = LdotD*CurLight->Color[1];
                    Lb = LdotD*CurLight->Color[2];

                    P[VERTEX_COLOR_R] += DiffuseR*Lr*LdotN;
                    P[VERTEX_COLOR_G] += DiffuseG*Lg*LdotN;
                    P[VERTEX_COLOR_B] += DiffuseB*Lb*LdotN;

                    if ( P[VERTEX_MATERIAL_SPECULAR_EXP] > 0.0 )
                    {
                        Rx = 2.0*Nx*LdotN - Lx;
                        Ry = 2.0*Ny*LdotN - Ly;
                        Rz = 2.0*Nz*LdotN - Lz;

                        VdotR = Vx*Rx + Vy*Ry + Vz*Rz;

                        if ( VdotR >= 0.0 )
                        {
                            Ks = pow(VdotR,P[VERTEX_MATERIAL_SPECULAR_EXP]);
                            P[VERTEX_COLOR_R] += Ks*SpecularR*Lr;
                            P[VERTEX_COLOR_G] += Ks*SpecularG*Lg;
                            P[VERTEX_COLOR_B] += Ks*SpecularB*Lb;
                        }
                    }
                }
            }
        }
    }

    P[VERTEX_COLOR_R] = MIN(MAX(P[VERTEX_COLOR_R],0.0),1.0);
    P[VERTEX_COLOR_G] = MIN(MAX(P[VERTEX_COLOR_G],0.0),1.0);
    P[VERTEX_COLOR_B] = MIN(MAX(P[VERTEX_COLOR_B],0.0),1.0);
}

void lmd_evaluate_scatter(FLOAT *P)
{
    FLOAT Vx, Vy, Vz;
    FLOAT Lx, Ly, Lz;
    FLOAT Rx, Ry, Rz;
    FLOAT Nx, Ny, Nz;

    FLOAT LdotN, LdotP, VdotN, VdotR, LdotD;

    FLOAT x,y,z,r;

    FLOAT AmbientR = P[VERTEX_MATERIAL_AMBIENT+0];
    FLOAT AmbientG = P[VERTEX_MATERIAL_AMBIENT+1];
    FLOAT AmbientB = P[VERTEX_MATERIAL_AMBIENT+2];

    FLOAT DiffuseR = P[VERTEX_MATERIAL_DIFFUSE+0];
    FLOAT DiffuseG = P[VERTEX_MATERIAL_DIFFUSE+1];
    FLOAT DiffuseB = P[VERTEX_MATERIAL_DIFFUSE+2];

    FLOAT Lr, Lg, Lb;
    LIGHT *CurLight;

    int light;

    x = P[VERTEX_COORD_X];
    y = P[VERTEX_COORD_Y];
    z = P[VERTEX_COORD_Z];

    P[VERTEX_COLOR_R] = AmbientR*LGT_AmbientColor[0];
    P[VERTEX_COLOR_G] = AmbientG*LGT_AmbientColor[1];
    P[VERTEX_COLOR_B] = AmbientB*LGT_AmbientColor[2];

    for( light = 0; light < MAX_LIGHTS; light++ )
    {
        CurLight = &LGT_Lights[light];

        if ( CurLight->LightInUse == LGT_LIGHT_ON )
        {
            if ( CurLight->shadow_map && LGT_Shadows )
            {
                if ( lmd_in_shadow(CurLight,P) ) continue;
            }

            if ( CurLight->PositionW > 0.0 )
            {
                Lx = CurLight->PositionX - x;
                Ly = CurLight->PositionY - y;
                Lz = CurLight->PositionZ - z;
                normalize(Lx,Ly,Lz);
            } else
            {
                Lx = CurLight->PositionX;
                Ly = CurLight->PositionY;
                Lz = CurLight->PositionZ;
            }

            /*
             * Check for spotlights.
             */
            LdotD = 1.0;
            if ( CurLight->FieldAngle > 0.0 )
            { 
                LdotD  = CurLight->DirectionX*Lx;
                LdotD += CurLight->DirectionY*Ly;
                LdotD += CurLight->DirectionZ*Lz;
                LdotD = -LdotD;

                if ( LdotD < 0 || LdotD < CurLight->FieldAngle ) continue;

                if ( CurLight->FieldN != 1.0 )
                {
                     LdotD = pow(LdotD,CurLight->FieldN);
                }
            }

            Lr = LdotD*CurLight->Color[0];
            Lg = LdotD*CurLight->Color[1];
            Lb = LdotD*CurLight->Color[2];

            P[VERTEX_COLOR_R] += DiffuseR*Lr;
            P[VERTEX_COLOR_G] += DiffuseG*Lg;
            P[VERTEX_COLOR_B] += DiffuseB*Lb;
        }
    }

    P[VERTEX_COLOR_R] = MIN(MAX(P[VERTEX_COLOR_R],0.0),1.0);
    P[VERTEX_COLOR_G] = MIN(MAX(P[VERTEX_COLOR_G],0.0),1.0);
    P[VERTEX_COLOR_B] = MIN(MAX(P[VERTEX_COLOR_B],0.0),1.0);
}

void lmd_evaluate_scatter_mie(FLOAT *P)
{
    FLOAT Vx, Vy, Vz, Lx, Ly, Lz;
    FLOAT Rx, Ry, Rz, Nx, Ny, Nz;

    FLOAT LdotN, LdotP, VdotN, VdotR, LdotD, LdotV;

    FLOAT x,y,z,r,a,phi;

    FLOAT AmbientR = P[VERTEX_MATERIAL_AMBIENT+0];
    FLOAT AmbientG = P[VERTEX_MATERIAL_AMBIENT+1];
    FLOAT AmbientB = P[VERTEX_MATERIAL_AMBIENT+2];

    FLOAT DiffuseR = P[VERTEX_MATERIAL_DIFFUSE+0];
    FLOAT DiffuseG = P[VERTEX_MATERIAL_DIFFUSE+1];
    FLOAT DiffuseB = P[VERTEX_MATERIAL_DIFFUSE+2];

    FLOAT Lr, Lg, Lb;
    LIGHT *CurLight;

    int light;

    x = P[VERTEX_COORD_X];
    y = P[VERTEX_COORD_Y];
    z = P[VERTEX_COORD_Z];

    if ( TRS_ViewpointW > 0.0 )
    {
        Vx = -x; Vy = -y; Vz = -z;
        normalize( Vx,Vy,Vz );
    } else
    {
        Vx = 0.0; Vy = 0.0; Vz = 1.0;
    }

    P[VERTEX_COLOR_R] = AmbientR*LGT_AmbientColor[0];
    P[VERTEX_COLOR_G] = AmbientG*LGT_AmbientColor[1];
    P[VERTEX_COLOR_B] = AmbientB*LGT_AmbientColor[2];

    for( light = 0; light < MAX_LIGHTS; light++ )
    {
        CurLight = &LGT_Lights[light];

        if ( CurLight->LightInUse == LGT_LIGHT_ON )
        {
            if ( CurLight->shadow_map && LGT_Shadows )
            {
                if ( lmd_in_shadow(CurLight,P) ) continue;
            }

            if ( CurLight->PositionW > 0.0 )
            {
                Lx = CurLight->PositionX - x;
                Ly = CurLight->PositionY - y;
                Lz = CurLight->PositionZ - z;
                normalize(Lx,Ly,Lz);
            } else
            {
                Lx = CurLight->PositionX;
                Ly = CurLight->PositionY;
                Lz = CurLight->PositionZ;
            }

            /*
             * Check for spotlights.
             */
            LdotD = 1.0;
            if ( CurLight->FieldAngle > 0.0 )
            { 
                LdotD  = CurLight->DirectionX*Lx;
                LdotD += CurLight->DirectionY*Ly;
                LdotD += CurLight->DirectionZ*Lz;
                LdotD = -LdotD;

                if ( LdotD < 0 || LdotD < CurLight->FieldAngle ) continue;
                if ( CurLight->FieldN != 1.0 ) LdotD = pow(LdotD,CurLight->FieldN);
            }

            LdotV = Lx*Vx + Ly*Vy + Lz*Vz;
            phi = acos(LdotV);
            if ( phi == 0.0 ) continue;
            phi = 2*sin(phi/2);

            a  = (sin(phi)/phi*phi - cos(phi)/phi)/phi;
            a = a*a;

            Lr = a*LdotD*CurLight->Color[0];
            Lg = a*LdotD*CurLight->Color[1];
            Lb = a*LdotD*CurLight->Color[2];

            P[VERTEX_COLOR_R] += DiffuseR*Lr;
            P[VERTEX_COLOR_G] += DiffuseG*Lg;
            P[VERTEX_COLOR_B] += DiffuseB*Lb;
        }
    }

    P[VERTEX_COLOR_R] = MIN(MAX(P[VERTEX_COLOR_R],0.0),1.0);
    P[VERTEX_COLOR_G] = MIN(MAX(P[VERTEX_COLOR_G],0.0),1.0);
    P[VERTEX_COLOR_B] = MIN(MAX(P[VERTEX_COLOR_B],0.0),1.0);
}
