/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

entity_t    *currententity;

int            r_visframecount;    // bumped when going to a new PVS
int            r_framecount;        // used for dlight push checking

mplane_t    frustum[4];

int            c_brush_polys, c_alias_polys;

GLuint        particletexture;    // little dot for particles
GLuint        playertextures[16];    // up to 16 color translated skins

//
// screen size info
//
refdef_t    r_refdef;

mleaf_t        *r_viewleaf, *r_oldviewleaf;

texture_t    *r_notexture_mip;

int        d_lightstylevalue[256];    // 8.8 fraction of base light value


void R_MarkLeaves (void);


/*
=================
R_CullBox

Returns true if the box is completely outside the frustom
=================
*/
qboolean R_CullBox (vec3_t mins, vec3_t maxs)
{
    int        i;

    for (i=0 ; i<4 ; i++)
        if (BoxOnPlaneSide (mins, maxs, &frustum[i]) == 2)
            return true;
    return false;
}


void R_RotateForEntity (entity_t *e)
{
    glTranslatef (e->origin[0],  e->origin[1],  e->origin[2]);

    glRotatef (e->angles[1],  0, 0, 1);
    glRotatef (-e->angles[0],  0, 1, 0);
    glRotatef (e->angles[2],  1, 0, 0);
}

/*
=============================================================

  SPRITE MODELS

=============================================================
*/

/*
================
R_GetSpriteFrame
================
*/
mspriteframe_t *R_GetSpriteFrame (entity_t *currententity)
{
    msprite_t        *psprite;
    mspritegroup_t    *pspritegroup;
    mspriteframe_t    *pspriteframe;
    int                i, numframes, frame;
    float            *pintervals, fullinterval, targettime, time;

    psprite = (msprite_t *)currententity->model->cache.data;
    frame = currententity->frame;

    if ((frame >= psprite->numframes) || (frame < 0))
    {
        Con_Printf ("R_DrawSprite: no such frame %d\n", frame);
        frame = 0;
    }

    if (psprite->frames[frame].type == SPR_SINGLE)
    {
        pspriteframe = psprite->frames[frame].frameptr;
    }
    else
    {
        pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
        pintervals = pspritegroup->intervals;
        numframes = pspritegroup->numframes;
        fullinterval = pintervals[numframes-1];

        time = cl.time + currententity->syncbase;

    // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
    // are positive, so we don't have to worry about division by 0
        targettime = time - ((int)(time / fullinterval)) * fullinterval;

        for (i=0 ; i<(numframes-1) ; i++)
        {
            if (pintervals[i] > targettime)
                break;
        }

        pspriteframe = pspritegroup->frames[i];
    }

    return pspriteframe;
}


/*
=================
R_DrawSpriteModel

=================
*/
void R_DrawSpriteModel (entity_t *e)
{
    vec3_t    point;
    mspriteframe_t    *frame;
    float        *up, *right;
    vec3_t        v_forward, v_right, v_up;
    msprite_t        *psprite;

    // don't even bother culling, because it's just a single
    // polygon without a surface cache
    frame = R_GetSpriteFrame (e);
    psprite = (msprite_t *)currententity->model->cache.data;

    if (psprite->type == SPR_ORIENTED)
    {    // bullet marks on walls
        AngleVectors (currententity->angles, v_forward, v_right, v_up);
        up = v_up;
        right = v_right;
    }
    else
    {    // normal sprite
        up = r_refdef.up;
        right = r_refdef.right;
    }

    glColor4f (1, 1, 1, currententity->alphaval);

    glBindTexture(GL_TEXTURE_2D, frame->gl_texturenum);

    glEnable (GL_ALPHA_TEST);
    glBegin (GL_QUADS);

    glTexCoord2f (0, 1);
    VectorMA (e->origin, frame->down, up, point);
    VectorMA (point, frame->left, right, point);
    glVertex3fv (point);

    glTexCoord2f (0, 0);
    VectorMA (e->origin, frame->up, up, point);
    VectorMA (point, frame->left, right, point);
    glVertex3fv (point);

    glTexCoord2f (1, 0);
    VectorMA (e->origin, frame->up, up, point);
    VectorMA (point, frame->right, right, point);
    glVertex3fv (point);

    glTexCoord2f (1, 1);
    VectorMA (e->origin, frame->down, up, point);
    VectorMA (point, frame->right, right, point);
    glVertex3fv (point);

    glEnd ();

    glDisable (GL_ALPHA_TEST);
}

