/*
 * 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 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_rmain.c

#include "quakedef.h"

entity_t      r_worldentity;

qboolean      r_cache_thrash;          // compatibility

vec3_t        modelorg, r_entorigin;
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, c_md3_polys;

int           blurtexture;
int           damagetexture;
int           particletexture;         // little dot for particles
int           playertextures;          // up to 16 color translated skins
int           skyboxtextures;
int           underwatertexture, detailtexture;

#define	INTERP_WEAP_MAXNUM	24
#define	INTERP_MINDIST		70
#define	INTERP_MAXDIST		300

typedef struct interpolated_weapon {
    char          name[MAX_QPATH];
    int           maxDistance;
} interp_weapon_t;

static interp_weapon_t interpolated_weapons[INTERP_WEAP_MAXNUM];
static int    interp_weap_num = 0;

int           DoWeaponInterpolation (void);

// view origin
vec3_t        vup;
vec3_t        vpn;
vec3_t        vright;
vec3_t        r_origin;

float         r_world_matrix[16];
float         r_base_world_matrix[16];

// screen size info
refdef_t      r_refdef;

mleaf_t      *r_viewleaf, *r_oldviewleaf;
mleaf_t      *r_viewleaf2, *r_oldviewleaf2;     // for watervis hack

texture_t    *r_notexture_mip;

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

cvar_t        r_drawentities = { "r_drawentities", "1" };
cvar_t        r_drawviewmodel = { "r_drawviewmodel", "1" };
cvar_t        r_viewmodelsize = { "r_viewmodelsize", "1" };
cvar_t        r_speeds = { "r_speeds", "0" };
cvar_t        r_fullbright = { "r_fullbright", "0" };
cvar_t        r_lightmap = { "r_lightmap", "0" };
cvar_t        r_shadows = { "r_shadows", "0", CVAR_ARCHIVE };
cvar_t        r_novis = { "r_novis", "0", CVAR_ARCHIVE };     // lxndr: fps eater
cvar_t        r_fullbrightskins = { "r_fullbrightskins", "0" };
cvar_t        r_fastsky = { "r_fastsky", "0" };
cvar_t        r_skycolor = { "r_skycolor", "4" };
cvar_t        r_drawflame = { "r_drawflame", "1" };
cvar_t        r_farclip = { "r_farclip", "4096", CVAR_ARCHIVE };
qboolean      OnChange_r_skybox (cvar_t * var, char *string);
cvar_t        r_skybox = { "r_skybox", "", 0, OnChange_r_skybox };

cvar_t        r_alpha = { "r_alpha", "0", CVAR_ARCHIVE };
cvar_t        r_alphalava = { "r_alphalava", "0.5", CVAR_ARCHIVE };
cvar_t        r_alphaslime = { "r_alphaslime", "0.7", CVAR_ARCHIVE };
cvar_t        r_alphateleport = { "r_alphateleport", "0.7", CVAR_ARCHIVE };
cvar_t        r_alphawater = { "r_alphawater", "0.5", CVAR_ARCHIVE };

cvar_t        r_dynamic = { "r_dynamic", "1", CVAR_ARCHIVE };
cvar_t        r_dynamicintensity = { "r_dynamicintensity", "0.5", CVAR_ARCHIVE };
cvar_t        r_dynamicradius = { "r_dynamicradius", "1", CVAR_ARCHIVE };

qboolean      OnChange_r_quality (cvar_t * var, char *string);
cvar_t        r_quality = { "r_quality", "0.6", CVAR_ARCHIVE | CVAR_AGGREGATE, OnChange_r_quality };

cvar_t        gl_clear = { "gl_clear", "1", CVAR_ARCHIVE };     // lxndr: solves rendering issue when alpha && !novis
cvar_t        gl_cull = { "gl_cull", "1" };
cvar_t        gl_ztrick = { "gl_ztrick", "0" };
cvar_t        gl_smoothmodels = { "gl_smoothmodels", "1", CVAR_ARCHIVE };
cvar_t        gl_affinemodels = { "gl_affinemodels", "0" };
cvar_t        gl_polyblend = { "gl_polyblend", "1", CVAR_ARCHIVE };
cvar_t        gl_playermip = { "gl_playermip", "0" };
cvar_t        gl_nocolors = { "gl_nocolors", "0" };
cvar_t        gl_finish = { "gl_finish", "0", CVAR_ARCHIVE };
cvar_t        gl_doubleeyes = { "gl_doubleeyes", "0" };
cvar_t        gl_interdist = { "gl_interpolate_distance", "135" };
cvar_t        gl_detail = { "gl_detail", "0", CVAR_ARCHIVE };
cvar_t        gl_caustics = { "gl_caustics", "1", CVAR_ARCHIVE };
cvar_t        gl_ringalpha = { "gl_ringalpha", "0.4" };
cvar_t        gl_fb_bmodels = { "gl_fb_bmodels", "0" }; // lxndr: see e1m1 for examples of bad results
cvar_t        gl_fb_models = { "gl_fb_models", "0" };   // lxndr: no more dynamic lights
cvar_t        gl_solidparticles = { "gl_solidparticles", "0" };
cvar_t        gl_vertexlights = { "gl_vertexlights", "0", CVAR_ARCHIVE };
cvar_t        gl_shownormals = { "gl_shownormals", "0" };
cvar_t        gl_lerptextures = { "gl_lerptextures", "0", CVAR_ARCHIVE };

cvar_t        gl_anisotropy = { "gl_anisotropy", "4", CVAR_ARCHIVE };
cvar_t        gl_antialiasing = { "gl_antialiasing", "0", CVAR_ARCHIVE };

cvar_t        gl_blur = { "gl_blur", "0", CVAR_ARCHIVE };
cvar_t        gl_bluralpha = { "gl_bluralpha", "0.5", CVAR_ARCHIVE };
cvar_t        gl_blurhealth = { "gl_blurhealth", "1", CVAR_ARCHIVE };
cvar_t        gl_blurscale = { "gl_blurscale", "0.2", CVAR_ARCHIVE };

cvar_t        gl_dlightintensity = { "gl_dlightintensity", "0.1", CVAR_ARCHIVE };
cvar_t        gl_dlightradius = { "gl_dlightradius", "0.25", CVAR_ARCHIVE };

cvar_t        gl_fog = { "gl_fog", "0", CVAR_ARCHIVE };
cvar_t        gl_fogcolor = { "gl_fogcolor", "0.5", CVAR_ARCHIVE };
cvar_t        gl_fogdarkness = { "gl_fogdarkness", "0.5", CVAR_ARCHIVE };
cvar_t        gl_fogdensity = { "gl_fogdensity", "0.5", CVAR_ARCHIVE };
cvar_t        gl_fogdistance = { "gl_fogdistance", "0.5", CVAR_ARCHIVE };

cvar_t        gl_loadlits = { "gl_loadlits", "0", CVAR_ARCHIVE | CVAR_AFTER_INIT, OnChange_reload };
cvar_t        gl_loadq3models = { "gl_loadq3models", "0", CVAR_ARCHIVE | CVAR_AFTER_INIT, OnChange_reload };

cvar_t        gl_part_explosions = { "gl_part_explosions", "0" };
cvar_t        gl_part_trails = { "gl_part_trails", "0" };
cvar_t        gl_part_spikes = { "gl_part_spikes", "0" };
cvar_t        gl_part_gunshots = { "gl_part_gunshots", "0" };
cvar_t        gl_part_blood = { "gl_part_blood", "0" };
cvar_t        gl_part_telesplash = { "gl_part_telesplash", "0" };
cvar_t        gl_part_blobs = { "gl_part_blobs", "0" };
cvar_t        gl_part_lavasplash = { "gl_part_lavasplash", "0" };
cvar_t        gl_part_flames = { "gl_part_flames", "0" };
cvar_t        gl_part_lightning = { "gl_part_lightning", "0" };
cvar_t        gl_part_damagesplash = { "gl_part_damagesplash", "0" };

extern qboolean OnChange_gl_part_muzzleflash (cvar_t * var, char *value);
cvar_t        gl_part_muzzleflash = { "gl_part_muzzleflash", "0", 0, OnChange_gl_part_muzzleflash };

int           lightmode = 2;

void          R_MarkLeaves (void);
void          R_InitBubble (void);

/*
 * OnChange_r_quality
 */
