/*=============================================================================
        Name    : shader.c
        Purpose : shader provides specialized shading models for non-rGL renderers

Created 22/06/1998 by khent
Copyright Relic Entertainment, Inc.  All rights reserved.
=============================================================================*/

#include "glinc.h"
#include <math.h>
#include <string.h>
#include "shader.h"
#include "clipper.h"
#include "fastmath.h"
#include "light.h"
#include "debug.h"
#include "render.h"
#include "memory.h"


/*=============================================================================
    Data
=============================================================================*/

#define SLOW_TO_INT 1

#ifndef CLAMP
#define CLAMP(X, MIN, MAX) ((X) < (MIN) ? (MIN) : ((X) > (MAX) ? (MAX) : (X)))
#endif

#ifndef MIN2
#define MIN2(X, Y) ((X) < (Y) ? (X) : (Y))
#endif

// ---
static double chop_temp;

#define FAST_TO_INT(X) ((chop_temp = (double)(X) + BIG_NUM), *(int*)(&chop_temp))
#define BIG_NUM ((float)(1 << 26)*(1 << 26)*1.5)
// ---

static real32 shDockFactor = 0.0f;
static color  shDockColor  = colWhite;
static real32 shDockScalarRed, shDockScalarGreen, shDockScalarBlue;

static real32 shSpecularDefault[3] = {11.0f, 2.0f, 4.0f};
static real32 shSpecularExponent[3] = {11.0f, 2.0f, 4.0f};

static bool shNormalize = TRUE;

static real32 shGammaAdjust;
static real32 shBrightness;

typedef real32 Mat2[2][2];

real32 shIdentityMatrix[16] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

enum
{
    M00 = 0, M01 = 4, M02 = 8, M03 = 12,
    M10 = 1, M11 = 5, M12 = 9, M13 = 13,
    M20 = 2, M21 = 6, M22 = 10,M23 = 14,
    M30 = 3, M31 = 7, M32 = 11,M33 = 15
};

static real32 shGlobalAmbient[4];
static real32 shBaseColor[4];
static real32 shStdBaseColor[4];
static sdword shBaseAlpha;

typedef struct shadeMaterial
{
    real32 ambient[4];
    real32 diffuse[4];
} shadeMaterial;

static shadeMaterial shMaterial;

typedef struct shadeLight
{
    real32 diffuse[4];
    real32 ambient[4];
    real32 matdiffuse[4];
    real32 stdmatdiffuse[4];
    real32 position[4];
    real32 worldposition[4];
    real32 inverseworldposition[4];
} shadeLight;

static shadeLight shLight[2];

static hmatrix shLightMatStack[10];
static long shLightMatStackPos = 0;

static sdword nVerts;
static hvector* normalList = NULL;
shColor* colorList = NULL;

/*=============================================================================
    Code
=============================================================================*/

/*-----------------------------------------------------------------------------
    Name        : shStartup
    Description : initializes the shader module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shStartup(void)
{
    sdword i;

    shSpecularDefault[0] = 11.0f;
    shSpecularDefault[1] = 2.0f;
    shSpecularDefault[2] = 4.0f;

    for (i = 0; i < 3; i++)
    {
        shSpecularExponent[i] = shSpecularDefault[i];
    }

    shGammaAdjust = 0.15f;
    shBrightness = shGammaAdjust * 127.0f;

	shLightMatStackPos = 0;
	shLightMatStack[0] = IdentityHMatrix;
}

/*-----------------------------------------------------------------------------
    Name        : shShutdown
    Description : shuts down the shader module
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shShutdown(void)
{
    nVerts = 0;
    if (normalList != NULL)
    {
        memFree(normalList);
        normalList = NULL;
    }
    if (colorList != NULL)
    {
        memFree(colorList);
        colorList = NULL;
    }
}

//increase "gamma" (brightness)
void shGammaUp()
{
    shGammaAdjust += 0.05f;
    shBrightness = shGammaAdjust * 127.0f;
}

//decrease "gamma" (brightness)
void shGammaDown()
{
    shGammaAdjust -= 0.05f;
    if (shGammaAdjust < 0.0f)
    {
        shGammaAdjust = 0.0f;
    }
    shBrightness = shGammaAdjust * 127.0f;
}

//reset "gamma" (brightness) to default
void shGammaReset()
{
    shGammaAdjust = 0.15f;
    shBrightness = shGammaAdjust * 127.0f;
}

//set "gamma" (brightness) to a particular value
void shGammaSet(sdword gamma)
{
    real32 fgamma;

    fgamma = (real32)gamma * 0.01f;
    shGammaAdjust = fgamma;
    shBrightness = shGammaAdjust * 127.0f;
}

//get "gamma" (brightness) for Game Options
sdword shGammaGet(void)
{
    real32 fgamma;

    fgamma = shGammaAdjust * 100.0f;
    return (sdword)fgamma;
}

/*-----------------------------------------------------------------------------
    Name        : shSetGlobalAmbient
    Description : interface to set shGlobalAmbient variable
    Inputs      : ambient - amount of global ambient light (4-element array)
    Outputs     : shGlobalAmbient is modified
    Return      :
----------------------------------------------------------------------------*/
void shSetGlobalAmbient(real32* ambient)
{
    shGlobalAmbient[0] = ambient[0];
    shGlobalAmbient[1] = ambient[1];
    shGlobalAmbient[2] = ambient[2];
    shGlobalAmbient[3] = ambient[3];
}