/*
=============================================================

  ALIAS MODELS

=============================================================
*/


#define NUMVERTEXNORMALS    162

float    r_avertexnormals[NUMVERTEXNORMALS][3] = {
#include "anorms.h"
};

extern vec3_t lightcolor;

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 16
float    r_avertexnormal_dots[SHADEDOT_QUANT][256] =
#include "anorm_dots.h"
;

float    *shadedots = r_avertexnormal_dots[0];

int    lastposenum;

/*
=============
GL_DrawAliasFrame
=============
*/
void GL_DrawAliasFrame (aliashdr_t *paliashdr, int posenum)
{
    float     l;
    trivertx_t    *verts;
    int        *order;
    int        count;

lastposenum = posenum;

    verts = (trivertx_t *)((byte *)paliashdr + paliashdr->posedata);
    verts += posenum * paliashdr->poseverts;
    order = (int *)((byte *)paliashdr + paliashdr->commands);

    while (1)
    {
        // get the vertex count and primitive type
        count = *order++;
        if (!count)
            break;        // done
        if (count < 0)
        {
            count = -count;
            glBegin (GL_TRIANGLE_FAN);
        }
        else
            glBegin (GL_TRIANGLE_STRIP);

        do
        {
            // texture coordinates come from the draw list
            glTexCoord2f (((float *)order)[0], ((float *)order)[1]);
            order += 2;

            // normals and vertexes come from the frame list
            l = shadedots[verts->lightnormalindex];
            glColor4f (l * lightcolor[0], l * lightcolor[1], l * lightcolor[2], currententity->alphaval);
            glVertex3f (verts->v[0], verts->v[1], verts->v[2]);
            verts++;
        } while (--count);

        glEnd ();
    }
}


/*
=================
R_SetupAliasFrame

=================
*/
void R_SetupAliasFrame (int frame, aliashdr_t *paliashdr)
{
    int                pose, numposes;
    float            interval;

    if ((frame >= paliashdr->numframes) || (frame < 0))
    {
        Con_DPrintf ("R_AliasSetupFrame: no such frame %d\n", frame);
        frame = 0;
    }

    pose = paliashdr->frames[frame].firstpose;
    numposes = paliashdr->frames[frame].numposes;

    if (numposes > 1)
    {
        interval = paliashdr->frames[frame].interval;
        pose += (int)(cl.time / interval) % numposes;
    }

    GL_DrawAliasFrame (paliashdr, pose);
}



/*
=================
R_DrawAliasModel
=================
*/
void R_DrawAliasModel (entity_t *e)
{
    int            i;
    int            lnum;
    vec3_t        dist;
    float        add;
    model_t        *clmodel;
    vec3_t        mins, maxs;
    aliashdr_t    *paliashdr;
    int            anim;

    clmodel = currententity->model;

    VectorAdd (currententity->origin, clmodel->mins, mins);
    VectorAdd (currententity->origin, clmodel->maxs, maxs);

    // Don't cull out the view model
    if (currententity != &cl.viewent && R_CullBox (mins, maxs))
        return;

    //
    // get lighting information
    //
    R_LightPoint (currententity->origin);

    // always give the gun some light
    if (e == &cl.viewent && Length(lightcolor) < 24)
        lightcolor[0] = lightcolor[1] = lightcolor[2] = 24;

    for (lnum=0 ; lnum<MAX_DLIGHTS ; lnum++)
    {
        if (cl_dlights[lnum].die >= cl.time)
        {
            VectorSubtract (currententity->origin,
                            cl_dlights[lnum].origin,
                            dist);
            add = cl_dlights[lnum].radius - Length(dist);

            if (add > 0)
            {
                lightcolor[0] += add * cl_dlights[lnum].color[0];
                lightcolor[1] += add * cl_dlights[lnum].color[1];
                lightcolor[2] += add * cl_dlights[lnum].color[2];
            }
        }
    }

    // ZOID: never allow players to go totally black
    i = currententity - cl_entities;
    if (i >= 1 && i <= cl.maxclients && Length(lightcolor) < 8)
        lightcolor[0] = lightcolor[1] = lightcolor[2] = 8;

    // HACK HACK HACK -- no fullbright colors, so make torches full light
    if (!strcmp (clmodel->name, "progs/flame2.mdl")
        || !strcmp (clmodel->name, "progs/flame.mdl") )
        lightcolor[0] = lightcolor[1] = lightcolor[2] = 256;

    shadedots = r_avertexnormal_dots[((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];
    VectorScale(lightcolor, 1.0f / 800.0f, lightcolor);

    //
    // locate the proper data
    //
    paliashdr = (aliashdr_t *)Mod_Extradata (currententity->model);

    c_alias_polys += paliashdr->numtris;

    //
    // draw all the triangles
    //

    glPushMatrix ();
    R_RotateForEntity (e);

    glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
    glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]);

    anim = (int)(cl.time*10) & 3;
    glBindTexture(GL_TEXTURE_2D, paliashdr->gl_texturenum[currententity->skinnum][anim]);

    // We can't dynamically recolor textures, so they are cached
    // seperately for the players.  Heads are just uncolored.
    if (currententity->recolor && !Cvar_VariableValue("gl_nocolors"))
    {
        i = currententity - cl_entities;
        if (i >= 1 && i<=cl.maxclients)
            glBindTexture(GL_TEXTURE_2D, playertextures[i-1]);
    }
    
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
    glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
    glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PRIMARY_COLOR);
    glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE, 4.0f);
    
    R_SetupAliasFrame (currententity->frame, paliashdr);

    glPopMatrix ();
}