qboolean OnChange_r_quality (cvar_t * var, char *string) {
    float         value = Q_atof (string);

    Cvar_Set (&gl_texturemode, "GL_NEAREST");
    Cvar_SetValue (&gl_anisotropy, 0);
    Cvar_SetValue (&gl_antialiasing, 0);
    Cvar_SetValue (&gl_bloom, 0);
    Cvar_SetValue (&gl_blur, 0);
    Cvar_SetValue (&gl_caustics, 0);
    Cvar_SetValue (&gl_clear, 0);
//    Cvar_SetValue (&gl_detail, 0);
    Cvar_SetValue (&gl_finish, 0);
    Cvar_SetValue (&gl_fog, 0);
//    Cvar_SetValue (&gl_loadlits, 0);
    Cvar_SetValue (&gl_polyblend, 0);
    Cvar_SetValue (&gl_smoke, 0);
    Cvar_SetValue (&gl_vertexlights, 0);
    Cvar_SetValue (&r_alpha, 0);
    Cvar_SetValue (&r_dynamic, 0);
    Cvar_SetValue (&r_explosiontype, 0);
    Cvar_SetValue (&r_farclip, 2048);
    Cvar_SetValue (&r_shadows, 0);
    R_SetParticleMode (pm_classic);

    if (value > 0.05) {
        Cvar_SetValue (&gl_anisotropy, 2);
        Cvar_SetValue (&gl_antialiasing, 2);
    }
    if (value > 0.15) {
        Cvar_Set (&gl_texturemode, "GL_LINEAR");
        Cvar_SetValue (&r_explosiontype, 4);
    }
    if (value > 0.25) {
        Cvar_Set (&gl_texturemode, "GL_LINEAR_MIPMAP_NEAREST");
        Cvar_SetValue (&gl_anisotropy, 4);
        Cvar_SetValue (&gl_antialiasing, 4);
        Cvar_SetValue (&r_farclip, 4096);
    }
    if (value > 0.35) {
        Cvar_Set (&gl_texturemode, "GL_LINEAR_MIPMAP_LINEAR");
    }
    if (value > 0.45) {
        Cvar_SetValue (&gl_anisotropy, 8);
        Cvar_SetValue (&gl_antialiasing, 8);
        Cvar_SetValue (&r_levels, 1);
    } else {
        Cvar_SetValue (&r_levels, 0);
    }
    if (value > 0.55) {
        Cvar_SetValue (&gl_clear, 1);
        Cvar_SetValue (&gl_finish, 1);
        Cvar_SetValue (&gl_polyblend, 1);
        Cvar_SetValue (&gl_vertexlights, 1);
        Cvar_SetValue (&r_alpha, 1);
        Cvar_SetValue (&r_dynamic, 1);
        Cvar_SetValue (&r_farclip, 8192);
        Cvar_SetValue (&r_shadows, 1);
    }
    if (value > 0.65) {
        Cvar_SetValue (&gl_anisotropy, 16);
        Cvar_SetValue (&gl_antialiasing, 16);
        Cvar_SetValue (&gl_fog, 1);
        if (!Q_strcasecmp (ambience.string, "default"))
            Cvar_SetValue (&v_gamma, 1.5);
    } else {
        if (!Q_strcasecmp (ambience.string, "default"))
            Cvar_ResetVar (&v_gamma);
    }
    if (value > 0.75) {
        Cvar_SetValue (&gl_caustics, 1);
//        Cvar_SetValue (&gl_detail, 1);
//        Cvar_SetValue (&gl_loadlits, 1);
        Cvar_SetValue (&gl_smoke, 1);
        R_SetParticleMode (pm_qmb);
    }
    if (value > 0.85) {
        Cvar_SetValue (&gl_anisotropy, 32);
        Cvar_SetValue (&gl_antialiasing, 32);
        Cvar_SetValue (&gl_blur, 1);
        Cvar_SetValue (&r_farclip, 16384);
    }
    if (value > 0.95) {
        Cvar_SetValue (&gl_bloom, 1);
    }

    return false;
}

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

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

    return false;
}

/*
 * R_CullSphere
 *
 * Returns true if the sphere is completely outside the frustum
 *
 */
qboolean R_CullSphere (vec3_t centre, float radius) {
    int           i;
    mplane_t     *p;

    for (i = 0, p = frustum; i < 4; i++, p++)
        if (PlaneDiff (centre, p) <= -radius)
            return true;

    return false;
}

/*
 * R_RotateForEntity
 */
void R_RotateForEntity (entity_t * ent, qboolean shadow) {
    int           i;
    float         lerpfrac, timepassed;
    vec3_t        d, interpolated;

    // positional interpolation
    timepassed = cl.time - ent->translate_start_time;

    if (ent->translate_start_time == 0 || timepassed > 1) {
        ent->translate_start_time = cl.time;
        VectorCopy (ent->origin, ent->origin1);
        VectorCopy (ent->origin, ent->origin2);
    }

    if (!VectorCompare (ent->origin, ent->origin2)) {
        ent->translate_start_time = cl.time;
        VectorCopy (ent->origin2, ent->origin1);
        VectorCopy (ent->origin, ent->origin2);
        lerpfrac = 0;
    } else {
        lerpfrac = timepassed / 0.1;
        if (cl.paused || lerpfrac > 1)
            lerpfrac = 1;
    }

    VectorInterpolate (ent->origin1, lerpfrac, ent->origin2, interpolated);
    glTranslatef (interpolated[0], interpolated[1], interpolated[2]);

    // orientation interpolation (Euler angles, yuck!)
    timepassed = cl.time - ent->rotate_start_time;

    if (ent->rotate_start_time == 0 || timepassed > 1) {
        ent->rotate_start_time = cl.time;
        VectorCopy (ent->angles, ent->angles1);
        VectorCopy (ent->angles, ent->angles2);
    }

    if (!VectorCompare (ent->angles, ent->angles2)) {
        ent->rotate_start_time = cl.time;
        VectorCopy (ent->angles2, ent->angles1);
        VectorCopy (ent->angles, ent->angles2);
        lerpfrac = 0;
    } else {
        lerpfrac = timepassed / 0.1;
        if (cl.paused || lerpfrac > 1)
            lerpfrac = 1;
    }

    VectorSubtract (ent->angles2, ent->angles1, d);

    // always interpolate along the shortest path
    for (i = 0; i < 3; i++) {
        if (d[i] > 180)
            d[i] -= 360;
        else if (d[i] < -180)
            d[i] += 360;
    }

    glRotatef (ent->angles1[1] + (lerpfrac * d[1]), 0, 0, 1);
    if (!shadow) {
        glRotatef (-ent->angles1[0] + (-lerpfrac * d[0]), 0, 1, 0);
        glRotatef (ent->angles1[2] + (lerpfrac * d[2]), 1, 0, 0);
    }
}

/*
 * R_RotateForViewEntity
 */
void R_RotateForViewEntity (entity_t * ent) {
    glTranslatef (ent->origin[0], ent->origin[1], ent->origin[2]);

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

/*
 *
 *
 * SPRITE MODELS
 *
 *
 */

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

    psprite = currententity->model->cache.data;
    frame = currententity->frame;

    if ((frame >= psprite->numframes) || (frame < 0)) {
        Con_DPrintf ("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 * ent) {
    vec3_t        point, right, up;
    mspriteframe_t *frame;
    msprite_t    *psprite;

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

    if (psprite->type == SPR_ORIENTED) {
        // bullet marks on walls
        AngleVectors (currententity->angles, NULL, right, up);
    } else if (psprite->type == SPR_FACING_UPRIGHT) {
        VectorSet (up, 0, 0, 1);
        right[0] = ent->origin[1] - r_origin[1];
        right[1] = -(ent->origin[0] - r_origin[0]);
        right[2] = 0;
        VectorNormalize (right);
    } else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT) {
        VectorSet (up, 0, 0, 1);
        VectorCopy (vright, right);
    } else {                           // normal sprite
        VectorCopy (vup, up);
        VectorCopy (vright, right);
    }

    GL_Bind (frame->gl_texturenum);

    glBegin (GL_QUADS);

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

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

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

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

    glEnd ();
}

/*
 *
 *
 * ALIAS MODELS
 *
 *
 */

#define NUMVERTEXNORMALS	162

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

vec3_t        shadevector;

qboolean      full_light;
float         shadelight, ambientlight;

// 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];

float         apitch, ayaw;

/*
 * R_DrawAliasFrame
 */
void R_DrawAliasFrame (int frame, aliashdr_t * paliashdr, entity_t * ent, int distance) {
    int           i, *order, count, pose, numposes;
    float         l, lerpfrac;
    vec3_t        lightvec, interpolated_verts;
    trivertx_t   *verts1, *verts2;

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

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

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

    if (ent->pose2 != pose) {
        ent->frame_start_time = cl.time;
        ent->pose1 = ent->pose2;
        ent->pose2 = pose;
        ent->framelerp = 0;
    } else {
        ent->framelerp = (cl.time - ent->frame_start_time) / ent->frame_interval;
    }

    // weird things start happening if blend passes 1
    if (cl.paused || ent->framelerp > 1)
        ent->framelerp = 1;

    verts1 = (trivertx_t *) ((byte *) paliashdr + paliashdr->posedata);
    verts2 = verts1;

    verts1 += ent->pose1 * paliashdr->poseverts;
    verts2 += ent->pose2 * paliashdr->poseverts;

    order = (int *) ((byte *) paliashdr + paliashdr->commands);

    if (ISTRANSPARENT (ent))
        glEnable (GL_BLEND);

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

        do {
            // texture coordinates come from the draw list
            if (gl_mtexable) {
                qglMultiTexCoord2f (GL_TEXTURE0_ARB, ((float *) order)[0], ((float *) order)[1]);
                qglMultiTexCoord2f (GL_TEXTURE1_ARB, ((float *) order)[0], ((float *) order)[1]);
            } else {
                glTexCoord2f (((float *) order)[0], ((float *) order)[1]);
            }

            order += 2;

            lerpfrac = VectorL2Compare (verts1->v, verts2->v, distance) ? ent->framelerp : 1;

            // normals and vertexes come from the frame list
            // blend the light intensity from the two frames together
            if (gl_vertexlights.value && !full_light) {
                l = R_LerpVertexLight (anorm_pitch[verts1->lightnormalindex], anorm_yaw[verts1->lightnormalindex],
                                       anorm_pitch[verts2->lightnormalindex], anorm_yaw[verts2->lightnormalindex],
                                       lerpfrac, apitch, ayaw);
                l = min (l, 1);

                for (i = 0; i < 3; i++)
                    lightvec[i] = lightcolor[i] / 256 + l;
                glColor4f (lightvec[0], lightvec[1], lightvec[2], ent->transparency);
            } else {
                l = FloatInterpolate (shadedots[verts1->lightnormalindex], lerpfrac,
                                      shadedots[verts2->lightnormalindex]);
                l = (l * shadelight + ambientlight) / 256;
                l = min (l, 1);

                glColor4f (l, l, l, ent->transparency);
            }

            VectorInterpolate (verts1->v, lerpfrac, verts2->v, interpolated_verts);
            glVertex3fv (interpolated_verts);

            verts1++;
            verts2++;
        }
        while (--count);

        glEnd ();
    }

    if (ISTRANSPARENT (ent))
        glDisable (GL_BLEND);
}

/*
 * R_DrawAliasShadow
 */
