/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
Copyright (C) 2020-2021 Vittorio Romeo

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_sky.c

#include "quakedef.hpp"
#include "util.hpp"
#include "cmd.hpp"
#include "console.hpp"
#include "image.hpp"
#include "common.hpp"
#include "glquake.hpp"
#include "gl_texmgr.hpp"
#include "client.hpp"
#include "srcformat.hpp"
#include "sys.hpp"

#define MAX_CLIP_VERTS 64

float Fog_GetDensity();
float* Fog_GetColor();

extern qmodel_t* loadmodel;
extern int rs_skypolys;  // for r_speeds readout
extern int rs_skypasses; // for r_speeds readout
float skyflatcolor[3];
float skymins[2][6], skymaxs[2][6];

bool skyroom_drawn;
bool skyroom_enabled;
qvec4 skyroom_origin;
qvec4 skyroom_orientation;

char skybox_name[1024]; // name of current skybox, or "" if no skybox

gltexture_t* skybox_textures[6];
gltexture_t *solidskytexture, *alphaskytexture;

extern cvar_t gl_farclip;
cvar_t r_fastsky = {"r_fastsky", "0", CVAR_NONE};
cvar_t r_sky_quality = {"r_sky_quality", "12", CVAR_NONE};
cvar_t r_skyalpha = {"r_skyalpha", "1", CVAR_NONE};
cvar_t r_skyfog = {"r_skyfog", "0.5", CVAR_NONE};

int skytexorder[6] = {0, 2, 1, 3, 4, 5}; // for skybox

vec3_t skyclip[6] = {
    {1, 1, 0}, {1, -1, 0}, {0, -1, 1}, {0, 1, 1}, {1, 0, 1}, {-1, 0, 1}};

int st_to_vec[6][3] = {
    {3, -1, 2}, {-3, 1, 2}, {1, 3, 2}, {-1, -3, 2}, {-2, -1, 3}, // straight up
    {2, -1, -3} // straight down
};

int vec_to_st[6][3] = {
    {-2, 3, 1}, {2, 3, -1}, {1, 3, 2}, {-1, 3, -2}, {-2, -1, 3}, {-2, 1, -3}};

float skyfog; // ericw

//==============================================================================
//
//  INIT
//
//==============================================================================

/*
=============
Sky_LoadTexture

A sky texture is 256*128, with the left side being a masked overlay
==============
*/
void Sky_LoadTexture(
    texture_t* mt, srcformat fmt, unsigned int srcwidth, unsigned int height)
{
    char texturename[64];
    int i, p, r, g, b, count;
    byte* src;
    byte* front_data;
    byte* back_data;
    unsigned* rgba;
    int rows, columns;
    int bb, bw, bh;
    int width = srcwidth / 2;

    TexMgr_BlockSize(fmt, &bb, &bw, &bh);
    columns = (width + bw - 1) / bw;
    rows = (height + bh - 1) / bh;

    front_data = (byte*)Hunk_AllocName(bb * columns * rows * 2, "skytex");
    back_data = front_data + bb * columns * rows;

    src = (byte*)(mt + 1);

    // extract back layer and upload
    for(i = 0; i < rows; i++)
    {
        memcpy(back_data + bb * i * columns,
            src + bb * (i * columns * 2 + columns), columns * bb);
    }

    q_snprintf(texturename, sizeof(texturename), "%s:%s_back", loadmodel->name,
        mt->name);
    solidskytexture = TexMgr_LoadImage(loadmodel, texturename, width, height,
        fmt, back_data, "", (src_offset_t)back_data, TEXPREF_NONE);

    // extract front layer and upload
    for(i = 0; i < rows; i++)
    {
        memcpy(front_data + bb * i * columns, src + bb * (i * columns * 2),
            columns * bb);
    }
    if(fmt == SRC_INDEXED)
    { // the lame texmgr only knows one transparent index...
        for(i = 0; i < width * height; i++)
        {
            if(front_data[i] == 0)
            {
                front_data[i] = 255;
            }
        }
    }
    q_snprintf(texturename, sizeof(texturename), "%s:%s_front", loadmodel->name,
        mt->name);
    alphaskytexture = TexMgr_LoadImage(loadmodel, texturename, width, height,
        fmt, front_data, "", (src_offset_t)front_data, TEXPREF_ALPHA);

    // calculate r_fastsky color based on average of all opaque foreground
    // colors, if we can.
    r = g = b = count = 0;
    if(fmt == SRC_INDEXED)
    {
        for(i = 0; i < width * height; i++)
        {
            p = src[i];
            if(p != 0)
            {
                rgba = &d_8to24table[p];
                r += ((byte*)rgba)[0];
                g += ((byte*)rgba)[1];
                b += ((byte*)rgba)[2];
                count++;
            }
        }
    }
    skyflatcolor[0] = (float)r / (count * 255);
    skyflatcolor[1] = (float)g / (count * 255);
    skyflatcolor[2] = (float)b / (count * 255);
}

