#include "pch.h"
#include "engine.h"
#include "rendertarget.h"

VARP(shadowmap, 0, 0, 1);

extern void cleanshadowmap();
VARFP(shadowmapsize, 7, 9, 11, cleanshadowmap());
VARP(shadowmapradius, 64, 96, 256);
VAR(shadowmapheight, 0, 32, 128);
VARP(shadowmapdist, 128, 256, 512);
VARFP(fpshadowmap, 0, 0, 1, cleanshadowmap());
VARFP(shadowmapprecision, 0, 0, 1, cleanshadowmap());
VARR(shadowmapambient, 0, 0, 0xFFFFFF);
VARP(shadowmapintensity, 0, 40, 100);

VARP(blurshadowmap, 0, 1, 3);
VARP(blursmsigma, 1, 100, 200);

#define SHADOWSKEW 0.7071068f

vec shadowoffset(0, 0, 0), shadowfocus(0, 0, 0), shadowdir(0, SHADOWSKEW, 1);
VAR(shadowmapcasters, 1, 0, 0);
float shadowmapmaxz = 0;

void setshadowdir(int angle)
{
    shadowdir = vec(0, SHADOWSKEW, 1);
    shadowdir.rotate_around_z(angle*RAD);
}

VARFR(shadowmapangle, 0, 0, 360, setshadowdir(shadowmapangle));

void guessshadowdir()
{
    if(shadowmapangle) return;
    vec lightpos(0, 0, 0), casterpos(0, 0, 0);
    int numlights = 0, numcasters = 0;
    const vector<extentity *> &ents = et->getents();
    loopv(ents)
    {
        extentity &e = *ents[i];
        switch(e.type)
        {
            case ET_LIGHT:
                if(!e.attr1) { lightpos.add(e.o); numlights++; }
                break;

             case ET_MAPMODEL:
                casterpos.add(e.o);
                numcasters++;
                break;

             default:
                if(e.type<ET_GAMESPECIFIC) break;
                casterpos.add(e.o);
                numcasters++;
                break;
         }
    }
    if(!numlights || !numcasters) return;
    lightpos.div(numlights);
    casterpos.div(numcasters);
    vec dir(lightpos);
    dir.sub(casterpos);
    dir.z = 0;
    if(dir.iszero()) return;
    dir.normalize();
    dir.mul(SHADOWSKEW);
    dir.z = 1;
    shadowdir = dir;
}

bool shadowmapping = false;

static glmatrixf shadowmapmatrix;

VARP(shadowmapbias, 0, 5, 1024);
VARP(shadowmappeelbias, 0, 20, 1024);
VAR(smdepthpeel, 0, 1, 1);
VAR(smoothshadowmappeel, 1, 0, 0);

static struct shadowmaptexture : rendertarget
{
    GLenum attachment() const
    {
        return renderpath==R_FIXEDFUNCTION ? GL_DEPTH_ATTACHMENT_EXT : GL_COLOR_ATTACHMENT0_EXT;
    }

    const GLenum *colorformats() const
    {
        if(renderpath==R_FIXEDFUNCTION) 
        {
            static const GLenum depthtexfmts[] = { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT32, GL_FALSE };
            return depthtexfmts;
        }
        
        static const GLenum colorfmts[] = { GL_RGBA16F_ARB, GL_RGBA16, GL_RGBA, GL_RGBA8, GL_FALSE };
        int offset = fpshadowmap && hasTF && hasFBO ? 0 : (shadowmapprecision && hasFBO ? 1 : 2);
        return &colorfmts[offset];
    }

    bool filter() const { return renderpath!=R_FIXEDFUNCTION || hasNVPCF; }
    bool swaptexs() const { return renderpath!=R_FIXEDFUNCTION; }

    bool scissorblur(int &x, int &y, int &w, int &h)
    {
        x = max(int(floor((scissorx1+1)/2*vieww)) - 2*blursize, 2);
        y = max(int(floor((scissory1+1)/2*viewh)) - 2*blursize, 2);
        w = min(int(ceil((scissorx2+1)/2*vieww)) + 2*blursize, vieww-2) - x;
        h = min(int(ceil((scissory2+1)/2*viewh)) + 2*blursize, viewh-2) - y;
        return true;
    }

    bool scissorrender(int &x, int &y, int &w, int &h)
    {
        x = y = 2;
        w = vieww - 2*2;
        h = viewh - 2*2;
        return true;
    }