void R_DrawAliasShadow (aliashdr_t * paliashdr, entity_t * ent, int distance, trace_t downtrace) {
    int          *order, count;
    float         lheight, lerpfrac, s1, c1;
    vec3_t        point1, point2, interpolated;
    trivertx_t   *verts1, *verts2;

    lheight = ent->origin[2] - lightspot[2];

    s1 = sin (ent->angles[1] / 180 * M_PI);
    c1 = cos (ent->angles[1] / 180 * M_PI);

    verts1 = (trivertx_t *) ((byte *) paliashdr + paliashdr->posedata);
    verts2 = verts1;

    verts1 += ent->pose1 * paliashdr->poseverts;
    verts2 += ent->pose2 * paliashdr->poseverts;

    order = (int *) ((byte *) paliashdr + paliashdr->commands);

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

        do {
            order += 2;

            lerpfrac = VectorL2Compare (verts1->v, verts2->v, distance) ? ent->framelerp : 1;

            point1[0] = verts1->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];
            point1[1] = verts1->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];
            point1[2] = verts1->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];

            point1[0] -= shadevector[0] * (point1[2] + lheight);
            point1[1] -= shadevector[1] * (point1[2] + lheight);

            point2[0] = verts2->v[0] * paliashdr->scale[0] + paliashdr->scale_origin[0];
            point2[1] = verts2->v[1] * paliashdr->scale[1] + paliashdr->scale_origin[1];
            point2[2] = verts2->v[2] * paliashdr->scale[2] + paliashdr->scale_origin[2];

            point2[0] -= shadevector[0] * (point2[2] + lheight);
            point2[1] -= shadevector[1] * (point2[2] + lheight);

            VectorInterpolate (point1, lerpfrac, point2, interpolated);

            interpolated[2] = -(ent->origin[2] - downtrace.endpos[2]);

            interpolated[2] +=
                ((interpolated[1] * (s1 * downtrace.plane.normal[0])) -
                 (interpolated[0] * (c1 * downtrace.plane.normal[0])) -
                 (interpolated[0] * (s1 * downtrace.plane.normal[1])) -
                 (interpolated[1] * (c1 * downtrace.plane.normal[1]))) + ((1 - downtrace.plane.normal[2]) * 20) + 0.2;

            glVertex3fv (interpolated);

            verts1++;
            verts2++;
        }
        while (--count);

        glEnd ();
    }
}

/*
 * R_SetupLighting
 */
void R_SetupLighting (entity_t * ent) {
    int           i, lnum;
    float         add;
    vec3_t        dist, dlight_color;
    model_t      *clmodel = ent->model;

    // make thunderbolt and torches full light
    if (clmodel->modhint == MOD_THUNDERBOLT || clmodel->modhint == MOD_LASER || clmodel->modhint == MOD_KSPIKE) {
        ambientlight = 200;
        shadelight = 0;
        full_light = ent->noshadow = true;
        return;
    } else if (clmodel->modhint == MOD_FLAME) {
        ambientlight = 200;
        shadelight = 0;
        full_light = ent->noshadow = true;
        return;
    } else if (clmodel->modhint == MOD_Q3GUNSHOT || clmodel->modhint == MOD_Q3TELEPORT) {
        ambientlight = 128;
        shadelight = 0;
        full_light = ent->noshadow = true;
        return;
    }
    // normal lighting
    ambientlight = shadelight = R_LightPoint (ent->origin);
    full_light = false;
    ent->noshadow = false;

    for (lnum = 0; lnum < MAX_DLIGHTS; lnum++) {
        if (cl_dlights[lnum].die < cl.time || !cl_dlights[lnum].radius)
            continue;

        VectorSubtract (ent->origin, cl_dlights[lnum].origin, dist);
        add = cl_dlights[lnum].radius - VectorLength (dist);

        if (add > 0) {
            // joe: only allow colorlight affection if dynamic lights are on
            if (r_dynamic.value) {
                VectorCopy (dlightcolor[cl_dlights[lnum].type], dlight_color);
                for (i = 0; i < 3; i++) {
                    lightcolor[i] = lightcolor[i] + (dlight_color[i] * add * r_dynamicintensity.value) * 2;
                    if (lightcolor[i] > 256) {
                        switch (i) {
                            case 0:
                                lightcolor[1] = lightcolor[1] - (lightcolor[1] / 3);
                                lightcolor[2] = lightcolor[2] - (lightcolor[2] / 3);
                                break;

                            case 1:
                                lightcolor[0] = lightcolor[0] - (lightcolor[0] / 3);
                                lightcolor[2] = lightcolor[2] - (lightcolor[2] / 3);
                                break;

                            case 2:
                                lightcolor[1] = lightcolor[1] - (lightcolor[1] / 3);
                                lightcolor[0] = lightcolor[0] - (lightcolor[0] / 3);
                                break;
                        }
                    }
                }
            } else {
                ambientlight += add;
            }
        }
    }

    // calculate pitch and yaw for vertex lighting
    if (gl_vertexlights.value) {
        apitch = ent->angles[0];
        ayaw = ent->angles[1];
    }
    // clamp lighting so it doesn't overbright as much
    ambientlight = min (128, ambientlight);
    if (ambientlight + shadelight > 192)
        shadelight = 192 - ambientlight;

    if (ent == &cl.viewent) {
        ent->noshadow = true;
        // always give the gun some light
        if (ambientlight < 24)
            ambientlight = shadelight = 24;
    }
    // never allow players to go totally black
    if (clmodel->modhint == MOD_PLAYER) {
        if (ambientlight < 8)
            ambientlight = shadelight = 8;
        if (r_fullbrightskins.value) {
            ambientlight = shadelight = 128;
            full_light = true;
        }
    }

    if (r_fullbrightskins.value == 2
        && (ent->modelindex == cl_modelindex[mi_fish] || ent->modelindex == cl_modelindex[mi_dog]
            || ent->modelindex == cl_modelindex[mi_soldier]
            || ent->modelindex == cl_modelindex[mi_enforcer]
            || ent->modelindex == cl_modelindex[mi_knight]
            || ent->modelindex == cl_modelindex[mi_hknight]
            || ent->modelindex == cl_modelindex[mi_scrag]
            || ent->modelindex == cl_modelindex[mi_ogre]
            || ent->modelindex == cl_modelindex[mi_fiend]
            || ent->modelindex == cl_modelindex[mi_vore]
            || ent->modelindex == cl_modelindex[mi_shambler])) {
        ambientlight = shadelight = 128;
        full_light = true;
    }
}

/*
 * R_SetupInterpolateDistance
 */
void R_SetupInterpolateDistance (entity_t * ent, int *distance) {
    *distance = INTERP_MAXDIST;

    if (ent->model->modhint == MOD_FLAME)
        *distance = 0;
    else if (ent->model->modhint == MOD_WEAPON) {
        if ((*distance = DoWeaponInterpolation ()) == -1)
            *distance = (int) gl_interdist.value;
    } else if (ent->modelindex == cl_modelindex[mi_player]) {
        // nailatt
        if (ent->frame == 103)
            *distance = 0;
        else if (ent->frame == 104)
            *distance = 59;
        // rockatt
        else if (ent->frame == 107)
            *distance = 0;
        else if (ent->frame == 108)
            *distance = 115;
        // shotatt
        else if (ent->frame == 113)
            *distance = 76;
        else if (ent->frame == 115)
            *distance = 79;
    } else if (ent->modelindex == cl_modelindex[mi_soldier]) {
        if (ent->frame == 84)
            *distance = 63;
        else if (ent->frame == 85)
            *distance = 49;
        else if (ent->frame == 86)
            *distance = 106;
    } else if (ent->modelindex == cl_modelindex[mi_enforcer]) {
        if (ent->frame == 36)
            *distance = 115;
        else if (ent->frame == 37)
            *distance = 125;
    }
}

/*
 * R_DrawAliasModel
 */