/*-----------------------------------------------------------------------------
    Name        : shSetMaterial
    Description : interface to set shMaterial
    Inputs      : ambient - ambient components
                  diffuse - diffuse components
    Outputs     : shMaterial is modified
    Return      :
----------------------------------------------------------------------------*/
void shSetMaterial(real32* ambient, real32* diffuse)
{
    if (ambient != NULL)
    {
        memcpy(shMaterial.ambient, ambient, 4*sizeof(real32));
    }
    if (diffuse != NULL)
    {
        memcpy(shMaterial.diffuse, diffuse, 4*sizeof(real32));
    }
}

/*-----------------------------------------------------------------------------
    Name        : shSetLighting
    Description :
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shSetLighting(sdword index, real32* diffuse, real32* ambient)
{
    memcpy(shLight[index].diffuse, diffuse, 4*sizeof(real32));
    memcpy(shLight[index].ambient, ambient, 4*sizeof(real32));
}

/*-----------------------------------------------------------------------------
    Name        : shUpdateLighting
    Description : called after the current material has been modified
    Inputs      :
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shUpdateLighting()
{
    sdword l;

    shBaseColor[0] = shGlobalAmbient[0] * shMaterial.ambient[0];
    shBaseColor[1] = shGlobalAmbient[1] * shMaterial.ambient[1];
    shBaseColor[2] = shGlobalAmbient[2] * shMaterial.ambient[2];
    shBaseColor[3] = MIN2(shMaterial.diffuse[3], 1.0f);

    shStdBaseColor[0] = shGlobalAmbient[0] * 0.196078f;
    shStdBaseColor[1] = shGlobalAmbient[1] * 0.196078f;
    shStdBaseColor[2] = shGlobalAmbient[2] * 0.196078f;
    shStdBaseColor[3] = shBaseColor[3];

    for (l = 0; l < lightNumLights; l++)
    {
        //update basecolor with light+material contribution to ambient
        shBaseColor[0] += shLight[l].ambient[0] * shMaterial.ambient[0];
        shBaseColor[1] += shLight[l].ambient[1] * shMaterial.ambient[1];
        shBaseColor[2] += shLight[l].ambient[2] * shMaterial.ambient[2];
        //shLight[l].position already contains vpInfNorm
        shLight[l].matdiffuse[0] = shLight[l].diffuse[0] * shMaterial.diffuse[0];
        shLight[l].matdiffuse[1] = shLight[l].diffuse[1] * shMaterial.diffuse[1];
        shLight[l].matdiffuse[2] = shLight[l].diffuse[2] * shMaterial.diffuse[2];

        shLight[l].stdmatdiffuse[0] = shLight[l].diffuse[0] * 0.784314f;
        shLight[l].stdmatdiffuse[1] = shLight[l].diffuse[1] * 0.784314f;
        shLight[l].stdmatdiffuse[2] = shLight[l].diffuse[2] * 0.784314f;
    }

    for (l = 0; l < 4; l++)
    {
        shBaseColor[l] *= 255.0f;
        shStdBaseColor[l] *= 255.0f;
    }
    for (l = 0; l < 3; l++)
    {
        shBaseColor[l] += shBrightness;
        shStdBaseColor[l] += shBrightness;
    }

    shBaseAlpha = (sdword)shBaseColor[3];
}

/*-----------------------------------------------------------------------------
    Name        : shSetExponent
    Description : sets the exponent to use for the specular alpha term
    Inputs      : index - [0..2]
                  exponent - if -1, reset to default; else use given as exponent
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shSetExponent(sdword index, real32 exponent)
{
    if (index < 0 || index > 2)
    {
        return;
    }
    shSpecularExponent[index] = (exponent == -1.0f) ? shSpecularDefault[index] : exponent;
}

/*-----------------------------------------------------------------------------
    Name        : shDockLight
    Description : sets the docking light scale factor
    Inputs      : t - [0..1]
    Outputs     : shDockFactor == t
    Return      :
----------------------------------------------------------------------------*/
void shDockLight(real32 t)
{
    shDockFactor = t;
}

/*-----------------------------------------------------------------------------
    Name        : shDockLightColor
    Description : sets the docking light colour
    Inputs      : c - the colour of the light
    Outputs     : shDockColor == c
    Return      :
----------------------------------------------------------------------------*/
void shDockLightColor(color c)
{
    shDockColor = c;
    shDockScalarRed = colReal32(colRed(c));
    shDockScalarGreen = colReal32(colGreen(c));
    shDockScalarBlue = colReal32(colBlue(c));
}