    void doclear()
    {
        if(!hasFBO && rtscissor)
        {
            glEnable(GL_SCISSOR_TEST);
            glScissor(screen->w-vieww, screen->h-viewh, vieww, viewh);
        }
        glClearColor(0, 0, 0, 0);
        glClear(GL_DEPTH_BUFFER_BIT | (renderpath!=R_FIXEDFUNCTION ? GL_COLOR_BUFFER_BIT : 0));
        if(!hasFBO && rtscissor) glDisable(GL_SCISSOR_TEST);
    }

    bool dorender()
    {
        // nvidia bug, must push modelview here, then switch to projection, then back to modelview before can safely modify it
        glPushMatrix();

        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        glOrtho(-shadowmapradius, shadowmapradius, -shadowmapradius, shadowmapradius, -shadowmapdist, shadowmapdist);

        glMatrixMode(GL_MODELVIEW);

        vec skewdir(shadowdir);
        skewdir.neg();
        skewdir.rotate_around_z(-camera1->yaw*RAD);

        vec dir;
        vecfromyawpitch(camera1->yaw, camera1->pitch, 1, 0, dir);
        dir.z = 0;
        dir.mul(shadowmapradius);

        vec dirx, diry;
        vecfromyawpitch(camera1->yaw, 0, 0, 1, dirx);
        vecfromyawpitch(camera1->yaw, 0, 1, 0, diry);
        shadowoffset.x = -fmod(dirx.dot(camera1->o) - skewdir.x*camera1->o.z, 2.0f*shadowmapradius/vieww);
        shadowoffset.y = -fmod(diry.dot(camera1->o) - skewdir.y*camera1->o.z, 2.0f*shadowmapradius/viewh);

        GLfloat skew[] =
        {
            1, 0, 0, 0,
            0, 1, 0, 0,
            skewdir.x, skewdir.y, 1, 0,
            0, 0, 0, 1
        };
        glLoadMatrixf(skew);
        glTranslatef(skewdir.x*shadowmapheight + shadowoffset.x, skewdir.y*shadowmapheight + shadowoffset.y + dir.magnitude(), -shadowmapheight);
        glRotatef(camera1->yaw, 0, 0, -1);
        glTranslatef(-camera1->o.x, -camera1->o.y, -camera1->o.z);
        shadowfocus = camera1->o;
        shadowfocus.add(dir);
        shadowfocus.add(vec(shadowdir).mul(shadowmapheight));
        shadowfocus.add(dirx.mul(shadowoffset.x));
        shadowfocus.add(diry.mul(shadowoffset.y));

        glmatrixf proj, mv;
        glGetFloatv(GL_PROJECTION_MATRIX, proj.v);
        glGetFloatv(GL_MODELVIEW_MATRIX, mv.v);
        shadowmapmatrix.mul(proj, mv);
        if(renderpath==R_FIXEDFUNCTION) shadowmapmatrix.projective();
        else shadowmapmatrix.projective(-1, 1-shadowmapbias/float(shadowmapdist));

        glColor3f(0, 0, 0);
        glDisable(GL_TEXTURE_2D);

        if(renderpath!=R_FIXEDFUNCTION) setenvparamf("shadowmapbias", SHPARAM_VERTEX, 0, -shadowmapbias/float(shadowmapdist), 1 - (shadowmapbias + (smoothshadowmappeel ? 0 : shadowmappeelbias))/float(shadowmapdist));
        else glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

        shadowmapcasters = 0;
        shadowmapmaxz = shadowfocus.z - shadowmapdist;
        shadowmapping = true;
        rendergame();
        shadowmapping = false;
        shadowmapmaxz = min(shadowmapmaxz, shadowfocus.z);

        glEnable(GL_TEXTURE_2D);

        if(renderpath==R_FIXEDFUNCTION) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        else if(shadowmapcasters && smdepthpeel) 
        {
            int sx, sy, sw, sh;
            bool scissoring = rtscissor && scissorblur(sx, sy, sw, sh) && sw > 0 && sh > 0;
            if(scissoring) 
            {
                if(!hasFBO)
                {
                    sx += screen->w-vieww;
                    sy += screen->h-viewh;
                }
                glScissor(sx, sy, sw, sh);
            }
            if(!rtscissor || scissoring) rendershadowmapreceivers();
        }

        glMatrixMode(GL_PROJECTION);
        glPopMatrix();

        glMatrixMode(GL_MODELVIEW);
        glPopMatrix();

        return shadowmapcasters>0;
    }