void R_DrawAliasModel (entity_t * ent) {
    int           i, anim, skinnum, distance, texture, fb_texture;
    vec3_t        mins, maxs;
    aliashdr_t   *paliashdr;
    model_t      *clmodel = ent->model;
    qboolean      isLumaSkin;

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

    if (ent->angles[0] || ent->angles[1] || ent->angles[2]) {
        if (R_CullSphere (ent->origin, clmodel->radius))
            return;
    } else {
        if (R_CullBox (mins, maxs))
            return;
    }

    VectorCopy (ent->origin, r_entorigin);
    VectorSubtract (r_origin, r_entorigin, modelorg);

    // get lighting information
    R_SetupLighting (ent);
    R_SetupInterpolateDistance (ent, &distance);

    shadedots = r_avertexnormal_dots[((int) (ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

    // locate the proper data
    paliashdr = (aliashdr_t *) Mod_Extradata (clmodel);

    c_alias_polys += paliashdr->numtris;

    // draw all the triangles
 
    glPushMatrix ();

    if (ent == &cl.viewent)
        R_RotateForViewEntity (ent);
    else
        R_RotateForEntity (ent, false);

    if (clmodel->modhint == MOD_EYES && gl_doubleeyes.value) {
        glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2] - (22 + 8));
        // double size of eyes, since they are really hard to see in gl
        glScalef (paliashdr->scale[0] * 2, paliashdr->scale[1] * 2, paliashdr->scale[2] * 2);
    } else if (ent == &cl.viewent) {
        float         scale = 0.5 + bound (0, r_viewmodelsize.value, 1) / 2;

        glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
        glScalef (paliashdr->scale[0] * scale, paliashdr->scale[1], paliashdr->scale[2]);
    } else {
        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;
    skinnum = ent->skinnum;
    if ((skinnum >= paliashdr->numskins) || (skinnum < 0)) {
        Con_DPrintf ("R_DrawAliasModel: no such skin # %d\n", skinnum);
        skinnum = 0;
    }

    texture = paliashdr->gl_texturenum[skinnum][anim];
    fb_texture = paliashdr->fb_texturenum[skinnum][anim];
    isLumaSkin = paliashdr->isLumaSkin[skinnum][anim];

    // we can't dynamically colormap textures, so they are cached
    // seperately for the players. Heads are just uncolored.
    if (ent->colormap != vid.colormap && !gl_nocolors.value) {
        i = ent - cl_entities;

        if (i > 0 && i <= cl.maxclients) {
            texture = playertextures - 1 + i;
            fb_texture = fb_skins[i - 1];
        }
    }

    if (full_light || !gl_fb_models.value)
        fb_texture = 0;

    if (gl_smoothmodels.value)
        glShadeModel (GL_SMOOTH);

    if (gl_affinemodels.value)
        glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

    if (fb_texture && gl_mtexable) {
        GL_DisableMultitexture ();
        glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        GL_Bind (texture);

        GL_EnableMultitexture ();
        if (isLumaSkin) {
            if (gl_add_ext) {
                glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
                GL_Bind (fb_texture);
            }
        } else {
            glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
            GL_Bind (fb_texture);
        }

        R_DrawAliasFrame (ent->frame, paliashdr, ent, distance);

        if (isLumaSkin && !gl_add_ext) {
            glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
            GL_Bind (fb_texture);

            glDepthMask (GL_FALSE);
            glEnable (GL_BLEND);
            glBlendFunc (GL_ONE, GL_ONE);

            R_DrawAliasFrame (ent->frame, paliashdr, ent, distance);

            glDisable (GL_BLEND);
            glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glDepthMask (GL_TRUE);
        }

        GL_DisableMultitexture ();
    } else {
        GL_DisableMultitexture ();
        glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        GL_Bind (texture);

        R_DrawAliasFrame (ent->frame, paliashdr, ent, distance);

        if (fb_texture) {
            glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
            GL_Bind (fb_texture);

            glDepthMask (GL_FALSE);
            if (isLumaSkin) {
                glEnable (GL_BLEND);
                glBlendFunc (GL_ONE, GL_ONE);
            } else {
                glEnable (GL_ALPHA_TEST);
            }

            R_DrawAliasFrame (ent->frame, paliashdr, ent, distance);

            if (isLumaSkin) {
                glDisable (GL_BLEND);
                glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            } else {
                glDisable (GL_ALPHA_TEST);
            }
            glDepthMask (GL_TRUE);
        }
    }

    glShadeModel (GL_FLAT);
    if (gl_affinemodels.value)
        glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glPopMatrix ();

    if (r_shadows.value && !ent->noshadow) {
        int           farclip;
        float         theta;
        vec3_t        downmove;
        trace_t       downtrace;
        static float  shadescale = 0;

        farclip = max ((int) r_farclip.value, 4096);

        if (!shadescale)
            shadescale = 1 / sqrt (2);
        theta = -ent->angles[1] / 180 * M_PI;

        VectorSet (shadevector, cos (theta) * shadescale, sin (theta) * shadescale, shadescale);

        glPushMatrix ();

        R_RotateForEntity (ent, true);

        VectorCopy (ent->origin, downmove);
        downmove[2] -= farclip;
        memset (&downtrace, 0, sizeof (downtrace));
        SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, ent->origin, downmove, &downtrace);

        glDepthMask (GL_FALSE);
        glDisable (GL_TEXTURE_2D);
        glEnable (GL_BLEND);
        glColor4f (0, 0, 0, (ambientlight - (mins[2] - downtrace.endpos[2])) / 150);
        if (gl_have_stencil && r_shadows.value == 2) {
            glEnable (GL_STENCIL_TEST);
            glStencilFunc (GL_EQUAL, 1, 2);
            glStencilOp (GL_KEEP, GL_KEEP, GL_INCR);
        }

        R_DrawAliasShadow (paliashdr, ent, distance, downtrace);

        glDepthMask (GL_TRUE);
        glEnable (GL_TEXTURE_2D);
        glDisable (GL_BLEND);
        if (gl_have_stencil && r_shadows.value == 2)
            glDisable (GL_STENCIL_TEST);

        glPopMatrix ();
    }

    glColor3ubv (color_white);
}

void Set_Interpolated_Weapon_f (void) {
    int           i;
    char          str[MAX_QPATH];

    if (cmd_source != src_command)
        return;

    if (Cmd_Argc () == 2) {
        for (i = 0; i < interp_weap_num; i++) {
            if (!Q_strcasecmp (Cmd_Argv (1), interpolated_weapons[i].name)) {
                Con_Printf ("%s`s distance is %d\n", Cmd_Argv (1), interpolated_weapons[i].maxDistance);
                return;
            }
        }
        Con_Printf ("%s`s distance is default (%d)\n", Cmd_Argv (1), (int) gl_interdist.value);
        return;
    }

    if (Cmd_Argc () != 3) {
        Con_Printf ("Usage: set_interpolated_weapon <model> <distance>\n");
        return;
    }

    Q_strcpy (str, Cmd_Argv (1));
    for (i = 0; i < interp_weap_num; i++)
        if (!Q_strcasecmp (str, interpolated_weapons[i].name))
            break;
    if (i == interp_weap_num) {
        if (interp_weap_num == INTERP_WEAP_MAXNUM) {
            Con_Printf ("interp_weap_num == INTERP_WEAP_MAXNUM\n");
            return;
        } else {
            interp_weap_num++;
        }
    }

    Q_strcpy (interpolated_weapons[i].name, str);
    interpolated_weapons[i].maxDistance = (int) Q_atof (Cmd_Argv (2));
}

int DoWeaponInterpolation (void) {
    int           i;

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

    for (i = 0; i < interp_weap_num; i++) {
        if (!interpolated_weapons[i].name[0])
            return -1;

        if (!Q_strcasecmp (currententity->model->name, va ("%s.mdl", interpolated_weapons[i].name))
            || !Q_strcasecmp (currententity->model->name, va ("progs/%s.mdl", interpolated_weapons[i].name)))
            return interpolated_weapons[i].maxDistance;
    }

    return -1;
}

/*
 *
 *
 * Q3 MODELS
 *
 *
 */

void R_RotateForTagEntity (tagentity_t * tagent, md3tag_t * tag, float *m) {
    int           i;
    float         lerpfrac, timepassed;

    // positional interpolation
    timepassed = cl.time - tagent->tag_translate_start_time;

    if (tagent->tag_translate_start_time == 0 || timepassed > 1) {
        tagent->tag_translate_start_time = cl.time;
        VectorCopy (tag->pos, tagent->tag_pos1);
        VectorCopy (tag->pos, tagent->tag_pos2);
    }

    if (!VectorCompare (tag->pos, tagent->tag_pos2)) {
        tagent->tag_translate_start_time = cl.time;
        VectorCopy (tagent->tag_pos2, tagent->tag_pos1);
        VectorCopy (tag->pos, tagent->tag_pos2);
        lerpfrac = 0;
    } else {
        lerpfrac = timepassed / 0.1;
        if (cl.paused || lerpfrac > 1)
            lerpfrac = 1;
    }

    VectorInterpolate (tagent->tag_pos1, lerpfrac, tagent->tag_pos2, m + 12);
    m[15] = 1;

    for (i = 0; i < 3; i++) {
        // orientation interpolation (Euler angles, yuck!)
        timepassed = cl.time - tagent->tag_rotate_start_time[i];

        if (tagent->tag_rotate_start_time[i] == 0 || timepassed > 1) {
            tagent->tag_rotate_start_time[i] = cl.time;
            VectorCopy (tag->rot[i], tagent->tag_rot1[i]);
            VectorCopy (tag->rot[i], tagent->tag_rot2[i]);
        }

        if (!VectorCompare (tag->rot[i], tagent->tag_rot2[i])) {
            tagent->tag_rotate_start_time[i] = cl.time;
            VectorCopy (tagent->tag_rot2[i], tagent->tag_rot1[i]);
            VectorCopy (tag->rot[i], tagent->tag_rot2[i]);
            lerpfrac = 0;
        } else {
            lerpfrac = timepassed / 0.1;
            if (cl.paused || lerpfrac > 1)
                lerpfrac = 1;
        }

        VectorInterpolate (tagent->tag_rot1[i], lerpfrac, tagent->tag_rot2[i], m + i * 4);
        m[i * 4 + 3] = 0;
    }
}

int           bodyframe = 0, legsframe = 0;
animtype_t    bodyanim, legsanim;

void R_ReplaceQ3Frame (int frame) {
    animdata_t   *currbodyanim, *currlegsanim;
    static animtype_t oldbodyanim, oldlegsanim;
    static float  bodyanimtime, legsanimtime;
    static qboolean deathanim = false;

    if (deathanim) {
        bodyanim = oldbodyanim;
        legsanim = oldlegsanim;
    }

    if (frame < 41 || frame > 102)
        deathanim = false;

    if (frame >= 0 && frame <= 5) {    // axrun
        bodyanim = torso_stand2;
        legsanim = legs_run;
    } else if (frame >= 6 && frame <= 11) {     // rockrun
        bodyanim = torso_stand;
        legsanim = legs_run;
    } else if ((frame >= 12 && frame <= 16) || (frame >= 35 && frame <= 40)) {  // stand, pain
        bodyanim = torso_stand;
        legsanim = legs_idle;
    } else if ((frame >= 17 && frame <= 28) || (frame >= 29 && frame <= 34)) {  // axstand, axpain
        bodyanim = torso_stand2;
        legsanim = legs_idle;
    } else if (frame >= 41 && frame <= 102 && !deathanim) {     // axdeath, deatha, b, c, d, e
        bodyanim = legsanim = rand () % 3;
        deathanim = true;
    } else if (frame >= 103 && frame <= 118) {  // gun attacks
        bodyanim = torso_attack;
    } else if (frame >= 119) {         // axe attacks
        bodyanim = torso_attack2;
    }

    currbodyanim = &anims[bodyanim];
    currlegsanim = &anims[legsanim];

    if (bodyanim == oldbodyanim) {
        if (cl.time >= bodyanimtime + currbodyanim->interval) {
            if (currbodyanim->loop_frames && bodyframe + 1 == currbodyanim->offset + currbodyanim->loop_frames)
                bodyframe = currbodyanim->offset;
            else if (bodyframe + 1 < currbodyanim->offset + currbodyanim->num_frames)
                bodyframe++;
            bodyanimtime = cl.time;
        }
    } else {
        bodyframe = currbodyanim->offset;
        bodyanimtime = cl.time;
    }

    if (legsanim == oldlegsanim) {
        if (cl.time >= legsanimtime + currlegsanim->interval) {
            if (currlegsanim->loop_frames && legsframe + 1 == currlegsanim->offset + currlegsanim->loop_frames)
                legsframe = currlegsanim->offset;
            else if (legsframe + 1 < currlegsanim->offset + currlegsanim->num_frames)
                legsframe++;
            legsanimtime = cl.time;
        }
    } else {
        legsframe = currlegsanim->offset;
        legsanimtime = cl.time;
    }

    oldbodyanim = bodyanim;
    oldlegsanim = legsanim;
}

int           multimodel_level;
qboolean      surface_transparent;

/*
 * R_DrawQ3Frame
 */
void R_DrawQ3Frame (int frame, md3header_t * pmd3hdr, md3surface_t * pmd3surf, entity_t * ent, int distance) {
    int           i, j, numtris, pose, pose1, pose2;
    float         l, lerpfrac;
    vec3_t        lightvec, interpolated_verts;
    unsigned int *tris;
    md3tc_t      *tc;
    md3vert_mem_t *verts, *v1, *v2;
    model_t      *clmodel = ent->model;

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

    if (ent->pose1 >= pmd3hdr->numframes)
        ent->pose1 = 0;

    pose = frame;

    if (!strcmp (clmodel->name, "progs/player/lower.md3"))
        ent->frame_interval = anims[legsanim].interval;
    else if (!strcmp (clmodel->name, "progs/player/upper.md3"))
        ent->frame_interval = anims[bodyanim].interval;
    else
        ent->frame_interval = 0.1;

    if (ent->pose2 != pose) {
        ent->frame_start_time = cl.time;
        ent->pose1 = ent->pose2;
        ent->pose2 = pose;
        ent->framelerp = 0;
    } else {
        ent->framelerp = (cl.time - ent->frame_start_time) / ent->frame_interval;
    }

    // weird things start happening if blend passes 1
    if (cl.paused || ent->framelerp > 1)
        ent->framelerp = 1;

    verts = (md3vert_mem_t *) ((byte *) pmd3hdr + pmd3surf->ofsverts);
    tc = (md3tc_t *) ((byte *) pmd3surf + pmd3surf->ofstc);
    tris = (unsigned int *) ((byte *) pmd3surf + pmd3surf->ofstris);
    numtris = pmd3surf->numtris * 3;
    pose1 = ent->pose1 * pmd3surf->numverts;
    pose2 = ent->pose2 * pmd3surf->numverts;

    if (surface_transparent) {
        glEnable (GL_BLEND);
        if (clmodel->modhint == MOD_Q3GUNSHOT || clmodel->modhint == MOD_Q3TELEPORT)
            glBlendFunc (GL_SRC_ALPHA, GL_ONE);
        else
            glBlendFunc (GL_ONE, GL_ONE);
        glDepthMask (GL_FALSE);
        glDisable (GL_CULL_FACE);
    } else if (ISTRANSPARENT (ent)) {
        glEnable (GL_BLEND);
    }

    glBegin (GL_TRIANGLES);
    for (i = 0; i < numtris; i++) {
        float         s, t;

        v1 = verts + *tris + pose1;
        v2 = verts + *tris + pose2;

        if (clmodel->modhint == MOD_Q3TELEPORT)
            s = tc[*tris].s, t = tc[*tris].t * 4;
        else
            s = tc[*tris].s, t = tc[*tris].t;

        if (gl_mtexable) {
            qglMultiTexCoord2f (GL_TEXTURE0_ARB, s, t);
            qglMultiTexCoord2f (GL_TEXTURE1_ARB, s, t);
        } else {
            glTexCoord2f (s, t);
        }

        lerpfrac = VectorL2Compare (v1->vec, v2->vec, distance) ? ent->framelerp : 1;

        if (gl_vertexlights.value && !full_light) {
            l = R_LerpVertexLight (v1->anorm_pitch, v1->anorm_yaw, v2->anorm_pitch, v2->anorm_yaw, lerpfrac, apitch,
                                   ayaw);
            l = min (l, 1);

            for (j = 0; j < 3; j++)
                lightvec[j] = lightcolor[j] / 256 + l;
            glColor4f (lightvec[0], lightvec[1], lightvec[2], ent->transparency);
        } else {
            l = FloatInterpolate (shadedots[v1->oldnormal >> 8], lerpfrac, shadedots[v2->oldnormal >> 8]);
            l = (l * shadelight + ambientlight) / 256;
            l = min (l, 1);

            glColor4f (l, l, l, ent->transparency);
        }

        VectorInterpolate (v1->vec, lerpfrac, v2->vec, interpolated_verts);
        glVertex3fv (interpolated_verts);

        tris++; // lxndr: was *tris++
    }
    glEnd ();

    if (gl_shownormals.value) {
        vec3_t        temp;

        tris = (unsigned int *) ((byte *) pmd3surf + pmd3surf->ofstris);
        glDisable (GL_TEXTURE_2D);
        glColor3ubv (color_white);
        glBegin (GL_LINES);
        for (i = 0; i < numtris; i++) {
            glVertex3fv (verts[*tris + pose1].vec);
            VectorMA (verts[*tris + pose1].vec, 2, verts[*tris + pose1].normal, temp);
            glVertex3fv (temp);
            tris++; // lxndr: was *tris++
        }
        glEnd ();
        glEnable (GL_TEXTURE_2D);
    }

    if (surface_transparent) {
        glDisable (GL_BLEND);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDepthMask (GL_TRUE);
        glEnable (GL_CULL_FACE);
    } else if (ISTRANSPARENT (ent)) {
        glDisable (GL_BLEND);
    }
}

/*
 * R_DrawQ3Shadow
 */
void R_DrawQ3Shadow (entity_t * ent, float lheight, float s1, float c1, trace_t downtrace) {
    int           i, j, numtris, pose1, pose2;
    vec3_t        point1, point2, interpolated;
    md3header_t  *pmd3hdr;
    md3surface_t *pmd3surf;
    unsigned int *tris;
    md3vert_mem_t *verts;
    model_t      *clmodel = ent->model;
#if 0
    float         m[16];
    md3tag_t     *tag;
    tagentity_t  *tagent;
#endif

    pmd3hdr = (md3header_t *) Mod_Extradata (clmodel);

    pmd3surf = (md3surface_t *) ((byte *) pmd3hdr + pmd3hdr->ofssurfs);
    for (i = 0; i < pmd3hdr->numsurfs; i++) {
        verts = (md3vert_mem_t *) ((byte *) pmd3hdr + pmd3surf->ofsverts);
        tris = (unsigned int *) ((byte *) pmd3surf + pmd3surf->ofstris);
        numtris = pmd3surf->numtris * 3;
        pose1 = ent->pose1 * pmd3surf->numverts;
        pose2 = ent->pose2 * pmd3surf->numverts;

        glBegin (GL_TRIANGLES);
        for (j = 0; j < numtris; j++) {
            // normals and vertexes come from the frame list
            VectorCopy (verts[*tris + pose1].vec, point1);

            point1[0] -= shadevector[0] * (point1[2] + lheight);
            point1[1] -= shadevector[1] * (point1[2] + lheight);

            VectorCopy (verts[*tris + pose2].vec, point2);

            point2[0] -= shadevector[0] * (point2[2] + lheight);
            point2[1] -= shadevector[1] * (point2[2] + lheight);

            VectorInterpolate (point1, ent->framelerp, point2, interpolated);

            interpolated[2] = -(ent->origin[2] - downtrace.endpos[2]);

            interpolated[2] +=
                ((interpolated[1] * (s1 * downtrace.plane.normal[0])) -
                 (interpolated[0] * (c1 * downtrace.plane.normal[0])) -
                 (interpolated[0] * (s1 * downtrace.plane.normal[1])) -
                 (interpolated[1] * (c1 * downtrace.plane.normal[1]))) + ((1 - downtrace.plane.normal[2]) * 20) + 0.2;

            glVertex3fv (interpolated);

            tris++; // lxndr: was *tris++
        }
        glEnd ();

        pmd3surf = (md3surface_t *) ((byte *) pmd3surf + pmd3surf->ofsend);
    }

    if (!pmd3hdr->numtags)             // single model, done
        return;

    // no multimodel shadow support yet
#if 0
    tag = (md3tag_t *) ((byte *) pmd3hdr + pmd3hdr->ofstags);
    tag += ent->pose2 * pmd3hdr->numtags;
    for (i = 0; i < pmd3hdr->numtags; i++, tag++) {
        if (multimodel_level == 0 && !strcmp (tag->name, "tag_torso")) {
            tagent = &q3player_body;
            ent = &q3player_body.ent;
            multimodel_level++;
        } else if (multimodel_level == 1 && !strcmp (tag->name, "tag_head")) {
            tagent = &q3player_head;
            ent = &q3player_head.ent;
            multimodel_level++;
        } else {
            continue;
        }

        glPushMatrix ();
        R_RotateForTagEntity (tagent, tag, m);
        glMultMatrixf (m);
        R_DrawQ3Shadow (ent, lheight, s1, c1, downtrace);
        glPopMatrix ();
    }
#endif
}

#define	ADD_EXTRA_TEXTURE(_texture, _param)	\
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, _param);\
	GL_Bind (_texture);	\
	\
	glDepthMask (GL_FALSE);	\
	glEnable (GL_BLEND);	\
	glBlendFunc (GL_ONE, GL_ONE);	\
	\
	R_DrawQ3Frame (frame, pmd3hdr, pmd3surf, ent, INTERP_MAXDIST);\
	\
	glDisable (GL_BLEND);	\
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);	\
	glDepthMask (GL_TRUE);