/*-----------------------------------------------------------------------------
    Name        : shTransformNormal
    Description : transforms and normalizes a normal by provided matrix
    Inputs      : out - output normal
                  in - input normal
                  m - the matrix with which to transform the normal by
    Outputs     : out contains the resultant normal
    Return      :
----------------------------------------------------------------------------*/
void shTransformNormal(vector* out, vector* in, real32* m)
{
    real32 ux, uy, uz;
    real64 tx, ty, tz;
    real64 len, scale;

    if (shNormalize)
    {
        ux = in->x;
        uy = in->y;
        uz = in->z;
        tx = ux*m[0] + uy*m[1] + uz*m[2];
        ty = ux*m[4] + uy*m[5] + uz*m[6];
        tz = ux*m[8] + uy*m[9] + uz*m[10];
        len = fmathSqrtDouble(tx*tx + ty*ty + tz*tz);
        scale = (len > 1E-30) ? (1.0 / len) : 1.0;
        out->x = (real32)(tx*scale);
        out->y = (real32)(ty*scale);
        out->z = (real32)(tz*scale);
    }
    else
    {
        ux = in->x;
        uy = in->y;
        uz = in->z;
        out->x = ux*m[0] + uy*m[1] + uz*m[2];
        out->y = ux*m[4] + uy*m[5] + uz*m[6];
        out->z = ux*m[8] + uy*m[9] + uz*m[10];
    }
}

/*-----------------------------------------------------------------------------
    Name        : _shTransformNormal
    Description : like shTransformNormal, but takes an additional parameter
    Inputs      : [as per shTransformNormal, with 1 addition]
                  normalize - 0 (don't normalize) or 1 (normalize)
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void _shTransformNormal(vector* out, vector* in, real32* m, sdword normalize)
{
    real32 ux, uy, uz;

    if (normalize)
    {
        real64 tx, ty, tz;
        real64 len, scale;

        ux = in->x;
        uy = in->y;
        uz = in->z;
        tx = ux*m[0] + uy*m[1] + uz*m[2];
        ty = ux*m[4] + uy*m[5] + uz*m[6];
        tz = ux*m[8] + uy*m[9] + uz*m[10];
        len = fmathSqrtDouble(tx*tx + ty*ty + tz*tz);
        scale = (len > 1E-30) ? (1.0 / len) : 1.0;
        out->x = (real32)(tx*scale);
        out->y = (real32)(ty*scale);
        out->z = (real32)(tz*scale);
    }
    else
    {
        ux = in->x;
        uy = in->y;
        uz = in->z;
        out->x = ux*m[0] + uy*m[1] + uz*m[2];
        out->y = ux*m[4] + uy*m[5] + uz*m[6];
        out->z = ux*m[8] + uy*m[9] + uz*m[10];
    }
}

/*-----------------------------------------------------------------------------
    Name        : shTransformVertex
    Description : transforms a vertex by provided matrix
    Inputs      : out - output vertex
                  in - input vertex
                  m - the matrix with which to transform the vertex by
    Outputs     : out contains the resultant vertex
    Return      :
----------------------------------------------------------------------------*/
void shTransformVertex(vector* out, vector* in, real32* m)
{
    real32 ox, oy, oz;

    ox = in->x;
    oy = in->y;
    oz = in->z;

    out->x = m[0]*ox + m[4]*oy +  m[8]*oz + m[12];
    out->y = m[1]*ox + m[5]*oy +  m[9]*oz + m[13];
    out->z = m[2]*ox + m[6]*oy + m[10]*oz + m[14];
}

/*-----------------------------------------------------------------------------
    Name        : shPow
    Description : wrapper for math.h's "pow"
    Inputs      : ..
    Outputs     :
    Return      : result
----------------------------------------------------------------------------*/
#define shPow(A,B) (real32)pow((real64)(A), (real64)(B))