//==================================================================================

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

    if (!Cvar_VariableValue("r_drawentities"))
        return;

    // draw sprites seperately, because of alpha blending
    for (i=0 ; i<cl_numvisedicts ; i++)
    {
        currententity = cl_visedicts[i];

        if (currententity == &cl_entities[cl.viewentity])
        {
            if (Cvar_VariableValue("chase_active.value") && chase_nodraw)
                continue;
            currententity->angles[0] *= 0.3f;
        }

        switch (currententity->model->type)
        {
        case mod_alias:
            R_DrawAliasModel (currententity);
            break;

        case mod_brush:
            R_DrawBrushModel (currententity);
            break;

        default:
            break;
        }
    }

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

        switch (currententity->model->type)
        {
        case mod_sprite:
            R_DrawSpriteModel (currententity);
            break;

        default:
            break;
        }
    }
}

/*
=============
R_DrawViewModel
=============
*/
void R_DrawViewModel (void)
{
    if (!Cvar_VariableValue("r_drawviewmodel"))
        return;
    if (Cvar_VariableValue("chase_active"))
        return;
    if (!Cvar_VariableValue("r_drawentities"))
        return;
    if (cl.items & IT_INVISIBILITY)
        return;
    if (cl.stats[STAT_HEALTH] <= 0)
        return;

    currententity = &cl.viewent;
    if (!currententity->model)
        return;

    // hack the depth range to prevent view model from poking into walls
    glDepthRange (0.0, 0.3);
    R_DrawAliasModel (currententity);
    glDepthRange (0.0, 1.0);
}


/*
============
R_PolyBlend
============
*/
void R_PolyBlend (void)
{
    if (!Cvar_VariableValue("gl_polyblend"))
        return;
    if (!v_blend[3])
        return;

    glDisable (GL_DEPTH_TEST);
    glDisable (GL_TEXTURE_2D);

    glLoadIdentity ();

    glRotatef (-90,  1, 0, 0);        // put Z going up
    glRotatef (90,  0, 0, 1);        // put Z going up

    glColor4fv (v_blend);

    glBegin (GL_QUADS);

    glVertex3f (10, 100, 100);
    glVertex3f (10, -100, 100);
    glVertex3f (10, -100, -100);
    glVertex3f (10, 100, -100);
    glEnd ();

    glEnable (GL_TEXTURE_2D);
}


int SignbitsForPlane (mplane_t *out)
{
    int    bits, j;

    // for fast box on planeside test

    bits = 0;
    for (j=0 ; j<3 ; j++)
    {
        if (out->normal[j] < 0)
            bits |= 1<<j;
    }
    return bits;
}