/*
 * R_SetupQ3Frame
 */
void R_SetupQ3Frame (entity_t * ent) {
    int           i, j, frame, shadernum, texture, fb_texture;
    float         m[16];
    md3header_t  *pmd3hdr;
    md3surface_t *pmd3surf;
    md3tag_t     *tag;
    model_t      *clmodel = ent->model;
    tagentity_t  *tagent;

    if (!strcmp (clmodel->name, "progs/player/lower.md3"))
        frame = legsframe;
    else if (!strcmp (clmodel->name, "progs/player/upper.md3"))
        frame = bodyframe;
    else
        frame = ent->frame;

    // locate the proper data
    pmd3hdr = (md3header_t *) Mod_Extradata (clmodel);

    // draw all the triangles

    // draw non-transparent surfaces first, then the transparent ones
    for (i = 0; i < 2; i++) {
        pmd3surf = (md3surface_t *) ((byte *) pmd3hdr + pmd3hdr->ofssurfs);
        for (j = 0; j < pmd3hdr->numsurfs; j++) {
            md3shader_mem_t *shader;

            surface_transparent = (strstr (pmd3surf->name, "energy")
                                   || strstr (pmd3surf->name, "f_")
                                   || strstr (pmd3surf->name, "flare")
                                   || strstr (pmd3surf->name, "flash")
                                   || strstr (pmd3surf->name, "Sphere")
                                   || strstr (pmd3surf->name, "telep"));

            if ((!i && surface_transparent) || (i && !surface_transparent)) {
                pmd3surf = (md3surface_t *) ((byte *) pmd3surf + pmd3surf->ofsend);
                continue;
            }

            c_md3_polys += pmd3surf->numtris;

            shadernum = ent->skinnum;
            if ((shadernum >= pmd3surf->numshaders) || (shadernum < 0)) {
                Con_DPrintf ("R_SetupQ3Frame: no such skin # %d\n", shadernum);
                shadernum = 0;
            }

            shader = (md3shader_mem_t *) ((byte *) pmd3hdr + pmd3surf->ofsshaders);

            texture = shader[shadernum].gl_texnum;
            fb_texture = shader[shadernum].fb_texnum;

            if (fb_texture && gl_mtexable) {
                GL_DisableMultitexture ();
                glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
                GL_Bind (texture);

                GL_EnableMultitexture ();
                if (gl_add_ext) {
                    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
                    GL_Bind (fb_texture);
                }

                R_DrawQ3Frame (frame, pmd3hdr, pmd3surf, ent, INTERP_MAXDIST);

                if (!gl_add_ext) {
                    ADD_EXTRA_TEXTURE (fb_texture, GL_DECAL);
                }

                GL_DisableMultitexture ();
            } else {
                GL_DisableMultitexture ();
                glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
                GL_Bind (texture);

                R_DrawQ3Frame (frame, pmd3hdr, pmd3surf, ent, INTERP_MAXDIST);

                if (fb_texture) {
                    ADD_EXTRA_TEXTURE (fb_texture, GL_REPLACE);
                }
            }

            pmd3surf = (md3surface_t *) ((byte *) pmd3surf + pmd3surf->ofsend);
        }
    }

    if (!pmd3hdr->numtags)             // single model, done
        return;

    tag = (md3tag_t *) ((byte *) pmd3hdr + pmd3hdr->ofstags);
    tag += frame * pmd3hdr->numtags;
    for (i = 0; i < pmd3hdr->numtags; i++, tag++) {
        if (multimodel_level == 0 && !strcmp (tag->name, "tag_torso")) {
            tagent = &q3player_body;
            ent = &q3player_body.ent;
            multimodel_level++;
        } else if (multimodel_level == 1 && !strcmp (tag->name, "tag_head")) {
            tagent = &q3player_head;
            ent = &q3player_head.ent;
            multimodel_level++;
        } else {
            continue;
        }

        glPushMatrix ();
        R_RotateForTagEntity (tagent, tag, m);
        glMultMatrixf (m);
        R_SetupQ3Frame (ent);
        glPopMatrix ();
    }
}