/*
==================
Sky_LoadSkyBox
==================
*/
const char* suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
void Sky_LoadSkyBox(const char* name)
{
    int width;
    int height;
    char filename[MAX_OSPATH];
    byte* data;
    bool nonefound = true;

    if(strcmp(skybox_name, name) == 0)
    {
        return; // no change
    }

    // purge old textures
    for(int i = 0; i < 6; i++)
    {
        if(skybox_textures[i] && skybox_textures[i] != notexture)
        {
            TexMgr_FreeTexture(skybox_textures[i]);
        }
        skybox_textures[i] = nullptr;
    }

    // turn off skybox if sky is set to ""
    if(name[0] == 0)
    {
        skybox_name[0] = 0;
        return;
    }

    // load textures
    for(int i = 0; i < 6; i++)
    {
        srcformat fmt;
        bool malloced;
        const int mark = Hunk_LowMark();
        q_snprintf(filename, sizeof(filename), "gfx/env/%s%s", name, suf[i]);
        data = Image_LoadImage(filename, &width, &height, &fmt, &malloced);
        if(data)
        {
            skybox_textures[i] = TexMgr_LoadImage(cl.worldmodel, filename,
                width, height, fmt, data, filename, 0, TEXPREF_NONE);
            nonefound = false;
        }
        else
        {
            Con_Printf("Couldn't load %s\n", filename);
            skybox_textures[i] = notexture;
        }
        if(malloced)
        {
            free(data);
        }
        Hunk_FreeToLowMark(mark);
    }

    if(nonefound) // go back to scrolling sky if skybox is totally missing
    {
        for(int i = 0; i < 6; i++)
        {
            if(skybox_textures[i] && skybox_textures[i] != notexture)
            {
                TexMgr_FreeTexture(skybox_textures[i]);
            }
            skybox_textures[i] = nullptr;
        }
        skybox_name[0] = 0;
        return;
    }

    q_strlcpy(skybox_name, name, sizeof(skybox_name));
}