    bool flipdebug() const { return false; }

    void dodebug(int w, int h)
    {
        if(shadowmapcasters)
        {
            glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
            debugscissor(w, h);
            glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
            debugblurtiles(w, h);
            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        }
    }
} shadowmaptex;

void cleanshadowmap()
{
    shadowmaptex.cleanup(true);
}

void vv(vec v)
{
    glVertex3fv(v.v);
}

#define VV(off) \
    vv(vec(shadowfocus).off); \
    vv(vec(bottom).off);

VAR(sci, 0, 0, 1);
VAR(scx, -1, 1, 1);
VAR(scy, -1, 1, 1);

static int scissoring = 0, oldscissor[4];

static int calcscissorbox(int &sx, int &sy, int &sw, int &sh, bool debug = false)
{
    scissoring = 0;

    int smx, smy, smw, smh;
    shadowmaptex.scissorblur(smx, smy, smw, smh);

    vec forward, right;
    vecfromyawpitch(camera1->yaw, 0, -1, 0, forward);
    vecfromyawpitch(camera1->yaw, 0, 0, -1, right);
    forward.mul(shadowmapradius*2.0f/shadowmaptex.viewh);
    right.mul(shadowmapradius*2.0f/shadowmaptex.vieww);

    vec bottom(shadowfocus);
    bottom.sub(vec(shadowdir).mul(shadowmapdist));
    bottom.add(vec(forward).mul(smy - shadowmaptex.viewh/2)).add(vec(right).mul(smx - shadowmaptex.vieww/2));
    vec top(bottom);
    top.add(vec(shadowdir).mul(shadowmapmaxz - (shadowfocus.z - shadowmapdist)));
   
    vec4 v[8];
    float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
    loopi(8)
    {
        vec c = i&4 ? top : bottom;
        if(i&1) c.add(vec(right).mul(smw));
        if(i&2) c.add(vec(forward).mul(smh));
        vec4 &p = v[i];
        mvpmatrix.transform(c, p);
        if(p.z >= 0)
        {
            float x = p.x / p.w, y = p.y / p.w;
            sx1 = min(sx1, x);
            sy1 = min(sy1, y);
            sx2 = max(sx2, x);
            sy2 = max(sy2, y);
        }
    }
    if(sx1 >= sx2 || sy1 >= sy2) return 0;
    loopi(8)
    {
        const vec4 &p = v[i];
        if(p.z >= 0) continue;
        loopj(3)
        {
            const vec4 &o = v[i^(1<<j)];
            if(o.z <= 0) continue;
            float t = p.z/(p.z - o.z),
                  w = p.w + t*(o.w - p.w),
                  x = (p.x + t*(o.x - p.x))/w,
                  y = (p.y + t*(o.y - p.y))/w;
            sx1 = min(sx1, x);
            sy1 = min(sy1, y);
            sx2 = max(sx2, x);
            sy2 = max(sy2, y);
        }
    }
    sx1 = max(sx1, -1.0f);
    sy1 = max(sy1, -1.0f);
    sx2 = min(sx2, 1.0f);
    sy2 = min(sy2, 1.0f);
    if(sx1 <= -1 && sy1 <= -1 && sx2 >= 1 && sy2 >= 1) return 0;

    int viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    sx = viewport[0] + int(floor((sx1+1)*0.5f*viewport[2]));
    sy = viewport[1] + int(floor((sy1+1)*0.5f*viewport[3]));
    sw = viewport[0] + int(ceil((sx2+1)*0.5f*viewport[2])) - sx;
    sh = viewport[1] + int(ceil((sy2+1)*0.5f*viewport[3])) - sy;
    if(sw <= 0 || sh <= 0) return 0;

    if(!debug && glIsEnabled(GL_SCISSOR_TEST))
    {
        glGetIntegerv(GL_SCISSOR_BOX, oldscissor);
        sw += sx;
        sh += sy;
        sx = max(sx, oldscissor[0]);
        sy = max(sy, oldscissor[1]);
        sw = min(sw, oldscissor[0] + oldscissor[2]) - sx;
        sh = min(sh, oldscissor[1] + oldscissor[3]) - sy;
        if(sw <= 0 || sh <= 0) return 0;
        scissoring = 2;
    }
    else scissoring = 1;

if(debug) {
    glDisable(GL_DEPTH_TEST);

    glPushMatrix();
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(-1, 1, -1, 1, -1, 1);
    
    glBegin(GL_LINE_LOOP);
    glVertex2f(sx1, sy1);
    glVertex2f(sx2, sy1);
    glVertex2f(sx2, sy2);
    glVertex2f(sx1, sy2);
    glEnd();

    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glEnable(GL_DEPTH_TEST);
}

    return scissoring;
}