/*
 * R_DrawQ3Model
 */
void R_DrawQ3Model (entity_t * ent) {
    vec3_t        mins, maxs, md3_scale_origin = { 0, 0, 0 };
    model_t      *clmodel = ent->model;

    if (clmodel->modhint == MOD_Q3TELEPORT)
        ent->origin[2] -= 30;

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

    if (ent->angles[0] || ent->angles[1] || ent->angles[2]) {
        if (R_CullSphere (ent->origin, clmodel->radius))
            return;
    } else {
        if (R_CullBox (mins, maxs))
            return;
    }

    VectorCopy (ent->origin, r_entorigin);
    VectorSubtract (r_origin, r_entorigin, modelorg);

    // get lighting information
    R_SetupLighting (ent);

    shadedots = r_avertexnormal_dots[((int) (ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

    glPushMatrix ();

    if (ent == &cl.viewent)
        R_RotateForViewEntity (ent);
    else
        R_RotateForEntity (ent, false);

    if (ent == &cl.viewent) {
        float         scale = 0.5 + bound (0, r_viewmodelsize.value, 1) / 2;

        glTranslatef (md3_scale_origin[0], md3_scale_origin[1], md3_scale_origin[2]);
        glScalef (scale, 1, 1);
    } else {
        glTranslatef (md3_scale_origin[0], md3_scale_origin[1], md3_scale_origin[2]);
    }

    if (gl_smoothmodels.value)
        glShadeModel (GL_SMOOTH);

    if (gl_affinemodels.value)
        glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

    if (!strcmp (clmodel->name, "progs/player/lower.md3")) {
        R_ReplaceQ3Frame (ent->frame);
        ent->noshadow = true;
    }

    multimodel_level = 0;
    R_SetupQ3Frame (ent);

    glShadeModel (GL_FLAT);
    if (gl_affinemodels.value)
        glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glPopMatrix ();

    if (r_shadows.value && !ent->noshadow) {
        int           farclip;
        float         theta, lheight, s1, c1;
        vec3_t        downmove;
        trace_t       downtrace;
        static float  shadescale = 0;

        farclip = max ((int) r_farclip.value, 4096);

        if (!shadescale)
            shadescale = 1 / sqrt (2);
        theta = -ent->angles[1] / 180 * M_PI;

        VectorSet (shadevector, cos (theta) * shadescale, sin (theta) * shadescale, shadescale);

        glPushMatrix ();

        R_RotateForEntity (ent, true);

        VectorCopy (ent->origin, downmove);
        downmove[2] -= farclip;
        memset (&downtrace, 0, sizeof (downtrace));
        SV_RecursiveHullCheck (cl.worldmodel->hulls, 0, 0, 1, ent->origin, downmove, &downtrace);

        lheight = ent->origin[2] - lightspot[2];

        s1 = sin (ent->angles[1] / 180 * M_PI);
        c1 = cos (ent->angles[1] / 180 * M_PI);

        glDepthMask (GL_FALSE);
        glDisable (GL_TEXTURE_2D);
        glEnable (GL_BLEND);
        glColor4f (0, 0, 0, (ambientlight - (mins[2] - downtrace.endpos[2])) / 150);
        if (gl_have_stencil && r_shadows.value == 2) {
            glEnable (GL_STENCIL_TEST);
            glStencilFunc (GL_EQUAL, 1, 2);
            glStencilOp (GL_KEEP, GL_KEEP, GL_INCR);
        }

        multimodel_level = 0;
        R_DrawQ3Shadow (ent, lheight, s1, c1, downtrace);

        glDepthMask (GL_TRUE);
        glEnable (GL_TEXTURE_2D);
        glDisable (GL_BLEND);
        if (gl_have_stencil && r_shadows.value == 2)
            glDisable (GL_STENCIL_TEST);

        glPopMatrix ();
    }

    glColor3ubv (color_white);
}

// 

void R_SetSpritesState (qboolean state) {
    static qboolean r_state = false;

    if (r_state == state)
        return;

    r_state = state;

    if (state) {
        GL_DisableMultitexture ();
        glEnable (GL_ALPHA_TEST);
    } else {
        glDisable (GL_ALPHA_TEST);
    }
}

void R_SetSpr32State (qboolean state) {
    static qboolean r_state = false;

    if (r_state == state)
        return;

    r_state = state;

    if (state) {
        GL_DisableMultitexture ();
        glEnable (GL_BLEND);
        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glDepthMask (GL_FALSE);
    } else {
        glDisable (GL_BLEND);
        glDepthMask (GL_TRUE);
    }
}

int SetFlameModelState (void) {
    if (!gl_part_flames.value && !strcmp (currententity->model->name, "progs/flame.mdl")) {     // lxndr: was flame0
        currententity->model = cl.model_precache[cl_modelindex[mi_flame1]];
    } else if (gl_part_flames.value) {
        vec3_t        liteorg;

        VectorCopy (currententity->origin, liteorg);
        if (currententity->baseline.modelindex == cl_modelindex[mi_flame0]) {
            if (gl_part_flames.value == 2) {
                liteorg[2] += 14;
                QMB_Q3TorchFlame (liteorg, 15);
            } else {
                liteorg[2] += 5.5;
                QMB_TorchFlame (liteorg, 7, 0.8);
            }
        } else if (currententity->baseline.modelindex == cl_modelindex[mi_flame0_md3]) {
            if (gl_part_flames.value == 2) {
                liteorg[2] += 12;
                QMB_Q3TorchFlame (liteorg, 15);
            } else {
                liteorg[2] += 3.5;
                QMB_TorchFlame (liteorg, 7, 0.8);
            }
        } else if (currententity->baseline.modelindex == cl_modelindex[mi_flame1]) {
            if (gl_part_flames.value == 2) {
                liteorg[2] += 14;
                QMB_Q3TorchFlame (liteorg, 15);
            } else {
                liteorg[2] += 5.5;
                QMB_TorchFlame (liteorg, 7, 0.8);
            }
            currententity->model = cl.model_precache[cl_modelindex[mi_flame0]];
        } else if (currententity->baseline.modelindex == cl_modelindex[mi_flame2]) {
            if (gl_part_flames.value == 2) {
                liteorg[2] += 14;
                QMB_Q3TorchFlame (liteorg, 32);
            } else {
                liteorg[2] -= 1;
                QMB_TorchFlame (liteorg, 12, 1);
            }
            return -1;                 // continue;
        }
    }

    return 0;
}

void SortEntitiesByTransparency (void) {
    int           i, j;
    entity_t     *tmp;

    for (i = 0; i < cl_numvisedicts; i++) {
        if (cl_visedicts[i]->istransparent) {
            for (j = cl_numvisedicts - 1; j > i; j--) {
                // if not transparent, exchange with transparent
                if (!(cl_visedicts[j]->istransparent)) {
                    tmp = cl_visedicts[i];
                    cl_visedicts[i] = cl_visedicts[j];
                    cl_visedicts[j] = tmp;
                    break;
                }
            }
            if (j == i)
                return;
        }
    }
}

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

    if (!r_drawentities.value)
        return;

    SortEntitiesByTransparency ();

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

        if (qmb_initialized && SetFlameModelState () == -1)
            continue;

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

            case mod_md3:
                R_DrawQ3Model (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_SetSpritesState (true);
                R_DrawSpriteModel (currententity);
                break;

            case mod_spr32:
                R_SetSpr32State (true);
                R_DrawSpriteModel (currententity);
                break;

            default:
                break;
        }
    }

    R_SetSpritesState (false);
    R_SetSpr32State (false);
}

/*
 * R_DrawViewModel
 */
void R_DrawViewModel (void) {
    currententity = &cl.viewent;

    if (!r_drawviewmodel.value || chase_active.value || !r_drawentities.value || (cl.stats[STAT_HEALTH] <= 0)
        || !currententity->model)
        return;

    currententity->transparency =
        (cl.items & IT_INVISIBILITY) ? gl_ringalpha.value : bound (0, r_drawviewmodel.value, 1);
    currententity->istransparent = true;

    // hack the depth range to prevent view model from poking into walls
    glDepthRange (gldepthmin, gldepthmin + 0.3 * (gldepthmax - gldepthmin));

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

        case mod_md3:
            R_DrawQ3Model (currententity);
            break;

        default:
            break;
    }

    glDepthRange (gldepthmin, gldepthmax);
}