void R_SetFrustum (void)
{
    int        i;

    if (r_refdef.fov_x == 90)
    {
        // front side is visible
        VectorAdd (r_refdef.forward, r_refdef.right, frustum[0].normal);
        VectorSubtract (r_refdef.forward, r_refdef.right, frustum[1].normal);

        VectorAdd (r_refdef.forward, r_refdef.up, frustum[2].normal);
        VectorSubtract (r_refdef.forward, r_refdef.up, frustum[3].normal);
    }
    else
    {
        // rotate forward right by FOV_X/2 degrees
        RotatePointAroundVector(frustum[0].normal, r_refdef.up, r_refdef.forward, -(90-r_refdef.fov_x / 2));
        // rotate forward left by FOV_X/2 degrees
        RotatePointAroundVector(frustum[1].normal, r_refdef.up, r_refdef.forward, 90-r_refdef.fov_x / 2);
        // rotate forward up by FOV_X/2 degrees
        RotatePointAroundVector(frustum[2].normal, r_refdef.right, r_refdef.forward, 90-r_refdef.fov_y / 2);
        // rotate forward down by FOV_X/2 degrees
        RotatePointAroundVector(frustum[3].normal, r_refdef.right, r_refdef.forward, -(90 - r_refdef.fov_y / 2));
    }

    for (i=0 ; i<4 ; i++)
    {
        frustum[i].type = PLANE_ANYZ;
        frustum[i].dist = DotProduct (r_refdef.origin, frustum[i].normal);
        frustum[i].signbits = SignbitsForPlane (&frustum[i]);
    }
}



/*
===============
R_SetupFrame
===============
*/
void R_SetupFrame (void)
{
    // don't allow cheats in multiplayer
    if (cl.maxclients > 1)
        Cvar_Set ("r_fullbright", "0");

    R_AnimateLight ();

    r_framecount++;

    // Recompute the direction vectors from the viewangles
    AngleVectors(r_refdef.viewangles, r_refdef.forward, r_refdef.right, r_refdef.up);

// current viewleaf
    r_oldviewleaf = r_viewleaf;
    r_viewleaf = Mod_PointInLeaf (r_refdef.origin, cl.worldmodel);

    V_SetContentsColor (r_viewleaf->contents);
    V_CalcBlend ();

    c_brush_polys = 0;
    c_alias_polys = 0;

}


/*
=============
R_SetupGL
=============
*/
void R_SetupGL (void)
{
    float    screenaspect;

    //
    // set up viewpoint
    //
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity ();

    glViewport (0, 0, vid.width, vid.height);

    screenaspect = (float)vid.width/vid.height;
    gluPerspective (r_refdef.fov_y,  screenaspect,  4,  4096);

    glCullFace(GL_FRONT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity ();

    glRotatef (-90,  1, 0, 0);        // put Z going up
    glRotatef (90,  0, 0, 1);        // put Z going up
    glRotatef (-r_refdef.viewangles[2],  1, 0, 0);
    glRotatef (-r_refdef.viewangles[0],  0, 1, 0);
    glRotatef (-r_refdef.viewangles[1],  0, 0, 1);
    glTranslatef (-r_refdef.origin[0],  -r_refdef.origin[1],  -r_refdef.origin[2]);

    //
    // set drawing parms
    //
    if (Cvar_VariableValue("gl_cull"))
        glEnable(GL_CULL_FACE);
    else
        glDisable(GL_CULL_FACE);

    glEnable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
}

/*
================
R_RenderScene

r_refdef must be set before the first call
================
*/
void R_RenderScene (void)
{
    R_SetupFrame ();

    R_SetFrustum ();

    R_SetupGL ();

    R_MarkLeaves ();    // done here so we know if we're in water

    R_DrawWorld ();        // adds static entities to the list

    R_DrawEntitiesOnList ();

    R_DrawParticles ();
}


/*
=============
R_Clear
=============
*/
void R_Clear (void)
{
    glClear (GL_DEPTH_BUFFER_BIT);
    glDepthFunc (GL_LEQUAL);
}


/*
================
R_RenderView

r_refdef must be set before the first call
================
*/
void R_RenderView (void)
{
    double    time1, time2;

    if (Cvar_VariableValue("r_norefresh"))
        return;

    if (!cl.worldmodel)
        Sys_Error ("R_RenderView: NULL worldmodel");

    if (Cvar_VariableValue("r_speeds"))
    {
        glFinish ();
        time1 = Sys_FloatTime ();
        c_brush_polys = 0;
        c_alias_polys = 0;
    }

    R_Clear ();

    // render normal view
    R_RenderScene ();
    R_DrawViewModel ();
    R_DrawWaterSurfaces ();

    R_PolyBlend ();

    if (Cvar_VariableValue("r_speeds"))
    {
        time2 = Sys_FloatTime ();
        Con_Printf ("%3i ms  %4i wpoly %4i epoly\n", (int)((time2-time1)*1000), c_brush_polys, c_alias_polys);
    }
}