VAR(dbgsmfrust, 0, 0, 1);

void drawshadowbox()
{
    if(!dbgsmfrust) return;

    vec bottom(shadowfocus), top(shadowfocus);
    bottom.sub(vec(shadowdir).mul(shadowmapdist));
    top.sub(vec(shadowdir).mul(min(float(shadowmapdist), shadowfocus.z - shadowmapmaxz)));

    notextureshader->set();
    glDisable(GL_TEXTURE_2D);
    glDepthMask(GL_FALSE);
   
    vec f, r;
    vecfromyawpitch(camera1->yaw, 0, -1, 0, f);
    vecfromyawpitch(camera1->yaw, 0, 0, -1, r);
    f.mul(shadowmapradius);
    r.mul(shadowmapradius);

    vec o00, o01, o10, o11;
    if(sci)
    {
        o00 = vec(r).mul(scx*shadowmaptex.scissorx1).add(vec(f).mul(scy*shadowmaptex.scissory1));
        o01 = vec(r).mul(scx*shadowmaptex.scissorx1).add(vec(f).mul(scy*shadowmaptex.scissory2));
        o10 = vec(r).mul(scx*shadowmaptex.scissorx2).add(vec(f).mul(scy*shadowmaptex.scissory1));
        o11 = vec(r).mul(scx*shadowmaptex.scissorx2).add(vec(f).mul(scy*shadowmaptex.scissory2));
    }
    else
    {
        o00 = vec(f).mul(-1).add(vec(r).mul(-1));
        o01 = vec(f).mul(-1).add(vec(r).mul(1));
        o10 = vec(f).mul(1).add(vec(r).mul(-1));
        o11 = vec(f).mul(1).add(vec(r).mul(1));
    }

    glColor3f(1, 0, 1);

    glBegin(GL_LINE_LOOP);
    vv(vec(top).add(o00));
    vv(vec(top).add(o10));
    vv(vec(top).add(o11));
    vv(vec(top).add(o01));
    glEnd();

    glColor3f(1, 1, 0);

    glBegin(GL_LINE_LOOP);
    vv(vec(shadowfocus).add(o00));
    vv(vec(shadowfocus).add(o10));
    vv(vec(shadowfocus).add(o11));
    vv(vec(shadowfocus).add(o01));
    glEnd();

    glBegin(GL_LINE_LOOP);
    vv(vec(bottom).add(o00));
    vv(vec(bottom).add(o10));
    vv(vec(bottom).add(o11));
    vv(vec(bottom).add(o01));
    glEnd();

    vec ground(shadowfocus);
    ground.sub(vec(shadowdir).mul(shadowfocus.z - camera1->o.z));
    ground.z -= 64;

    glBegin(GL_LINE_LOOP);
    vv(vec(ground).add(o00));
    vv(vec(ground).add(o10));
    vv(vec(ground).add(o11));
    vv(vec(ground).add(o01));
    glEnd();

    glBegin(GL_LINES);
    VV(add(o00));
    VV(add(o10));
    VV(add(o11));
    VV(add(o01));
    glEnd();

    glColor3f(0, 0, 1);
    int sx, sy, sw, sh;
    calcscissorbox(sx, sy, sw, sh, true);

    glDepthMask(GL_TRUE);
    glEnable(GL_TEXTURE_2D);
    defaultshader->set();
}

void calcshadowmapbb(const vec &o, float xyrad, float zrad, float &x1, float &y1, float &x2, float &y2)
{
    vec skewdir(shadowdir);
    skewdir.neg();
    skewdir.rotate_around_z(-camera1->yaw*RAD);

    vec ro(o);
    ro.sub(camera1->o);
    ro.rotate_around_z(-camera1->yaw*RAD);
    ro.x += ro.z * skewdir.x + shadowoffset.x;
    ro.y += ro.z * skewdir.y + shadowmapradius * cosf(camera1->pitch*RAD) + shadowoffset.y;

    vec high(ro), low(ro);
    high.x += zrad * skewdir.x;
    high.y += zrad * skewdir.y;
    low.x -= zrad * skewdir.x;
    low.y -= zrad * skewdir.y;

    x1 = (min(high.x, low.x) - xyrad) / shadowmapradius;
    y1 = (min(high.y, low.y) - xyrad) / shadowmapradius;
    x2 = (max(high.x, low.x) + xyrad) / shadowmapradius;
    y2 = (max(high.y, low.y) + xyrad) / shadowmapradius;
}