/*
 * R_RenderBlur
 *
 */
void R_RenderBlur (void) {
    if (gl_blur.value) {
        int           vwidth = 1, vheight = 1;
        float         vs, vt, cs, ct;
        float         r, hscale;

        if (gl_nonpoweroftwo_ext) {    //we can use any size, supposedly
            vwidth = glwidth;
            vheight = glheight;
        } else {                       //limit the texture size to square and use padding.
            while (vwidth < glwidth)
                vwidth *= 2;
            while (vheight < glheight)
                vheight *= 2;
        }

        glViewport (glx, gly, glwidth, glheight);

        GL_Bind (blurtexture);

        // go 2d
        glMatrixMode (GL_PROJECTION);
        glPushMatrix ();
        glLoadIdentity ();
        glOrtho (0, glwidth, 0, glheight, -99999, 99999);
        glMatrixMode (GL_MODELVIEW);
        glPushMatrix ();
        glLoadIdentity ();

        //blend the last frame onto the scene
        //the maths is because our texture is over-sized (must be power of two)
        cs = vs = (float) glwidth / vwidth * 0.5;
        ct = vt = (float) glheight / vheight * 0.5;

        if (gl_blurhealth.value || cl.stats[STAT_HEALTH] <= 0) {
            hscale = (100.0 - cl.stats[STAT_HEALTH]) / 100;
            r = 0.999 - hscale * (gl_blurscale.value + 1) / 200;
        } else if (gl_blurscale.value) {
            r = 0.999 - gl_blurscale.value / 100;
        } else {
            r = 1;
        }
        vs *= r;
        vt *= r;

        glDisable (GL_DEPTH_TEST);
        glDisable (GL_CULL_FACE);
        glDisable (GL_ALPHA_TEST);
        glEnable (GL_BLEND);

        if (gl_blurhealth.value || cl.stats[STAT_HEALTH] <= 0)
            r = hscale * (gl_bluralpha.value + 1) / 2;
        else
            r = gl_bluralpha.value;
        r += (((int) v_fps + (int) v_fpsold) / 2 - DEFAULT_FPS) / cl_maxfps.value * 0.5;
        glColor4f (1, 1, 1, bound (0, r, 0.9));

        glBegin (GL_QUADS);
        glTexCoord2f (cs - vs, ct - vt);
        glVertex2f (0, 0);
        glTexCoord2f (cs + vs, ct - vt);
        glVertex2f (glwidth, 0);
        glTexCoord2f (cs + vs, ct + vt);
        glVertex2f (glwidth, glheight);
        glTexCoord2f (cs - vs, ct + vt);
        glVertex2f (0, glheight);
        glEnd ();
        glPopMatrix ();

        //copy the image into the texture so that we can play with it next frame too!
        glCopyTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, glx, gly, vwidth, vheight, 0);
        glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glPopMatrix ();
    }
}

/*
 * R_RenderFog
 *
 */
void R_RenderFog (void) {
    float         r, g, b, color, darkness, density, distance;
    if (gl_fog.value) {
        // darkness = powf(0.5, 1 + gl_fogdarkness.value * 10) / 2;
        // shift = darkness / (1 + gl_fogdarkness.value / 10);

        if (gl_fogcolor.value == 0.5) {
            r = g = b = gl_fogdarkness.value;
        } else {
            darkness = gl_fogdarkness.value * 1.1 - 1;
            color = (gl_fogcolor.value * 0.8 + 0.1) * M_PI;
            r = cosf (color / 2) + darkness;
            g = sinf (color) + darkness;
            b = sinf (color / 2) + darkness;
        }
        // Con_Printf ("r=%f g=%f b=%f darkness=%f\t", r, g, b, darkness);

        distance = 2048 * (0.5 + gl_fogdistance.value);
        density = 65536 / powf (2, gl_fogdensity.value * 10);
        // Con_Printf ("density=%f distance=%f\n", density, distance);

        GLfloat       c[4] = { r, g, b, 1.0 };
        glFogfv (GL_FOG_COLOR, c);
        glFogi (GL_FOG_MODE, GL_LINEAR);
        glFogf (GL_FOG_START, distance - 2048);
        glFogf (GL_FOG_END, distance + density);
        glHint (GL_FOG_HINT, GL_NICEST);
        //glClearColor (r, g, b, 1.0); // lxndr: no performance improvement ?

        glEnable (GL_FOG);
    } else {
        glDisable (GL_FOG);
    }
}

/*
 * R_PolyBlend
 */
void R_PolyBlend (void) {
    if ((vid_hwgamma_enabled && gl_hwblend.value) || !v_blend[3])
        return;

    glDisable (GL_ALPHA_TEST);
    glDisable (GL_TEXTURE_2D);
    glEnable (GL_BLEND);

    glColor4fv (v_blend);

    glBegin (GL_QUADS);
    glVertex2f (r_refdef.vrect.x, r_refdef.vrect.y);
    glVertex2f (r_refdef.vrect.x + r_refdef.vrect.width, r_refdef.vrect.y);
    glVertex2f (r_refdef.vrect.x + r_refdef.vrect.width, r_refdef.vrect.y + r_refdef.vrect.height);
    glVertex2f (r_refdef.vrect.x, r_refdef.vrect.y + r_refdef.vrect.height);
    glEnd ();

    glDisable (GL_BLEND);
    glEnable (GL_TEXTURE_2D);
    glEnable (GL_ALPHA_TEST);

    glColor3ubv (color_white);
}

/*
 * R_BrightenScreen
 */
void R_BrightenScreen (void) {
    float         f;
    extern float  vid_gamma;

    if (vid_hwgamma_enabled || v_contrast.value <= 1.0)
        return;

    f = min (v_contrast.value, 3);
    f = pow (f, vid_gamma);

    glDisable (GL_TEXTURE_2D);
    glEnable (GL_BLEND);
    glBlendFunc (GL_DST_COLOR, GL_ONE);

    glBegin (GL_QUADS);
    while (f > 1) {
        if (f >= 2)
            glColor3ubv (color_white);
        else
            glColor3f (f - 1, f - 1, f - 1);
        glVertex2f (0, 0);
        glVertex2f (vid.width, 0);
        glVertex2f (vid.width, vid.height);
        glVertex2f (0, vid.height);
        f *= 0.5;
    }
    glEnd ();

    glEnable (GL_TEXTURE_2D);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable (GL_BLEND);

    glColor3ubv (color_white);
}

/*
 * R_Q3DamageDraw
 */
void R_Q3DamageDraw (void) {
    float         scale, halfwidth, halfheight;
    vec3_t        center;
    extern float  damagetime, damagecount;

    if (!qmb_initialized || !gl_part_damagesplash.value || damagetime + 0.5 < cl.time) {
        return;
    } else if (damagetime > cl.time) {
        damagetime = 0;
        return;
    }

    glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    GL_Bind (damagetexture);

    glDisable (GL_ALPHA_TEST);
    glEnable (GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glPushMatrix ();

    center[0] = vid.width / 2;
    center[1] = vid.height / 2;
    scale = 1 + damagecount / 100;
    halfwidth = vid.width * scale / 2;
    halfheight = vid.height * scale / 2;
    glColor4f (1, 1, 1, 0.8 * (damagetime + 0.5 - cl.time));

    glBegin (GL_QUADS);
    glTexCoord2f (0, 0);
    glVertex2f (center[0] - halfwidth, center[1] - halfheight);
    glTexCoord2f (0, 1);
    glVertex2f (center[0] + halfwidth, center[1] - halfheight);
    glTexCoord2f (1, 1);
    glVertex2f (center[0] + halfwidth, center[1] + halfheight);
    glTexCoord2f (1, 0);
    glVertex2f (center[0] - halfwidth, center[1] + halfheight);
    glEnd ();

    glPopMatrix ();

    glDisable (GL_BLEND);
    glEnable (GL_ALPHA_TEST);

    glColor3ubv (color_white);
}

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;

    // rotate VPN right by FOV_X/2 degrees
    RotatePointAroundVector (frustum[0].normal, vup, vpn, -(90 - r_refdef.fov_x / 2));
    // rotate VPN left by FOV_X/2 degrees
    RotatePointAroundVector (frustum[1].normal, vup, vpn, 90 - r_refdef.fov_x / 2);
    // rotate VPN up by FOV_X/2 degrees
    RotatePointAroundVector (frustum[2].normal, vright, vpn, 90 - r_refdef.fov_y / 2);
    // rotate VPN down by FOV_X/2 degrees
    RotatePointAroundVector (frustum[3].normal, vright, vpn, -(90 - r_refdef.fov_y / 2));

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

/*
 * R_SetupFrame
 */
void R_SetupFrame (void) {
    vec3_t        testorigin;
    mleaf_t      *leaf;

    if (nehahra) {
        if (r_oldsky.value && r_skybox.string[0])
            Cvar_Set (&r_skybox, "");
        if (!r_oldsky.value && !r_skybox.string[0])
            Cvar_Set (&r_skybox, prev_skybox);
    }

    R_AnimateLight ();

    r_framecount++;

    // build the transformation matrix for the given view angles
    VectorCopy (r_refdef.vieworg, r_origin);
    AngleVectors (r_refdef.viewangles, vpn, vright, vup);

    // current viewleaf
    r_oldviewleaf = r_viewleaf;
    r_oldviewleaf2 = r_viewleaf2;

    r_viewleaf = Mod_PointInLeaf (r_origin, cl.worldmodel);
    r_viewleaf2 = NULL;

    // check above and below so crossing solid water doesn't draw wrong
    if (r_viewleaf->contents <= CONTENTS_WATER && r_viewleaf->contents >= CONTENTS_LAVA) {
        // look up a bit
        VectorCopy (r_origin, testorigin);
        testorigin[2] += 10;
        leaf = Mod_PointInLeaf (testorigin, cl.worldmodel);
        if (leaf->contents == CONTENTS_EMPTY)
            r_viewleaf2 = leaf;
    } else if (r_viewleaf->contents == CONTENTS_EMPTY) {
        // look down a bit
        VectorCopy (r_origin, testorigin);
        testorigin[2] -= 10;
        leaf = Mod_PointInLeaf (testorigin, cl.worldmodel);
        if (leaf->contents <= CONTENTS_WATER && leaf->contents >= CONTENTS_LAVA)
            r_viewleaf2 = leaf;
    }

    V_SetContentsColor (r_viewleaf->contents);
    // V_AddWaterfog (r_viewleaf->contents);
    if (nehahra)
        Neh_SetupFrame ();
    V_CalcBlend ();

    r_cache_thrash = false;
}

__inline void MYgluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) {
    GLdouble      xmin, xmax, ymin, ymax;

    ymax = zNear * tan (fovy * M_PI / 360.0);
    ymin = -ymax;

    xmin = ymin * aspect;
    xmax = ymax * aspect;

    glFrustum (xmin, xmax, ymin, ymax, zNear, zFar);
}