/*-----------------------------------------------------------------------------
    Name        : shColour
    Description : shades a vertex according to the standard lighting model
    Inputs      : side = 0 or 1 (front or back)
                  norm - normal at the vertex
                  color - where output colours go
                  minv - inverse modelview matrix
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shColour(
    sdword side, vector* norm, ubyte* color, real32* minv)
{
    sdword l;
    real32 sumR, sumG, sumB;
    real32 nx, ny, nz;

    if (side == 0)
    {
        nx = norm->x;
        ny = norm->y;
        nz = norm->z;
    }
    else
    {
        nx = -norm->x;
        ny = -norm->y;
        nz = -norm->z;
    }

    sumR = shBaseColor[0];
    sumG = shBaseColor[1];
    sumB = shBaseColor[2];

    for (l = 0; l < lightNumLights; l++)
    {
        real32 nDotVP = nx * shLight[l].inverseworldposition[0]
                      + ny * shLight[l].inverseworldposition[1]
                      + nz * shLight[l].inverseworldposition[2];
        if (nDotVP > 0.0f)
        {
            nDotVP *= 255.0f;
            sumR += nDotVP * shLight[l].matdiffuse[0];
            sumG += nDotVP * shLight[l].matdiffuse[1];
            sumB += nDotVP * shLight[l].matdiffuse[2];
        }
    }

#if SLOW_TO_INT
    color[0] = (ubyte)MIN2(sumR, 255.0f);
    color[1] = (ubyte)MIN2(sumG, 255.0f);
    color[2] = (ubyte)MIN2(sumB, 255.0f);
#else
    color[0] = FAST_TO_INT(MIN2(sumR, 255.0f));
    color[1] = FAST_TO_INT(MIN2(sumG, 255.0f));
    color[2] = FAST_TO_INT(MIN2(sumB, 255.0f));
#endif
    color[3] = shBaseAlpha;
}

/*-----------------------------------------------------------------------------
    Name        : shColourSet
    Description : shades a vertex according to the standard lighting model.
                  also informs the GL of the colour via glColor4ub
    Inputs      : side = 0 or 1 (front or back)
                  norm - normal at the vertex
                  color - where output colours go
                  minv - inverse modelview matrix
    Outputs     : current GL colour is updated
    Return      :
----------------------------------------------------------------------------*/
void shColourSet(
    sdword side, vector* norm, real32* minv)
{
    sdword l;
    real32 sumR, sumG, sumB;
    real32 nx, ny, nz;
    real32 nDotVP;

    if (side == 0)
    {
        nx = norm->x;
        ny = norm->y;
        nz = norm->z;
    }
    else
    {
        nx = -norm->x;
        ny = -norm->y;
        nz = -norm->z;
    }

    sumR = shBaseColor[0];
    sumG = shBaseColor[1];
    sumB = shBaseColor[2];

    for (l = 0; l < lightNumLights; l++)
    {
        nDotVP = nx * shLight[l].inverseworldposition[0]
               + ny * shLight[l].inverseworldposition[1]
               + nz * shLight[l].inverseworldposition[2];
        if (nDotVP > 0.0f)
        {
            nDotVP *= 255.0f;
            sumR += nDotVP * shLight[l].matdiffuse[0];
            sumG += nDotVP * shLight[l].matdiffuse[1];
            sumB += nDotVP * shLight[l].matdiffuse[2];
        }
    }

#if SLOW_TO_INT
    glColor4ub((ubyte)MIN2(sumR, 255.0f),
               (ubyte)MIN2(sumG, 255.0f),
               (ubyte)MIN2(sumB, 255.0f),
               (ubyte)shBaseAlpha);
#else
    glColor4ub((ubyte)FAST_TO_INT(MIN2(sumR, 255.0f)),
               (ubyte)FAST_TO_INT(MIN2(sumG, 255.0f)),
               (ubyte)FAST_TO_INT(MIN2(sumB, 255.0f)),
               (ubyte)shBaseAlpha);
#endif
}

/*-----------------------------------------------------------------------------
    Name        : shGrowBuffers
    Description : possibly grow the normal & colour lists
    Inputs      : n - number of vertices
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shGrowBuffers(sdword nVertices)
{
    if (normalList == NULL)
    {
        nVerts = nVertices;
        normalList = (hvector*)memAlloc(nVerts * sizeof(hvector), "sh normal list", NonVolatile);
        colorList = (shColor*)memAlloc(nVerts * sizeof(shColor), "sh color list", NonVolatile);
    }

    if (nVerts < nVertices)
    {
        nVerts = nVertices;
        normalList = (hvector*)memRealloc(normalList, nVerts * sizeof(hvector), "sh normal list", NonVolatile);
        colorList = (shColor*)memRealloc(colorList, nVerts * sizeof(shColor), "sh color list", NonVolatile);
    }
}

/*-----------------------------------------------------------------------------
    Name        : shShadeBuffer
    Description : applies shading to a list of normals
    Inputs      : nVertices - number of vertices (uh, normals)
                  source - input normals
    Outputs     : shColorList is filled
    Return      :
----------------------------------------------------------------------------*/
void shShadeBuffer(sdword nVertices, hvector* source)
{
    sdword i, l;
    real32 sumR, sumG, sumB;
    real32 nx, ny, nz;
    real32 nDotVP;
    real32 t;

    if (shDockFactor == 0.0f)
    {
        for (i = 0; i < nVertices; i++)
        {
            nx = (source + i)->x;
            ny = (source + i)->y;
            nz = (source + i)->z;

            sumR = shStdBaseColor[0];
            sumG = shStdBaseColor[1];
            sumB = shStdBaseColor[2];

            for (l = 0; l < lightNumLights; l++)
            {
                nDotVP = nx * shLight[l].inverseworldposition[0]
                       + ny * shLight[l].inverseworldposition[1]
                       + nz * shLight[l].inverseworldposition[2];
                if (nDotVP > 0.0f)
                {
                    nDotVP *= 255.0f;
                    sumR += nDotVP * shLight[l].stdmatdiffuse[0];
                    sumG += nDotVP * shLight[l].stdmatdiffuse[1];
                    sumB += nDotVP * shLight[l].stdmatdiffuse[2];
                }
            }

            colorList[i].c[0] = (ubyte)MIN2(sumR, 255.0f);
            colorList[i].c[1] = (ubyte)MIN2(sumG, 255.0f);
            colorList[i].c[2] = (ubyte)MIN2(sumB, 255.0f);
            colorList[i].c[3] = (ubyte)shBaseAlpha;
        }
    }
    else
    {
        t = 1.1f - shDockFactor;
        if (t > 1.0f)
        {
            t = 1.0f;
        }

        for (i = 0; i < nVertices; i++)
        {
            nx = (source + i)->x;
            ny = (source + i)->y;
            nz = (source + i)->z;

            sumR = shStdBaseColor[0];
            sumG = shStdBaseColor[1];
            sumB = shStdBaseColor[2];

            for (l = 0; l < lightNumLights; l++)
            {
                nDotVP = nx * shLight[l].inverseworldposition[0]
                       + ny * shLight[l].inverseworldposition[1]
                       + nz * shLight[l].inverseworldposition[2];
                if (nDotVP > 0.0f)
                {
                    nDotVP *= 255.0f;
                    sumR += nDotVP * shLight[l].stdmatdiffuse[0];
                    sumG += nDotVP * shLight[l].stdmatdiffuse[1];
                    sumB += nDotVP * shLight[l].stdmatdiffuse[2];
                }
            }

            sumR *= t;
            sumG *= t;
            sumB *= t;

            t = shDockFactor * 255.0f;

            sumR += t * shDockScalarRed;
            sumG += t * shDockScalarGreen;
            sumB += t * shDockScalarBlue;

            colorList[i].c[0] = (ubyte)MIN2(sumR, 255.0f);
            colorList[i].c[1] = (ubyte)MIN2(sumG, 255.0f);
            colorList[i].c[2] = (ubyte)MIN2(sumB, 255.0f);
            colorList[i].c[3] = (ubyte)shBaseAlpha;
        }
    }
}