bool addshadowmapcaster(const vec &o, float xyrad, float zrad)
{
    if(o.z + zrad <= shadowfocus.z - shadowmapdist || o.z - zrad >= shadowfocus.z) return false;

    shadowmapmaxz = max(shadowmapmaxz, o.z + zrad);

    float x1, y1, x2, y2;
    calcshadowmapbb(o, xyrad, zrad, x1, y1, x2, y2);

    if(!shadowmaptex.addblurtiles(x1, y1, x2, y2, 2)) return false;

    shadowmapcasters++;
    return true;
}

bool isshadowmapreceiver(vtxarray *va)
{
    if(!shadowmap || !shadowmapcasters) return false;

    if(va->shadowmapmax.z <= shadowfocus.z - shadowmapdist || va->shadowmapmin.z >= shadowfocus.z) return false;

    float xyrad = SQRT2*0.5f*max(va->shadowmapmax.x-va->shadowmapmin.x, va->shadowmapmax.y-va->shadowmapmin.y),
          zrad = 0.5f*(va->shadowmapmax.z-va->shadowmapmin.z),
          x1, y1, x2, y2;
    if(xyrad<0 || zrad<0) return false;

    vec center(va->shadowmapmin.tovec());
    center.add(va->shadowmapmax.tovec()).mul(0.5f);
    calcshadowmapbb(center, xyrad, zrad, x1, y1, x2, y2);

    return shadowmaptex.checkblurtiles(x1, y1, x2, y2, 2);

#if 0
    // cheaper inexact test
    float dz = va->o.z + va->size/2 - shadowfocus.z;
    float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y;
    float skew = va->size/2*SHADOWSKEW;
    if(!shadowmap || !shadowmaptex ||
       va->o.z + va->size <= shadowfocus.z - shadowmapdist || va->o.z >= shadowfocus.z ||
       va->o.x + va->size <= cx - shadowmapradius-skew || va->o.x >= cx + shadowmapradius+skew || 
       va->o.y + va->size <= cy - shadowmapradius-skew || va->o.y >= cy + shadowmapradius+skew) 
        return false;
    return true;
#endif
}

bool isshadowmapcaster(const vec &o, float rad)
{
    // cheaper inexact test
    float dz = o.z - shadowfocus.z;
    float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y;
    float skew = rad*SHADOWSKEW;
    if(!shadowmapping ||
       o.z + rad <= shadowfocus.z - shadowmapdist || o.z - rad >= shadowfocus.z ||
       o.x + rad <= cx - shadowmapradius-skew || o.x - rad >= cx + shadowmapradius+skew ||
       o.y + rad <= cy - shadowmapradius-skew || o.y - rad >= cy + shadowmapradius+skew)
        return false;
    return true;
}

VAR(scitest, 0, 0, 1);