/*
=================
Sky_NewMap
=================
*/
void Sky_NewMap()
{
    char key[128];
    char value[4096];
    const char* data;
    int i;

    //
    // initially no sky
    //
    skyroom_enabled = false;
    skybox_name[0] = 0;
    for(i = 0; i < 6; i++)
    {
        skybox_textures[i] = nullptr;
    }
    skyfog = r_skyfog.value;

    //
    // read worldspawn (this is so ugly, and shouldn't it be done on the
    // server?)
    //
    data = cl.worldmodel->entities;
    if(!data)
    {
        return; // FIXME: how could this possibly ever happen? -- if there's
                // no worldspawn then the sever wouldn't send the loadmap
                // message to the client
    }

    data = COM_Parse(data);
    if(!data)
    {           // should never happen
        return; // error
    }
    if(com_token[0] != '{')
    {           // should never happen
        return; // error
    }
    while(true)
    {
        data = COM_Parse(data);
        if(!data)
        {
            return; // error
        }
        if(com_token[0] == '}')
        {
            break; // end of worldspawn
        }
        if(com_token[0] == '_')
        {
            q_strlcpy(key, com_token + 1, sizeof(key));
        }
        else
        {
            q_strlcpy(key, com_token, sizeof(key));
        }
        while(key[0] && key[strlen(key) - 1] == ' ')
        {
            // remove trailing spaces
            key[strlen(key) - 1] = 0;
        }
        data = COM_Parse(data);
        if(!data)
        {
            return; // error
        }
        q_strlcpy(value, com_token, sizeof(value));

        if(!strcmp("sky", key))
        {
            Sky_LoadSkyBox(value);
        }
        else if(!strcmp("skyroom", key))
        { //"_skyroom" "X Y Z". ideally the gamecode would do this with an
          // entity, but people want to use the vanilla gamecode from 1996 for
          // some reason.
            const char* t = COM_Parse(value);
            skyroom_origin[0] = atof(com_token);
            t = COM_Parse(t);
            skyroom_origin[1] = atof(com_token);
            t = COM_Parse(t);
            skyroom_origin[2] = atof(com_token);
            t = COM_Parse(t);
            skyroom_origin[3] = atof(com_token);
            skyroom_enabled = true;

            t = COM_Parse(t);
            skyroom_orientation[3] = atof(com_token);
            t = COM_Parse(t);
            skyroom_orientation[0] = atof(com_token);
            t = COM_Parse(t);
            skyroom_orientation[1] = atof(com_token);
            t = COM_Parse(t);
            skyroom_orientation[2] = atof(com_token);
        }

        else if(!strcmp("skyfog", key))
        {
            skyfog = atof(value);

#if 1 // also accept non-standard keys
        }
        else if(!strcmp("skyname", key))
        {
            // half-life
            Sky_LoadSkyBox(value);
        }
        else if(!strcmp("qlsky", key))
        {
            // quake lives
            Sky_LoadSkyBox(value);
        }
#endif
    }
}

/*
=================
Sky_SkyCommand_f
=================
*/
void Sky_SkyCommand_f()
{
    switch(Cmd_Argc())
    {
        case 1: Con_Printf("\"sky\" is \"%s\"\n", skybox_name); break;
        case 2: Sky_LoadSkyBox(Cmd_Argv(1)); break;
        default: Con_Printf("usage: sky <skyname>\n");
    }
}

static void Sky_SkyRoomCommand_f()
{
    switch(Cmd_Argc())
    {
        case 1:
            if(skyroom_enabled)
            {
                Con_Printf("\"skyroom\" is \"%f %f %f %f %f %f %f %f\"\n",
                    skyroom_origin[0], skyroom_origin[1], skyroom_origin[2],
                    skyroom_origin[3], skyroom_orientation[3],
                    skyroom_orientation[0], skyroom_orientation[1],
                    skyroom_orientation[2]);
            }
            else
            {
                Con_Printf("\"skyroom\" is \"\"\n");
            }
            break;
        case 4:
        case 5:
        case 6:
            Sky_LoadSkyBox(Cmd_Argv(1));
            skyroom_enabled = true;
            skyroom_origin[0] = atof(Cmd_Argv(1));
            skyroom_origin[1] = atof(Cmd_Argv(2));
            skyroom_origin[2] = atof(Cmd_Argv(3));
            skyroom_origin[3] = atof(Cmd_Argv(4)); // paralax

            skyroom_orientation[3] = atof(Cmd_Argv(5)); // speed
            skyroom_orientation[0] = atof(Cmd_Argv(6));
            skyroom_orientation[1] = atof(Cmd_Argv(7));
            skyroom_orientation[2] = atof(Cmd_Argv(8));
            break;
        case 2:
        default:
            Con_Printf(
                "usage: skyroom origin_x origin_y origin_z paralax_scale speed "
                "axis_x axis_y axis_z\n");
    }
}

/*
====================
R_SetSkyfog_f -- ericw
====================
*/
static void R_SetSkyfog_f(cvar_t* var)
{
    // clear any skyfog setting from worldspawn
    skyfog = var->value;
}

/*
=============
Sky_Init
=============
*/
void Sky_Init()
{
    int i;

    Cvar_RegisterVariable(&r_fastsky);
    Cvar_RegisterVariable(&r_sky_quality);
    Cvar_RegisterVariable(&r_skyalpha);
    Cvar_RegisterVariable(&r_skyfog);
    Cvar_SetCallback(&r_skyfog, R_SetSkyfog_f);

    Cmd_AddCommand("sky", Sky_SkyCommand_f);
    Cmd_AddCommand("skyroom", Sky_SkyRoomCommand_f);

    skybox_name[0] = 0;
    for(i = 0; i < 6; i++)
    {
        skybox_textures[i] = nullptr;
    }
}