void shColourSet0(vector* norm)
{
    sdword l;
    real32 sumR, sumG, sumB;
    real32 nx, ny, nz;
    real32 nDotVP;

    nx = norm->x;
    ny = norm->y;
    nz = norm->z;

    sumR = shBaseColor[0];
    sumG = shBaseColor[1];
    sumB = shBaseColor[2];

    for (l = 0; l < lightNumLights; l++)
    {
        nDotVP = nx * shLight[l].inverseworldposition[0]
               + ny * shLight[l].inverseworldposition[1]
               + nz * shLight[l].inverseworldposition[2];
        if (nDotVP > 0.0f)
        {
            nDotVP *= 255.0f;
            sumR += nDotVP * shLight[l].matdiffuse[0];
            sumG += nDotVP * shLight[l].matdiffuse[1];
            sumB += nDotVP * shLight[l].matdiffuse[2];
        }
    }

    if (shDockFactor != 0.0f)
    {
        //leave at least 10% of original light
        real32 t = 1.1f - shDockFactor;
        if (t > 1.0f)
        {
            t = 1.0f;
        }

        sumR *= t;
        sumG *= t;
        sumB *= t;

        t = shDockFactor * 255.0f;

        //add in dock light colour
        sumR += t * shDockScalarRed;
        sumG += t * shDockScalarGreen;
        sumB += t * shDockScalarBlue;
    }

#if SLOW_TO_INT
    glColor4ub((ubyte)MIN2(sumR, 255.0f),
               (ubyte)MIN2(sumG, 255.0f),
               (ubyte)MIN2(sumB, 255.0f),
               (ubyte)shBaseAlpha);
#else
    glColor4ub((ubyte)FAST_TO_INT(MIN2(sumR, 255.0f)),
               (ubyte)FAST_TO_INT(MIN2(sumG, 255.0f)),
               (ubyte)FAST_TO_INT(MIN2(sumB, 255.0f)),
               (ubyte)shBaseAlpha);
#endif
}

real32 gl_pow(real32 a, real32 b)
{
    return (real32)pow((double)a, (double)b);
}