void pushshadowmap()
{
    if(!shadowmap || !shadowmaptex.rendertex) return;

    if(renderpath==R_FIXEDFUNCTION)
    {
        static GLfloat texgenS[4] = { 1, 0, 0, 0 },
                       texgenT[4] = { 0, 1, 0, 0 },
                       texgenR[4] = { 0, 0, 1, 0 };

        glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex);

        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS);
        glEnable(GL_TEXTURE_GEN_S);

        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT);
        glEnable(GL_TEXTURE_GEN_T);

        glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_R, GL_OBJECT_PLANE, texgenR);
        glEnable(GL_TEXTURE_GEN_R);

        // intel driver bug workaround: when R texgen is enabled, it uses the value of Q, even if not enabled!
        // MUST set Q with glTexCoord4f, glTexCoord3f does not work
        glTexCoord4f(0, 0, 0, 1);

        if(hasDT && hasSH)
        {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL);
            glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);
        }
        else
        {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_SGIX, GL_TRUE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_OPERATOR_SGIX, GL_TEXTURE_GEQUAL_R_SGIX);
        }

        glColor3f(shadowmapintensity/100.0f, shadowmapintensity/100.0f, shadowmapintensity/100.0f);

        int sx, sy, sw, sh;
        if(scitest && calcscissorbox(sx, sy, sw, sh))
        {
            glScissor(sx, sy, sw, sh);
            if(scissoring<=1) glEnable(GL_SCISSOR_TEST);
        }
        return;
    }

    glActiveTexture_(GL_TEXTURE7_ARB);
    glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex);

    glActiveTexture_(GL_TEXTURE2_ARB);
    glMatrixMode(GL_TEXTURE);
    glLoadMatrixf(shadowmapmatrix.v);
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);

    glActiveTexture_(GL_TEXTURE0_ARB);
    glClientActiveTexture_(GL_TEXTURE0_ARB);

    float r, g, b;
	if(!shadowmapambient)
	{
		if(hdr.skylight[0] || hdr.skylight[1] || hdr.skylight[2])
		{
			r = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[0]));
			g = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[1]));
			b = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[2]));
		}
		else r = g = b = max(25.0f, 2.0f*hdr.ambient);
	}
    else if(shadowmapambient<=255) r = g = b = shadowmapambient;
    else { r = (shadowmapambient>>16)&0xFF; g = (shadowmapambient>>8)&0xFF; b = shadowmapambient&0xFF; }
    setenvparamf("shadowmapambient", SHPARAM_PIXEL, 7, r/255.0f, g/255.0f, b/255.0f);
}

void adjustshadowmatrix(const ivec &o, float scale)
{
    if(!shadowmap || !shadowmaptex.rendertex) return;

    if(renderpath==R_FIXEDFUNCTION)
    {
        const GLfloat *v = shadowmapmatrix.v;
        GLfloat texgenS[4] = { v[0]*scale, v[4]*scale, v[8]*scale, v[0]*o.x + v[4]*o.y + v[8]*o.z + v[12] },
                texgenT[4] = { v[1]*scale, v[5]*scale, v[9]*scale, v[1]*o.x + v[5]*o.y + v[9]*o.z + v[13] },
                texgenR[4] = { v[2]*scale, v[6]*scale, v[10]*scale, v[2]*o.x + v[6]*o.y + v[10]*o.z + v[14] };
        glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS);
        glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT);
        glTexGenfv(GL_R, GL_OBJECT_PLANE, texgenR);
    }
    else
    {
        glActiveTexture_(GL_TEXTURE2_ARB);
        glMatrixMode(GL_TEXTURE);
        glPopMatrix();
        glPushMatrix();
        glTranslatef(o.x, o.y, o.z);
        glScalef(scale, scale, scale);
        glMatrixMode(GL_MODELVIEW);
        glActiveTexture_(GL_TEXTURE0_ARB);
    }
}

void popshadowmap()
{
    if(!shadowmap || !shadowmaptex.rendertex) return;

    if(renderpath!=R_FIXEDFUNCTION) 
    {
        glActiveTexture_(GL_TEXTURE2_ARB);
        glMatrixMode(GL_TEXTURE);
        glPopMatrix();
        glMatrixMode(GL_MODELVIEW);

        glActiveTexture_(GL_TEXTURE0_ARB);
    }
    else
    {
        if(scissoring>1) glScissor(oldscissor[0], oldscissor[1], oldscissor[2], oldscissor[3]);
        else if(scissoring) glDisable(GL_SCISSOR_TEST);

        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_GEN_R);

        if(hasDT && hasSH) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);
        else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_SGIX, GL_FALSE);
    }
}

void rendershadowmap()
{
    if(!shadowmap || (renderpath==R_FIXEDFUNCTION && (!hasSGIDT || !hasSGISH))) return;

    // Apple/ATI bug - fixed-function fog state can force software fallback even when fragment program is enabled
    if(renderpath!=R_FIXEDFUNCTION || !fogging) glDisable(GL_FOG); 
    shadowmaptex.render(1<<shadowmapsize, 1<<shadowmapsize, renderpath!=R_FIXEDFUNCTION ? blurshadowmap : 0, blursmsigma/100.0f);
    if(renderpath!=R_FIXEDFUNCTION || !fogging) glEnable(GL_FOG);
}

VAR(debugsm, 0, 0, 1);

void viewshadowmap()
{
    if(!shadowmap) return;
    shadowmaptex.debug();
}