//==============================================================================
//
//  PROCESS SKY SURFS
//
//==============================================================================

/*
=================
Sky_ProjectPoly

update sky bounds
=================
*/
void Sky_ProjectPoly(int nump, vec3_t vecs)
{
    int i;
    int j;
    float s;
    float t;
    float dv;
    int axis;
    float* vp;

    // decide which face it maps to
    qvec3 v = vec3_zero;
    for(i = 0, vp = vecs; i < nump; i++, vp += 3)
    {
        VectorAdd(vp, v, v);
    }

    qvec3 av;
    av[0] = fabs(v[0]);
    av[1] = fabs(v[1]);
    av[2] = fabs(v[2]);
    if(av[0] > av[1] && av[0] > av[2])
    {
        if(v[0] < 0)
        {
            axis = 1;
        }
        else
        {
            axis = 0;
        }
    }
    else if(av[1] > av[2] && av[1] > av[0])
    {
        if(v[1] < 0)
        {
            axis = 3;
        }
        else
        {
            axis = 2;
        }
    }
    else
    {
        if(v[2] < 0)
        {
            axis = 5;
        }
        else
        {
            axis = 4;
        }
    }

    // project new texture coords
    for(i = 0; i < nump; i++, vecs += 3)
    {
        j = vec_to_st[axis][2];
        if(j > 0)
        {
            dv = vecs[j - 1];
        }
        else
        {
            dv = -vecs[-j - 1];
        }

        j = vec_to_st[axis][0];
        if(j < 0)
        {
            s = -vecs[-j - 1] / dv;
        }
        else
        {
            s = vecs[j - 1] / dv;
        }
        j = vec_to_st[axis][1];
        if(j < 0)
        {
            t = -vecs[-j - 1] / dv;
        }
        else
        {
            t = vecs[j - 1] / dv;
        }

        if(s < skymins[0][axis])
        {
            skymins[0][axis] = s;
        }
        if(t < skymins[1][axis])
        {
            skymins[1][axis] = t;
        }
        if(s > skymaxs[0][axis])
        {
            skymaxs[0][axis] = s;
        }
        if(t > skymaxs[1][axis])
        {
            skymaxs[1][axis] = t;
        }
    }
}

/*
=================
Sky_ClipPoly
=================
*/
void Sky_ClipPoly(int nump, vec3_t vecs, int stage)
{
    float* norm;
    float* v;
    bool front;
    bool back;
    float d;
    float e;
    float dists[MAX_CLIP_VERTS];
    int sides[MAX_CLIP_VERTS];
    vec3_t newv[2][MAX_CLIP_VERTS];
    int newc[2];
    int i;
    int j;

    if(nump > MAX_CLIP_VERTS - 2)
    {
        Sys_Error("Sky_ClipPoly: MAX_CLIP_VERTS");
    }
    if(stage == 6) // fully clipped
    {
        Sky_ProjectPoly(nump, vecs);
        return;
    }

    front = back = false;
    norm = skyclip[stage];
    for(i = 0, v = vecs; i < nump; i++, v += 3)
    {
        d = DotProduct(v, norm);
        if(d > ON_EPSILON)
        {
            front = true;
            sides[i] = SIDE_FRONT;
        }
        else if(d < ON_EPSILON)
        {
            back = true;
            sides[i] = SIDE_BACK;
        }
        else
        {
            sides[i] = SIDE_ON;
        }
        dists[i] = d;
    }

    if(!front || !back)
    {
        // not clipped
        Sky_ClipPoly(nump, vecs, stage + 1);
        return;
    }

    // clip it
    sides[i] = sides[0];
    dists[i] = dists[0];
    VectorCopy(vecs, (vecs + (i * 3)));
    newc[0] = newc[1] = 0;

    for(i = 0, v = vecs; i < nump; i++, v += 3)
    {
        switch(sides[i])
        {
            case SIDE_FRONT:
                VectorCopy(v, newv[0][newc[0]]);
                newc[0]++;
                break;
            case SIDE_BACK:
                VectorCopy(v, newv[1][newc[1]]);
                newc[1]++;
                break;
            case SIDE_ON:
                VectorCopy(v, newv[0][newc[0]]);
                newc[0]++;
                VectorCopy(v, newv[1][newc[1]]);
                newc[1]++;
                break;
        }

        if(sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON ||
            sides[i + 1] == sides[i])
        {
            continue;
        }

        d = dists[i] / (dists[i] - dists[i + 1]);
        for(j = 0; j < 3; j++)
        {
            e = v[j] + d * (v[j + 3] - v[j]);
            newv[0][newc[0]][j] = e;
            newv[1][newc[1]][j] = e;
        }
        newc[0]++;
        newc[1]++;
    }

    // continue
    Sky_ClipPoly(newc[0], newv[0][0], stage + 1);
    Sky_ClipPoly(newc[1], newv[1][0], stage + 1);
}