/*-----------------------------------------------------------------------------
    Name        : shSpecularColour
    Description : shades a vertex according to the specular model in use
    Inputs      : specInd - [0..2] the index of the specular shader
                  side = 0 or 1
    Outputs     :
    Return      :
----------------------------------------------------------------------------*/
void shSpecularColour(
    sdword specInd, sdword side, vector* vobj, vector* norm, ubyte* color,
    real32* m, real32* minv)
{
    vector veye = {0.0f, 0.0f, 1.0f};
    vector xnorm, xvobj;
    real32 nx, ny, nz;
    real32 alpha, nDotVP;
    real32 fade;
    extern bool bFade;
    extern real32 meshFadeAlpha;

    fade = bFade ? meshFadeAlpha : 1.0f;

    shTransformNormal(&xnorm, norm, minv);

    if (side == 0)
    {
        nx = xnorm.x;
        ny = xnorm.y;
        nz = xnorm.z;
    }
    else
    {
        nx = -xnorm.x;
        ny = -xnorm.y;
        nz = -xnorm.z;
    }

    if (specInd == 0)
    {
        nDotVP = nz;
        if (nDotVP > 0.0f)
        {
            alpha = shPow(CLAMP(nDotVP, 0.0f, 1.0f), shSpecularExponent[specInd]);
        }
        else
        {
            alpha = 0.0f;
        }

#if SLOW_TO_INT
        color[3] = (ubyte)(fade * (real32)color[3] * CLAMP(alpha, 0.0f, 1.0f));
#else
        color[3] = (ubyte)FAST_TO_INT(fade * (real32)color[3] * CLAMP(alpha, 0.0f, 1.0f));
#endif
    }
    else if (specInd == 1)
    {
        vector vpInfNorm[2];
        real32 alpha0, alpha1;
        sdword l, c;

        memcpy(&vpInfNorm[0], shLight[0].position, sizeof(vector));
        memcpy(&vpInfNorm[1], shLight[1].position, sizeof(vector));

#if 0
        shTransformVertex(&xvobj, vobj, m);
        veye.x = xvobj.x;
        veye.y = xvobj.z;
        veye.z = xvobj.y;
        vecNormalize(&veye);
        nDotVP = nx * veye.x + ny * veye.y + nz * veye.z;
        alpha0 = gl_pow(CLAMP(nDotVP, 0.0f, 1.0f), 5.0f);
#endif

        alpha1 = 0.0f;
        for (l = 0; l < lightNumLights; l++)
        {
            nDotVP = nx * vpInfNorm[l].x
                   + ny * vpInfNorm[l].y
                   + nz * vpInfNorm[l].z;
            if (nDotVP > 0.0f)
            {
                alpha1 += shPow(nDotVP, shSpecularExponent[1]);
            }
        }

        alpha = 2.3f * alpha1;//(0.9f * alpha0) + (0.23f * alpha1);

        c = (sdword)(fade * color[3] * alpha);
        c = CLAMP(c, 0, 255);

        color[3] = (ubyte)c;
    }
    else/* if (specInd == 2)*/
    {
        shTransformVertex(&xvobj, vobj, m);
        veye.x = xvobj.x;
        veye.y = xvobj.y;
        veye.z = xvobj.z;
        vecNormalize(&veye);

        nDotVP = nx * veye.x + ny * veye.y + nz * veye.z;
        if (nDotVP < 0.0f)
        {
            nDotVP = -nDotVP;
        }
        if (nDotVP > 0.0f)
        {
            alpha = shPow(CLAMP(nDotVP, 0.0f, 1.0f), shSpecularExponent[2]);
        }
        else
        {
            alpha = 0.0f;
        }

#if SLOW_TO_INT
        color[1] = (ubyte)((real32)color[1] * CLAMP(alpha, 0.0f, 0.92f));
        color[3] = (ubyte)(fade * (real32)color[3] * CLAMP(alpha, 0.0f, 1.0f));
#else
        color[1] = (ubyte)FAST_TO_INT((real32)color[1] * CLAMP(alpha, 0.0f, 0.92f));
        color[3] = (ubyte)FAST_TO_INT(fade * (real32)color[3] * CLAMP(alpha, 0.0f, 1.0f));
#endif
    }
}

/*-----------------------------------------------------------------------------
    Name        : shInvertMatrixGeneral
    Description : matrix inverter, general case
    Inputs      : out - output matrix
                  m - input matrix
    Outputs     : out contains inverse
    Return      :
----------------------------------------------------------------------------*/
static void shInvertMatrixGeneral(real32* out, real32 const* m)
{
    Mat2 r1, r2, r3, r4, r5, r6, r7;
    real32 const* A = m;
    real32* C = out;
    real32 one_over_det;

    /*
     * A is the 4x4 source matrix (to be inverted).
     * C is the 4x4 destination matrix
     * a11 is the 2x2 matrix in the upper left quadrant of A
     * a12 is the 2x2 matrix in the upper right quadrant of A
     * a21 is the 2x2 matrix in the lower left quadrant of A
     * a22 is the 2x2 matrix in the lower right quadrant of A
     * similarly, cXX are the 2x2 quadrants of the destination matrix
     */

    /* R1 = inverse( a11 ) */
    one_over_det = 1.0f / ((A[M00] * A[M11]) - (A[M10] * A[M01]));
    r1[0][0] = one_over_det * A[M11];
    r1[0][1] = one_over_det * -A[M01];
    r1[1][0] = one_over_det * -A[M10];
    r1[1][1] = one_over_det * A[M00];

    /* R2 = a21 x R1 */
    r2[0][0] = A[M20] * r1[0][0] + A[M21] * r1[1][0];
    r2[0][1] = A[M20] * r1[0][1] + A[M21] * r1[1][1];
    r2[1][0] = A[M30] * r1[0][0] + A[M31] * r1[1][0];
    r2[1][1] = A[M30] * r1[0][1] + A[M31] * r1[1][1];

    /* R3 = R1 x a12 */
    r3[0][0] = r1[0][0] * A[M02] + r1[0][1] * A[M12];
    r3[0][1] = r1[0][0] * A[M03] + r1[0][1] * A[M13];
    r3[1][0] = r1[1][0] * A[M02] + r1[1][1] * A[M12];
    r3[1][1] = r1[1][0] * A[M03] + r1[1][1] * A[M13];

    /* R4 = a21 x R3 */
    r4[0][0] = A[M20] * r3[0][0] + A[M21] * r3[1][0];
    r4[0][1] = A[M20] * r3[0][1] + A[M21] * r3[1][1];
    r4[1][0] = A[M30] * r3[0][0] + A[M31] * r3[1][0];
    r4[1][1] = A[M30] * r3[0][1] + A[M31] * r3[1][1];

    /* R5 = R4 - a22 */
    r5[0][0] = r4[0][0] - A[M22];
    r5[0][1] = r4[0][1] - A[M23];
    r5[1][0] = r4[1][0] - A[M32];
    r5[1][1] = r4[1][1] - A[M33];

    /* R6 = inverse( R5 ) */
    one_over_det = 1.0f / ((r5[0][0] * r5[1][1]) - (r5[1][0] * r5[0][1]));
    r6[0][0] = one_over_det * r5[1][1];
    r6[0][1] = one_over_det * -r5[0][1];
    r6[1][0] = one_over_det * -r5[1][0];
    r6[1][1] = one_over_det * r5[0][0];

    /* c12 = R3 x R6 */
    C[M02] = r3[0][0] * r6[0][0] + r3[0][1] * r6[1][0];
    C[M03] = r3[0][0] * r6[0][1] + r3[0][1] * r6[1][1];
    C[M12] = r3[1][0] * r6[0][0] + r3[1][1] * r6[1][0];
    C[M13] = r3[1][0] * r6[0][1] + r3[1][1] * r6[1][1];

    /* c21 = R6 x R2 */
    C[M20] = r6[0][0] * r2[0][0] + r6[0][1] * r2[1][0];
    C[M21] = r6[0][0] * r2[0][1] + r6[0][1] * r2[1][1];
    C[M30] = r6[1][0] * r2[0][0] + r6[1][1] * r2[1][0];
    C[M31] = r6[1][0] * r2[0][1] + r6[1][1] * r2[1][1];

    /* R7 = R3 x c21 */
    r7[0][0] = r3[0][0] * C[M20] + r3[0][1] * C[M30];
    r7[0][1] = r3[0][0] * C[M21] + r3[0][1] * C[M31];
    r7[1][0] = r3[1][0] * C[M20] + r3[1][1] * C[M30];
    r7[1][1] = r3[1][0] * C[M21] + r3[1][1] * C[M31];

    /* c11 = R1 - R7 */
    C[M00] = r1[0][0] - r7[0][0];
    C[M01] = r1[0][1] - r7[0][1];
    C[M10] = r1[1][0] - r7[1][0];
    C[M11] = r1[1][1] - r7[1][1];

    /* c22 = -R6 */
    C[M22] = -r6[0][0];
    C[M23] = -r6[0][1];
    C[M32] = -r6[1][0];
    C[M33] = -r6[1][1];
}