/*
 * R_SetupGL
 */
void R_SetupGL (void) {
    float         screenaspect;
    int           x, x2, y2, y, w, h, farclip;

    // set up viewpoint
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    x = r_refdef.vrect.x * glwidth / vid.width;
    x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * glwidth / vid.width;
    y = (vid.height - r_refdef.vrect.y) * glheight / vid.height;
    y2 = (vid.height - (r_refdef.vrect.y + r_refdef.vrect.height)) * glheight / vid.height;

    w = x2 - x;
    h = y - y2;

    glViewport (glx + x, gly + y2, w, h);

    screenaspect = (float) r_refdef.vrect.width / r_refdef.vrect.height;
    farclip = max ((int) r_farclip.value, 4096);
    MYgluPerspective (r_refdef.fov_y, screenaspect, 4, farclip);

    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.vieworg[0], -r_refdef.vieworg[1], -r_refdef.vieworg[2]);

    glGetFloatv (GL_MODELVIEW_MATRIX, r_world_matrix);

    // set drawing parms
    if (gl_cull.value)
        glEnable (GL_CULL_FACE);
    else
        glDisable (GL_CULL_FACE);

    glDisable (GL_BLEND);
    glDisable (GL_ALPHA_TEST);
    glEnable (GL_DEPTH_TEST);
}

/*
 * R_Init
 */
void R_Init (void) {
    void          R_ToggleParticles_f (void);

    Cmd_AddCommand ("timerefresh", R_TimeRefresh_f);
    Cmd_AddCommand ("pointfile", R_ReadPointFile_f);
    Cmd_AddCommand ("toggleparticles", R_ToggleParticles_f);
    Cmd_AddCommand ("set_interpolated_weapon", Set_Interpolated_Weapon_f);

    Cvar_Register (&r_lightmap);
    Cvar_Register (&r_fullbright);
    Cvar_Register (&r_drawentities);
    Cvar_Register (&r_drawviewmodel);
    Cvar_Register (&r_viewmodelsize);
    Cvar_Register (&r_shadows);
    Cvar_Register (&r_novis);
    Cvar_Register (&r_speeds);
    Cvar_Register (&r_fullbrightskins);
    Cvar_Register (&r_fastsky);
    Cvar_Register (&r_skycolor);
    Cvar_Register (&r_drawflame);
    Cvar_Register (&r_skybox);
    Cvar_Register (&r_farclip);

    Cvar_Register (&r_alpha);
    Cvar_Register (&r_alphalava);
    Cvar_Register (&r_alphaslime);
    Cvar_Register (&r_alphateleport);
    Cvar_Register (&r_alphawater);

    Cvar_Register (&r_dynamic);
    Cvar_Register (&r_dynamicintensity);
    Cvar_Register (&r_dynamicradius);

    Cvar_Register (&r_quality);

    Cvar_Register (&gl_finish);
    Cvar_Register (&gl_clear);
    Cvar_Register (&gl_cull);
    Cvar_Register (&gl_ztrick);
    Cvar_Register (&gl_smoothmodels);
    Cvar_Register (&gl_affinemodels);
    Cvar_Register (&gl_polyblend);
    Cvar_Register (&gl_playermip);
    Cvar_Register (&gl_nocolors);
    Cvar_Register (&gl_loadlits);
    Cvar_Register (&gl_doubleeyes);
    Cvar_Register (&gl_interdist);
    Cvar_Register (&gl_detail);
    Cvar_Register (&gl_caustics);
    Cvar_Register (&gl_ringalpha);
    Cvar_Register (&gl_fb_bmodels);
    Cvar_Register (&gl_fb_models);
    Cvar_Register (&gl_solidparticles);
    Cvar_Register (&gl_vertexlights);
    Cvar_Register (&gl_shownormals);
    Cvar_Register (&gl_loadq3models);
    Cvar_Register (&gl_lerptextures);

    Cvar_Register (&gl_anisotropy);
    Cvar_Register (&gl_antialiasing);

    Cvar_Register (&gl_bloom);
    Cvar_Register (&gl_bloomdarken);
    Cvar_Register (&gl_bloomalpha);
    Cvar_Register (&gl_bloomdiamondsize);
    Cvar_Register (&gl_bloomintensity);
    Cvar_Register (&gl_bloomsamplesize);
    Cvar_Register (&gl_bloomfastsample);

    Cvar_Register (&gl_blur);
    Cvar_Register (&gl_bluralpha);
    Cvar_Register (&gl_blurhealth);
    Cvar_Register (&gl_blurscale);

    Cvar_Register (&gl_dlightintensity);
    Cvar_Register (&gl_dlightradius);

    Cvar_Register (&gl_fog);
    Cvar_Register (&gl_fogcolor);
    Cvar_Register (&gl_fogdarkness);
    Cvar_Register (&gl_fogdensity);
    Cvar_Register (&gl_fogdistance);

    Cvar_Register (&gl_part_blobs);
    Cvar_Register (&gl_part_blood);
    Cvar_Register (&gl_part_damagesplash);
    Cvar_Register (&gl_part_explosions);
    Cvar_Register (&gl_part_flames);
    Cvar_Register (&gl_part_gunshots);
    Cvar_Register (&gl_part_lavasplash);
    Cvar_Register (&gl_part_lightning);
    Cvar_Register (&gl_part_muzzleflash);
    Cvar_Register (&gl_part_spikes);
    Cvar_Register (&gl_part_telesplash);
    Cvar_Register (&gl_part_trails);

    Cmd_AddLegacyCommand ("loadsky", "r_skybox");
#ifdef __LEGACY__
    Cmd_AddLegacyCommand ("r_wateralpha", "r_alphawater");
#endif

    // this minigl driver seems to slow us down if the particles are drawn WITHOUT Z buffer bits
    if (!strcmp (gl_vendor, "METABYTE/WICKED3D"))
        Cvar_SetDefault (&gl_solidparticles, 1);

    if (!gl_allow_ztrick)
        Cvar_SetDefault (&gl_ztrick, 0);

    Ambience_Init ();

    R_InitTextures ();
    R_InitBubble ();
    R_InitParticles ();
    R_InitSmokes ();
    R_InitVertexLights ();
    R_InitDecals ();

    R_InitOtherTextures ();
    R_InitBloomTextures ();

    blurtexture = texture_extension_number++;

    playertextures = texture_extension_number;
    texture_extension_number += 16;

    // fullbright skins
    texture_extension_number += 16;

    skyboxtextures = texture_extension_number;
    texture_extension_number += 6;
}

/*
 * 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
    S_ExtraUpdate ();                  // don't let sound get messed up if going slow
    R_DrawWorld ();                    // adds static entities to the list
    S_ExtraUpdate ();                  // don't let sound get messed up if water
    R_DrawEntitiesOnList ();
    R_DrawWaterSurfaces ();
    R_SmokeFrame ();
    GL_DisableMultitexture ();
}

int           gl_ztrickframe = 0;

/*
 * R_Clear
 */
void R_Clear (void) {
    int           clearbits = 0;

    if (gl_clear.value || (!vid_hwgamma_enabled && v_contrast.value > 1))
        clearbits |= GL_COLOR_BUFFER_BIT;

    if (gl_ztrick.value) {
        if (clearbits)
            glClear (clearbits);

        gl_ztrickframe = !gl_ztrickframe;
        if (gl_ztrickframe) {
            gldepthmin = 0;
            gldepthmax = 0.49999;
            glDepthFunc (GL_LEQUAL);
        } else {
            gldepthmin = 1;
            gldepthmax = 0.5;
            glDepthFunc (GL_GEQUAL);
        }
    } else {
        clearbits |= GL_DEPTH_BUFFER_BIT;
        glClear (clearbits);
        gldepthmin = 0;
        gldepthmax = 1;
        glDepthFunc (GL_LEQUAL);
    }

    glDepthRange (gldepthmin, gldepthmax);

    if (r_shadows.value == 2) {
        glClearStencil (GL_TRUE);
        glClear (GL_STENCIL_BUFFER_BIT);
    }
}

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

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

    if (r_speeds.value) {
        glFinish ();
        time1 = Sys_DoubleTime ();
        c_brush_polys = c_alias_polys = c_md3_polys = 0;
    }

    if (gl_finish.value)
        glFinish ();

    R_Clear ();

    // render normal view

    R_RenderScene ();
    R_DrawDecals ();
    R_RenderDlights ();
    R_DrawParticles ();
    R_DrawViewModel ();
    R_RenderFog ();
    R_BloomBlend ();
    R_RenderBlur ();

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