/*
================
Sky_ProcessPoly
================
*/
void Sky_ProcessPoly(glpoly_t* p)
{
    int i;
    vec3_t verts[MAX_CLIP_VERTS];

    // draw it
    DrawGLPoly(p);
    rs_brushpasses++;

    // update sky bounds
    if(!r_fastsky.value)
    {
        for(i = 0; i < p->numverts; i++)
            VectorSubtract(p->verts[i], r_origin, verts[i]);
        Sky_ClipPoly(p->numverts, verts[0], 0);
    }
}

/*
================
Sky_ProcessTextureChains -- handles sky polys in world model
================
*/
void Sky_ProcessTextureChains()
{
    int i;
    msurface_t* s;
    texture_t* t;

    if(!r_drawworld_cheatsafe)
    {
        return;
    }

    for(i = 0; i < cl.worldmodel->numtextures; i++)
    {
        t = cl.worldmodel->textures[i];

        if(!t || !t->texturechains[chain_world] ||
            !(t->texturechains[chain_world]->flags & SURF_DRAWSKY))
        {
            continue;
        }

        for(s = t->texturechains[chain_world]; s; s = s->texturechain)
        {
            if(!s->culled)
            {
                Sky_ProcessPoly(s->polys);
            }
        }
    }
}

/*
================
Sky_ProcessEntities -- handles sky polys on brush models
================
*/
void Sky_ProcessEntities()
{
    entity_t* e;
    msurface_t* s;
    glpoly_t* p;
    int j;
    int k;
    int mark;
    float dot;
    bool rotated;
    qvec3 temp;
    qvec3 forward;
    qvec3 right;
    qvec3 up;

    if(!r_drawentities.value)
    {
        return;
    }

    for(int i = 0; i < cl_numvisedicts; i++)
    {
        e = cl_visedicts[i];

        if(e->model->type != mod_brush)
        {
            continue;
        }

        if(R_CullModelForEntity(e))
        {
            continue;
        }

        if(e->alpha == ENTALPHA_ZERO)
        {
            continue;
        }

        modelorg = r_refdef.vieworg - e->origin;
        if(e->angles[0] || e->angles[1] || e->angles[2])
        {
            rotated = true;

            std::tie(forward, right, up) =
                quake::util::getAngledVectors(e->angles);

            temp = modelorg;
            modelorg[0] = DotProduct(temp, forward);
            modelorg[1] = -DotProduct(temp, right);
            modelorg[2] = DotProduct(temp, up);
        }
        else
        {
            rotated = false;
        }

        s = &e->model->surfaces[e->model->firstmodelsurface];

        for(j = 0; j < e->model->nummodelsurfaces; j++, s++)
        {
            if(s->flags & SURF_DRAWSKY)
            {
                dot = DotProduct(modelorg, s->plane->normal) - s->plane->dist;
                if(((s->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
                    (!(s->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
                {
                    // copy the polygon and translate manually, since
                    // Sky_ProcessPoly needs it to be in world space
                    mark = Hunk_LowMark();
                    p = (glpoly_t*)Hunk_Alloc(
                        sizeof(*s->polys)); // FIXME: don't allocate for
                                            // each poly
                    p->numverts = s->polys->numverts;
                    for(k = 0; k < p->numverts; k++)
                    {
                        if(rotated)
                        {
                            p->verts[k][0] =
                                e->origin[0] +
                                s->polys->verts[k][0] * forward[0] -
                                s->polys->verts[k][1] * right[0] +
                                s->polys->verts[k][2] * up[0];
                            p->verts[k][1] =
                                e->origin[1] +
                                s->polys->verts[k][0] * forward[1] -
                                s->polys->verts[k][1] * right[1] +
                                s->polys->verts[k][2] * up[1];
                            p->verts[k][2] =
                                e->origin[2] +
                                s->polys->verts[k][0] * forward[2] -
                                s->polys->verts[k][1] * right[2] +
                                s->polys->verts[k][2] * up[2];
                        }
                        else
                            VectorAdd(
                                s->polys->verts[k], e->origin, p->verts[k]);
                    }
                    Sky_ProcessPoly(p);
                    Hunk_FreeToLowMark(mark);
                }
            }
        }
    }
}

//==============================================================================
//
//  RENDER SKYBOX
//
//==============================================================================

/*
==============
Sky_EmitSkyBoxVertex
==============
*/
void Sky_EmitSkyBoxVertex(float s, float t, int axis)
{
    vec3_t v;
    vec3_t b;
    int j;
    int k;
    float w;
    float h;

    b[0] = s * gl_farclip.value / sqrt(3.0);
    b[1] = t * gl_farclip.value / sqrt(3.0);
    b[2] = gl_farclip.value / sqrt(3.0);

    for(j = 0; j < 3; j++)
    {
        k = st_to_vec[axis][j];
        if(k < 0)
        {
            v[j] = -b[-k - 1];
        }
        else
        {
            v[j] = b[k - 1];
        }
        v[j] += r_origin[j];
    }

    // convert from range [-1,1] to [0,1]
    s = (s + 1) * 0.5;
    t = (t + 1) * 0.5;

    // avoid bilerp seam
    w = skybox_textures[skytexorder[axis]]->width;
    h = skybox_textures[skytexorder[axis]]->height;
    s = s * (w - 1) / w + 0.5 / w;
    t = t * (h - 1) / h + 0.5 / h;

    t = 1.0 - t;
    glTexCoord2f(s, t);
    glVertex3fv(v);
}

/*
==============
Sky_DrawSkyBox

FIXME: eliminate cracks by adding an extra vert on tjuncs
==============
*/
void Sky_DrawSkyBox()
{
    int i;

    for(i = 0; i < 6; i++)
    {
        if(skymins[0][i] >= skymaxs[0][i] || skymins[1][i] >= skymaxs[1][i])
        {
            continue;
        }

        GL_Bind(skybox_textures[skytexorder[i]]);

#if 1 // FIXME: this is to avoid tjunctions until i can do it the right way
        skymins[0][i] = -1;
        skymins[1][i] = -1;
        skymaxs[0][i] = 1;
        skymaxs[1][i] = 1;
#endif
        glBegin(GL_QUADS);
        Sky_EmitSkyBoxVertex(skymins[0][i], skymins[1][i], i);
        Sky_EmitSkyBoxVertex(skymins[0][i], skymaxs[1][i], i);
        Sky_EmitSkyBoxVertex(skymaxs[0][i], skymaxs[1][i], i);
        Sky_EmitSkyBoxVertex(skymaxs[0][i], skymins[1][i], i);
        glEnd();

        rs_skypolys++;
        rs_skypasses++;

        if(Fog_GetDensity() > 0 && skyfog > 0)
        {
            float* c;

            c = Fog_GetColor();
            glEnable(GL_BLEND);
            glDisable(GL_TEXTURE_2D);
            glColor4f(c[0], c[1], c[2], CLAMP(0.0, skyfog, 1.0));

            glBegin(GL_QUADS);
            Sky_EmitSkyBoxVertex(skymins[0][i], skymins[1][i], i);
            Sky_EmitSkyBoxVertex(skymins[0][i], skymaxs[1][i], i);
            Sky_EmitSkyBoxVertex(skymaxs[0][i], skymaxs[1][i], i);
            Sky_EmitSkyBoxVertex(skymaxs[0][i], skymins[1][i], i);
            glEnd();

            glColor3f(1, 1, 1);
            glEnable(GL_TEXTURE_2D);
            glDisable(GL_BLEND);

            rs_skypasses++;
        }
    }
}

//==============================================================================
//
//  RENDER CLOUDS
//
//==============================================================================

/*
==============
Sky_SetBoxVert
==============
*/
void Sky_SetBoxVert(float s, float t, int axis, vec3_t v)
{
    vec3_t b;
    int j;
    int k;

    b[0] = s * gl_farclip.value / sqrt(3.0);
    b[1] = t * gl_farclip.value / sqrt(3.0);
    b[2] = gl_farclip.value / sqrt(3.0);

    for(j = 0; j < 3; j++)
    {
        k = st_to_vec[axis][j];
        if(k < 0)
        {
            v[j] = -b[-k - 1];
        }
        else
        {
            v[j] = b[k - 1];
        }
        v[j] += r_origin[j];
    }
}

/*
=============
Sky_GetTexCoord
=============
*/
void Sky_GetTexCoord(vec3_t v, float speed, float* s, float* t)
{
    vec3_t dir;
    float length;
    float scroll;

    VectorSubtract(v, r_origin, dir);
    dir[2] *= 3; // flatten the sphere

    length = dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
    length = sqrt(length);
    length = 6 * 63 / length;

    scroll = cl.time * speed;
    scroll -= (int)scroll & ~127;

    *s = (scroll + dir[0] * length) * (1.0 / 128);
    *t = (scroll + dir[1] * length) * (1.0 / 128);
}

/*
===============
Sky_DrawFaceQuad
===============
*/
void Sky_DrawFaceQuad(glpoly_t* p)
{
    float s;
    float t;
    float* v;
    int i;

    if(gl_mtexable && r_skyalpha.value >= 1.0)
    {
        GL_Bind(solidskytexture);
        GL_EnableMultitexture();
        GL_Bind(alphaskytexture);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

        glBegin(GL_QUADS);
        for(i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE)
        {
            Sky_GetTexCoord(v, 8, &s, &t);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB, s, t);
            Sky_GetTexCoord(v, 16, &s, &t);
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB, s, t);
            glVertex3fv(v);
        }
        glEnd();

        GL_DisableMultitexture();

        rs_skypolys++;
        rs_skypasses++;
    }
    else
    {
        GL_Bind(solidskytexture);

        if(r_skyalpha.value < 1.0)
        {
            glColor3f(1, 1, 1);
        }

        glBegin(GL_QUADS);
        for(i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE)
        {
            Sky_GetTexCoord(v, 8, &s, &t);
            glTexCoord2f(s, t);
            glVertex3fv(v);
        }
        glEnd();

        GL_Bind(alphaskytexture);
        glEnable(GL_BLEND);

        if(r_skyalpha.value < 1.0)
        {
            glColor4f(1, 1, 1, r_skyalpha.value);
        }

        glBegin(GL_QUADS);
        for(i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE)
        {
            Sky_GetTexCoord(v, 16, &s, &t);
            glTexCoord2f(s, t);
            glVertex3fv(v);
        }
        glEnd();

        glDisable(GL_BLEND);

        rs_skypolys++;
        rs_skypasses += 2;
    }

    if(Fog_GetDensity() > 0 && skyfog > 0)
    {
        float* c;

        c = Fog_GetColor();
        glEnable(GL_BLEND);
        glDisable(GL_TEXTURE_2D);
        glColor4f(c[0], c[1], c[2], CLAMP(0.0, skyfog, 1.0));

        glBegin(GL_QUADS);
        for(i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE)
        {
            glVertex3fv(v);
        }
        glEnd();

        glColor3f(1, 1, 1);
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_BLEND);

        rs_skypasses++;
    }
}

/*
==============
Sky_DrawFace
==============
*/

void Sky_DrawFace(int axis)
{
    glpoly_t* p;
    vec3_t verts[4];
    int i;
    int j;
    int start;
    qfloat di;
    qfloat qi;
    qfloat dj;
    qfloat qj;
    qvec3 vup;
    qvec3 vright;
    qvec3 temp;
    qvec3 temp2;

    Sky_SetBoxVert(-1.0, -1.0, axis, verts[0]);
    Sky_SetBoxVert(-1.0, 1.0, axis, verts[1]);
    Sky_SetBoxVert(1.0, 1.0, axis, verts[2]);
    Sky_SetBoxVert(1.0, -1.0, axis, verts[3]);

    start = Hunk_LowMark();
    p = (glpoly_t*)Hunk_Alloc(sizeof(glpoly_t));

    VectorSubtract(verts[2], verts[3], vup);
    VectorSubtract(verts[2], verts[1], vright);

    di = q_max((int)r_sky_quality.value, 1);
    qi = 1.0 / di;
    dj = (axis < 4) ? di * 2 : di; // subdivide vertically more than
                                   // horizontally on skybox sides
    qj = 1.0 / dj;

    for(i = 0; i < di; i++)
    {
        for(j = 0; j < dj; j++)
        {
            if(i * qi < skymins[0][axis] / 2 + 0.5 - qi ||
                i * qi > skymaxs[0][axis] / 2 + 0.5 ||
                j * qj < skymins[1][axis] / 2 + 0.5 - qj ||
                j * qj > skymaxs[1][axis] / 2 + 0.5)
            {
                continue;
            }

            // if (i&1 ^ j&1) continue; //checkerboard test
            temp = vright * (qi * i);
            temp2 = vup * (qj * j);
            temp += temp2;
            VectorAdd(verts[0], temp, p->verts[0]);

            temp = vup * qj;
            VectorAdd(p->verts[0], temp, p->verts[1]);

            temp = vright * qi;
            VectorAdd(p->verts[1], temp, p->verts[2]);

            VectorAdd(p->verts[0], temp, p->verts[3]);

            Sky_DrawFaceQuad(p);
        }
    }
    Hunk_FreeToLowMark(start);
}

/*
==============
Sky_DrawSkyLayers

draws the old-style scrolling cloud layers
==============
*/
void Sky_DrawSkyLayers()
{
    int i;

    if(r_skyalpha.value < 1.0)
    {
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    }

    for(i = 0; i < 6; i++)
    {
        if(skymins[0][i] < skymaxs[0][i] && skymins[1][i] < skymaxs[1][i])
        {
            Sky_DrawFace(i);
        }
    }

    if(r_skyalpha.value < 1.0)
    {
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    }
}

/*
==============
Sky_DrawSky

called once per frame before drawing anything else
==============
*/
void Sky_DrawSky()
{
    int i;

    // in these special render modes, the sky faces are handled in
    // the normal world/brush renderer
    if(r_drawflat_cheatsafe || r_lightmap_cheatsafe || skyroom_drawn)
    {
        return;
    }

    //
    // reset sky bounds
    //
    for(i = 0; i < 6; i++)
    {
        skymins[0][i] = skymins[1][i] = FLT_MAX;
        skymaxs[0][i] = skymaxs[1][i] = -FLT_MAX;
    }

    //
    // process world and bmodels: draw flat-shaded sky surfs, and
    // update skybounds
    //
    Fog_DisableGFog();
    glDisable(GL_TEXTURE_2D);
    if(Fog_GetDensity() > 0)
    {
        glColor3fv(Fog_GetColor());
    }
    else
    {
        glColor3fv(skyflatcolor);
    }
    Sky_ProcessTextureChains();
    Sky_ProcessEntities();
    glColor3f(1, 1, 1);
    glEnable(GL_TEXTURE_2D);

    //
    // render slow sky: cloud layers or skybox
    //
    if(!r_fastsky.value && !(Fog_GetDensity() > 0 && skyfog >= 1))
    {
        glDepthFunc(GL_GEQUAL);
        glDepthMask(0);

        if(skybox_name[0])
        {
            Sky_DrawSkyBox();
        }
        else
        {
            Sky_DrawSkyLayers();
        }

        glDepthMask(1);
        glDepthFunc(GL_LEQUAL);
    }

    Fog_EnableGFog();
}