/*-----------------------------------------------------------------------------
    Name        : shInvertMatrix
    Description : invert a matrix, possibly by using general matrix inverter
    Inputs      : out - output matrix
                  m - input matrix
    Outputs     : out contains inverse of m
    Return      :
----------------------------------------------------------------------------*/
void shInvertMatrix(real32* out, real32 const* m)
{
#define MAT(m,r,c) (m)[((c)<<2)+(r)]

#define m11 MAT(m,0,0)
#define m12 MAT(m,0,1)
#define m13 MAT(m,0,2)
#define m14 MAT(m,0,3)
#define m21 MAT(m,1,0)
#define m22 MAT(m,1,1)
#define m23 MAT(m,1,2)
#define m24 MAT(m,1,3)
#define m31 MAT(m,2,0)
#define m32 MAT(m,2,1)
#define m33 MAT(m,2,2)
#define m34 MAT(m,2,3)
#define m41 MAT(m,3,0)
#define m42 MAT(m,3,1)
#define m43 MAT(m,3,2)
#define m44 MAT(m,3,3)

    real32 det;
    real32 tmp[16];    /* allow out == in */

    if (m41 != 0.0f || m42 != 0.0f || m43 != 0.0f || m44 != 1.0f)
    {
        shInvertMatrixGeneral(out, m);
        return;
    }

    /* Inverse = adjoint / det */

    tmp[0] = m22 * m33 - m23 * m32;
    tmp[1] = m23 * m31 - m21 * m33;
    tmp[2] = m21 * m32 - m22 * m31;

    /* compute determinant using cofactors */
    det = m11 * tmp[0] + m12 * tmp[1] + m13 * tmp[2];

    /* singularity test */
    if (det == 0.0f)
    {
        memcpy(out, shIdentityMatrix, 16*sizeof(real32));
    }
    else
    {
        real32 d12, d13, d23, d24, d34, d41;
        real32 im11, im12, im13, im14;

        det= 1.0f / det;

        /* compute rest of inverse */
        tmp[0] *= det;
        tmp[1] *= det;
        tmp[2] *= det;
        tmp[3]  = 0.0f;

        im11 = m11 * det;
        im12 = m12 * det;
        im13 = m13 * det;
        im14 = m14 * det;
        tmp[4] = im13 * m32 - im12 * m33;
        tmp[5] = im11 * m33 - im13 * m31;
        tmp[6] = im12 * m31 - im11 * m32;
        tmp[7] = 0.0f;

        /* Pre-compute 2x2 dets for first two rows when computing */
        /* cofactors of last two rows. */
        d12 = im11*m22 - m21*im12;
        d13 = im11*m23 - m21*im13;
        d23 = im12*m23 - m22*im13;
        d24 = im12*m24 - m22*im14;
        d34 = im13*m24 - m23*im14;
        d41 = im14*m21 - m24*im11;

        tmp[8] =  d23;
        tmp[9] = -d13;
        tmp[10] = d12;
        tmp[11] = 0.0f;

        tmp[12] = -(m32 * d34 - m33 * d24 + m34 * d23);
        tmp[13] =  (m31 * d34 + m33 * d41 + m34 * d13);
        tmp[14] = -(m31 * d24 + m32 * d41 + m34 * d12);
        tmp[15] =  1.0f;

        memcpy(out, tmp, 16*sizeof(real32));
    }
#undef m11
#undef m12
#undef m13
#undef m14
#undef m21
#undef m22
#undef m23
#undef m24
#undef m31
#undef m32
#undef m33
#undef m34
#undef m41
#undef m42
#undef m43
#undef m44
#undef MAT
}

#define TRANSFORM_POINT(Q, M, P) \
    Q[0] = M[0] * P[0] + M[4] * P[1] + M[8]  * P[2] + M[12] * P[3]; \
    Q[1] = M[1] * P[0] + M[5] * P[1] + M[9]  * P[2] + M[13] * P[3]; \
    Q[2] = M[2] * P[0] + M[6] * P[1] + M[10] * P[2] + M[14] * P[3]; \
    Q[3] = M[3] * P[0] + M[7] * P[1] + M[11] * P[2] + M[15] * P[3];

/*-----------------------------------------------------------------------------
    Name        : shSetLightPosition
    Description : maintains knowledge of a GL's light positions
    Inputs      : index - [0..1], the light whose position is being updated
                  position - the position
                  m - the matrix to transform by
    Outputs     : shLight[index].position will contain the transformed light position
    Return      :
----------------------------------------------------------------------------*/
void shSetLightPosition(sdword index, real32* position, real32* m)
{
real32 xposition[4];

    dbgAssert(index >= 0 && index < 2);

    TRANSFORM_POINT(xposition, m, position);
    vecNormalize((vector*)&xposition);
    shLight[index].position[0] = xposition[0];
    shLight[index].position[1] = xposition[1];
    shLight[index].position[2] = xposition[2];
    shLight[index].position[3] = xposition[3];

	shLight[index].worldposition[0] = position[0];
	shLight[index].worldposition[1] = position[1];
	shLight[index].worldposition[2] = position[2];
	shLight[index].worldposition[3] = position[3];
    vecNormalize((vector *)&shLight[index].worldposition);
}

#define matrixdot(x1,x2,x3,y1,y2,y3) \
    ( ((x1)*(y1)) + ((x2)*(y2)) + ((x3)*(y3)) )

void shMultiplyHVecByHMatRotInv(hvector *result, hvector *vector, hmatrix *matrix)
{
    result->x = matrixdot(vector->x,vector->y,vector->z,matrix->m11,matrix->m21,matrix->m31);
    result->y = matrixdot(vector->x,vector->y,vector->z,matrix->m12,matrix->m22,matrix->m32);
    result->z = matrixdot(vector->x,vector->y,vector->z,matrix->m13,matrix->m23,matrix->m33);
}

static void shUpdateLightsFromMatrix(hmatrix *pMat)
{
long	i;

	for(i=0; i<2; i++)
	{
		shMultiplyHVecByHMatRotInv(
			(hvector *)shLight[i].inverseworldposition,
			(hvector *)shLight[i].worldposition,
			pMat);
	}
}

static void shMultiplyHMatByHMatRot(hmatrix *result, hmatrix *first, hmatrix *second)
{
	result->m11 = matrixdot(first->m11,first->m12,first->m13,second->m11,second->m21,second->m31);
	result->m12 = matrixdot(first->m11,first->m12,first->m13,second->m12,second->m22,second->m32);
	result->m13 = matrixdot(first->m11,first->m12,first->m13,second->m13,second->m23,second->m33);

	result->m21 = matrixdot(first->m21,first->m22,first->m23,second->m11,second->m21,second->m31);
	result->m22 = matrixdot(first->m21,first->m22,first->m23,second->m12,second->m22,second->m32);
	result->m23 = matrixdot(first->m21,first->m22,first->m23,second->m13,second->m23,second->m33);

	result->m31 = matrixdot(first->m31,first->m32,first->m33,second->m11,second->m21,second->m31);
	result->m32 = matrixdot(first->m31,first->m32,first->m33,second->m12,second->m22,second->m32);
	result->m33 = matrixdot(first->m31,first->m32,first->m33,second->m13,second->m23,second->m33);
}


void shPushLightMatrix(hmatrix *pMat)
{
hmatrix	*pCurMat, *pNewMat;

	pCurMat = &shLightMatStack[shLightMatStackPos];
	shLightMatStackPos++;

	dbgAssert(shLightMatStackPos < 10);

	pNewMat = pCurMat+1;

	shMultiplyHMatByHMatRot(pNewMat, pCurMat, pMat);

	shUpdateLightsFromMatrix(pNewMat);
}

void shPopLightMatrix(void)
{
	shLightMatStackPos--;

	dbgAssert(shLightMatStackPos >= 0);

	shUpdateLightsFromMatrix(&shLightMatStack[shLightMatStackPos]);
}

