/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2010-2014 QuakeSpasm developers

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.

*/
// r_main.c

#include "quakedef.hpp"

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

//johnfitz -- rendering statistics
int rs_brushpolys, rs_aliaspolys, rs_skypolys;
int rs_dynamiclightmaps, rs_brushpasses, rs_aliaspasses, rs_skypasses;

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

float r_fovx, r_fovy; //johnfitz -- rendering fov may be different becuase of r_waterwarp and r_stereo

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

mleaf_t *r_viewleaf, *r_oldviewleaf;

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


convar r_norefresh{"r_norefresh", "0"};
convar r_drawentities{"r_drawentities", "1"};
convar r_drawviewmodel{"r_drawviewmodel", "1"};
convar r_speeds{"r_speeds", "0"};
convar r_pos{"r_pos", "0"};
convar r_fullbright{"r_fullbright", "0"};
convar r_lightmap{"r_lightmap", "0"};
convar r_shadows{"r_shadows", "0", {.archive = true}};
convar r_wateralpha{"r_wateralpha", "1", {.archive = true}};
convar r_litwater{"r_litwater", "1"};
convar r_dynamic{"r_dynamic", "1", {.archive = true}};
convar r_novis{"r_novis", "0", {.archive = true}};

convar gl_finish{"gl_finish", "0"};
convar gl_clear{"gl_clear", "1"};
convar gl_cull{"gl_cull", "1"};
convar gl_smoothmodels{"gl_smoothmodels", "1"};
convar gl_affinemodels{"gl_affinemodels", "0"};
convar gl_polyblend{"gl_polyblend", "1"};
convar gl_flashblend{"gl_flashblend", "0", {.archive = true}};
convar gl_playermip{"gl_playermip", "0"};
convar gl_nocolors{"gl_nocolors", "0"};

//johnfitz -- new cvars
convar r_stereo{"r_stereo", "0"};
convar r_stereodepth{"r_stereodepth", "128"};
convar r_clearcolor{"r_clearcolor", "2", {.archive = true}};
convar r_drawflat{"r_drawflat", "0"};
convar r_flatlightstyles{"r_flatlightstyles", "0"};
convar gl_fullbrights{"gl_fullbrights", "1", {.archive = true}};
convar gl_farclip{"gl_farclip", "65536", {.archive = true}};
convar gl_overbright{"gl_overbright", "1", {.archive = true}};
convar gl_overbright_models{"gl_overbright_models", "1", {.archive = true}};
convar r_oldskyleaf{"r_oldskyleaf", "0"};
convar r_drawworld{"r_drawworld", "1"};
convar r_showtris{"r_showtris", "0"};
convar r_showbboxes{"r_showbboxes", "0"};
convar r_lerpmodels{"r_lerpmodels", "1"};
convar r_lerpmove{"r_lerpmove", "1"};
convar r_nolerp_list{
    "r_nolerp_list",
    "progs/flame.mdl,progs/flame2.mdl,progs/braztall.mdl,progs/brazshrt.mdl,progs/longtrch.mdl,progs/flame_pyre.mdl,progs/v_saw.mdl,progs/v_xfist.mdl,progs/h2stuff/newfire.mdl"
};
convar r_noshadow_list{
    "r_noshadow_list",
    "progs/flame2.mdl,progs/flame.mdl,progs/bolt1.mdl,progs/bolt2.mdl,progs/bolt3.mdl,progs/laser.mdl"
};

extern convar r_vfog;
//johnfitz

convar gl_zfix{"gl_zfix", "0"}; // QuakeSpasm z-fighting fix

convar r_lavaalpha{"r_lavaalpha", "0"};
convar r_telealpha{"r_telealpha", "0"};
convar r_slimealpha{"r_slimealpha", "0"};

float map_wateralpha, map_lavaalpha, map_telealpha, map_slimealpha;

bool r_drawflat_cheatsafe, r_fullbright_cheatsafe, r_lightmap_cheatsafe, r_drawworld_cheatsafe; //johnfitz

convar r_scale{"r_scale", "1", {.archive = true}};

//==============================================================================
//
// GLSL GAMMA CORRECTION
//
//==============================================================================

static GLuint r_gamma_texture;
static GLuint r_gamma_program;
static int r_gamma_texture_width, r_gamma_texture_height;

// uniforms used in gamma shader
static GLint gammaLoc;
static GLint contrastLoc;
static GLint textureLoc;

/*
=============
GLSLGamma_DeleteTexture
=============
*/
void GLSLGamma_DeleteTexture(void) {
    glDeleteTextures(1, &r_gamma_texture);
    r_gamma_texture = 0;
    r_gamma_program = 0; // deleted in R_DeleteShaders
}

/*
=============
GLSLGamma_CreateShaders
=============
*/
static void GLSLGamma_CreateShaders(void) {
    const GLchar *vertSource =
            "#version 110\n"
            "\n"
            "void main(void) {\n"
            "	gl_Position = vec4(gl_Vertex.xy, 0.0, 1.0);\n"
            "	gl_TexCoord[0] = gl_MultiTexCoord0;\n"
            "}\n";

    const GLchar *fragSource =
            "#version 110\n"
            "\n"
            "uniform sampler2D GammaTexture;\n"
            "uniform float GammaValue;\n"
            "uniform float ContrastValue;\n"
            "\n"
            "void main(void) {\n"
            "	  vec4 frag = texture2D(GammaTexture, gl_TexCoord[0].xy);\n"
            "	  frag.rgb = frag.rgb * ContrastValue;\n"
            "	  gl_FragColor = vec4(pow(frag.rgb, vec3(GammaValue)), 1.0);\n"
            "}\n";

    if (!gl_glsl_gamma_able)
        return;

    r_gamma_program = GL_CreateProgram(vertSource, fragSource, 0, NULL);

    // get uniform locations
    gammaLoc = GL_GetUniformLocation(&r_gamma_program, "GammaValue");
    contrastLoc = GL_GetUniformLocation(&r_gamma_program, "ContrastValue");
    textureLoc = GL_GetUniformLocation(&r_gamma_program, "GammaTexture");
}

/*
=============
GLSLGamma_GammaCorrect
=============
*/
void GLSLGamma_GammaCorrect(void) {
    float smax, tmax;

    if (!gl_glsl_gamma_able)
        return;

    if (vid_gamma.value == 1 && vid_contrast.value == 1)
        return;

    // create render-to-texture texture if needed
    if (!r_gamma_texture) {
        glGenTextures(1, &r_gamma_texture);
        glBindTexture(GL_TEXTURE_2D, r_gamma_texture);

        r_gamma_texture_width = glwidth;
        r_gamma_texture_height = glheight;

        if (!gl_texture_NPOT) {
            r_gamma_texture_width = TexMgr_Pad(r_gamma_texture_width);
            r_gamma_texture_height = TexMgr_Pad(r_gamma_texture_height);
        }

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, r_gamma_texture_width, r_gamma_texture_height, 0, GL_BGRA,
                     GL_UNSIGNED_INT_8_8_8_8_REV, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    }

    // create shader if needed
    if (!r_gamma_program) {
        GLSLGamma_CreateShaders();
        if (!r_gamma_program) {
            Sys_Error("GLSLGamma_CreateShaders failed");
        }
    }

    // copy the framebuffer to the texture
    GL_DisableMultitexture();
    glBindTexture(GL_TEXTURE_2D, r_gamma_texture);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, glx, gly, glwidth, glheight);

    // draw the texture back to the framebuffer with a fragment shader
    GL_UseProgramFunc(r_gamma_program);
    GL_Uniform1fFunc(gammaLoc, vid_gamma.value);
    GL_Uniform1fFunc(contrastLoc, std::min(2.0f, std::max(1.0f, vid_contrast.value)));
    GL_Uniform1iFunc(textureLoc, 0); // use texture unit 0

    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);

    glViewport(glx, gly, glwidth, glheight);

    smax = glwidth / (float) r_gamma_texture_width;
    tmax = glheight / (float) r_gamma_texture_height;

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex2f(-1, -1);
    glTexCoord2f(smax, 0);
    glVertex2f(1, -1);
    glTexCoord2f(smax, tmax);
    glVertex2f(1, 1);
    glTexCoord2f(0, tmax);
    glVertex2f(-1, 1);
    glEnd();

    GL_UseProgramFunc(0);

    // clear cached binding
    GL_ClearBindings();
}

/*
=================
R_CullBox -- johnfitz -- replaced with new function from lordhavoc

Returns true if the box is completely outside the frustum
=================
*/
bool R_CullBox(vec3_t emins, vec3_t emaxs) {
    int i;
    mplane_t *p;
    byte signbits;
    float vec[3];

    for (i = 0; i < 4; i++) {
        p = frustum + i;
        signbits = p->signbits;
        vec[0] = ((signbits & 1) ? emins : emaxs)[0];
        vec[1] = ((signbits & 2) ? emins : emaxs)[1];
        vec[2] = ((signbits & 4) ? emins : emaxs)[2];
        if (p->normal[0] * vec[0] + p->normal[1] * vec[1] + p->normal[2] * vec[2] < p->dist)
            return true;
    }
    return false;
}

/*
===============
R_CullModelForEntity -- johnfitz -- uses correct bounds based on rotation
===============
*/
bool R_CullModelForEntity(entity_t *e) {
    vec3_t mins, maxs;
    vec_t scalefactor, *minbounds, *maxbounds;

    if (e->angles[0] || e->angles[2]) //pitch or roll
    {
        minbounds = e->model->rmins;
        maxbounds = e->model->rmaxs;
    } else if (e->angles[1]) //yaw
    {
        minbounds = e->model->ymins;
        maxbounds = e->model->ymaxs;
    } else //no rotation
    {
        minbounds = e->model->mins;
        maxbounds = e->model->maxs;
    }

    scalefactor = ENTSCALE_DECODE(e->scale);
    if (scalefactor != 1.0f) {
        VectorMA(e->origin, scalefactor, minbounds, mins);
        VectorMA(e->origin, scalefactor, maxbounds, maxs);
    } else {
        VectorAdd(e->origin, minbounds, mins);
        VectorAdd(e->origin, maxbounds, maxs);
    }

    return R_CullBox(mins, maxs);
}

/*
===============
R_RotateForEntity -- johnfitz -- modified to take origin and angles instead of pointer to entity
===============
*/
void R_RotateForEntity(vec3_t origin, vec3_t angles, unsigned char scale) {
    float scalefactor = ENTSCALE_DECODE(scale);
    glTranslatef(origin[0], origin[1], origin[2]);
    glRotatef(angles[1], 0, 0, 1);
    glRotatef(-angles[0], 0, 1, 0);
    glRotatef(angles[2], 1, 0, 0);
    if (scalefactor != 1.0f)
        glScalef(scalefactor, scalefactor, scalefactor);
}

/*
=============
GL_PolygonOffset -- johnfitz

negative offset moves polygon closer to camera
=============
*/
void GL_PolygonOffset(int offset) {
    if (offset > 0) {
        glEnable(GL_POLYGON_OFFSET_FILL);
        glEnable(GL_POLYGON_OFFSET_LINE);
        glPolygonOffset(1, offset);
    } else if (offset < 0) {
        glEnable(GL_POLYGON_OFFSET_FILL);
        glEnable(GL_POLYGON_OFFSET_LINE);
        glPolygonOffset(-1, offset);
    } else {
        glDisable(GL_POLYGON_OFFSET_FILL);
        glDisable(GL_POLYGON_OFFSET_LINE);
    }
}

//==============================================================================
//
// SETUP FRAME
//
//==============================================================================

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;
}

/*
===============
TurnVector -- johnfitz

turn forward towards side on the plane defined by forward and side
if angle = 90, the result will be equal to side
assumes side and forward are perpendicular, and normalized
to turn away from side, use a negative angle
===============
*/
void TurnVector(vec3_t out, const vec3_t forward, const vec3_t side, float angle) {
    float scale_forward, scale_side;

    scale_forward = cos(DEG2RAD(angle));
    scale_side = sin(DEG2RAD(angle));

    out[0] = scale_forward * forward[0] + scale_side * side[0];
    out[1] = scale_forward * forward[1] + scale_side * side[1];
    out[2] = scale_forward * forward[2] + scale_side * side[2];
}

/*
===============
R_SetFrustum -- johnfitz -- rewritten
===============
*/
void R_SetFrustum(float fovx, float fovy) {
    int i;

    if (r_stereo.value)
        fovx += 10; //silly hack so that polygons don't drop out becuase of stereo skew

    TurnVector(frustum[0].normal, vpn, vright, fovx / 2 - 90); //left plane
    TurnVector(frustum[1].normal, vpn, vright, 90 - fovx / 2); //right plane
    TurnVector(frustum[2].normal, vpn, vup, 90 - fovy / 2); //bottom plane
    TurnVector(frustum[3].normal, vpn, vup, fovy / 2 - 90); //top plane

    for (i = 0; i < 4; i++) {
        frustum[i].type = PLANE_ANYZ;
        frustum[i].dist = DotProduct(r_origin, frustum[i].normal); //FIXME: shouldn't this always be zero?
        frustum[i].signbits = SignbitsForPlane(&frustum[i]);
    }
}

/*
=============
GL_SetFrustum -- johnfitz -- written to replace MYgluPerspective
=============
*/
#define NEARCLIP 4
float frustum_skew = 0.0; //used by r_stereo
void GL_SetFrustum(float fovx, float fovy) {
    float xmax, ymax;
    xmax = NEARCLIP * tan(fovx * M_PI / 360.0);
    ymax = NEARCLIP * tan(fovy * M_PI / 360.0);
    glFrustum(-xmax + frustum_skew, xmax + frustum_skew, -ymax, ymax, NEARCLIP, gl_farclip.value);
}

/*
=============
R_SetupGL
=============
*/
void R_SetupGL(void) {
    int scale;

    //johnfitz -- rewrote this section
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    scale = std::clamp((int)r_scale.value, 1, 4); // ericw -- see R_ScaleView
    glViewport(glx + r_refdef.vrect.x,
               gly + glheight - r_refdef.vrect.y - r_refdef.vrect.height,
               r_refdef.vrect.width / scale,
               r_refdef.vrect.height / scale);
    //johnfitz

    GL_SetFrustum(r_fovx, r_fovy); //johnfitz -- use r_fov* vars

    //	glCullFace(GL_BACK); //johnfitz -- glquake used CCW with backwards culling -- let's do it right

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

    //
    // 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_Clear -- johnfitz -- rewritten and gutted
=============
*/
void R_Clear(void) {
    unsigned int clearbits;

    clearbits = GL_DEPTH_BUFFER_BIT;
    // from mh -- if we get a stencil buffer, we should clear it, even though we don't use it
    if (gl_stencilbits)
        clearbits |= GL_STENCIL_BUFFER_BIT;
    if (gl_clear.value)
        clearbits |= GL_COLOR_BUFFER_BIT;
    glClear(clearbits);
}

/*
===============
R_SetupScene -- johnfitz -- this is the stuff that needs to be done once per eye in stereo mode
===============
*/
void R_SetupScene(void) {
    R_SetupGL();
}

/*
===============
R_SetupView -- johnfitz -- this is the stuff that needs to be done once per frame, even in stereo mode
===============
*/
void R_SetupView(void) {
    // Need to do those early because we now update dynamic light maps during R_MarkSurfaces
    R_PushDlights();
    R_AnimateLight();
    r_framecount++;

    Fog_SetupFrame(); //johnfitz

    // 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_viewleaf = Mod_PointInLeaf(r_origin, cl.worldmodel);

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

    //johnfitz -- calculate r_fovx and r_fovy here
    r_fovx = r_refdef.fov_x;
    r_fovy = r_refdef.fov_y;
    if (r_waterwarp.value) {
        int contents = Mod_PointInLeaf(r_origin, cl.worldmodel)->contents;
        if (contents == CONTENTS_WATER || contents == CONTENTS_SLIME || contents == CONTENTS_LAVA) {
            //variance is a percentage of width, where width = 2 * tan(fov / 2) otherwise the effect is too dramatic at high FOV and too subtle at low FOV.  what a mess!
            r_fovx = atan(tan(DEG2RAD(r_refdef.fov_x) / 2) * (0.97 + sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180;
            r_fovy = atan(tan(DEG2RAD(r_refdef.fov_y) / 2) * (1.03 - sin(cl.time * 1.5) * 0.03)) * 2 / M_PI_DIV_180;
        }
    }
    //johnfitz

    R_SetFrustum(r_fovx, r_fovy); //johnfitz -- use r_fov* vars

    R_MarkSurfaces(); //johnfitz -- create texture chains from PVS

    R_UpdateWarpTextures(); //johnfitz -- do this before R_Clear

    R_Clear();

    //johnfitz -- cheat-protect some draw modes
    r_drawflat_cheatsafe = r_fullbright_cheatsafe = r_lightmap_cheatsafe = false;
    r_drawworld_cheatsafe = true;
    if (cl.maxclients == 1) {
        if (!r_drawworld.value) r_drawworld_cheatsafe = false;

        if (r_drawflat.value) r_drawflat_cheatsafe = true;
        else if (r_fullbright.value || !cl.worldmodel->lightdata) r_fullbright_cheatsafe = true;
        else if (r_lightmap.value) r_lightmap_cheatsafe = true;
    }
    //johnfitz
}

//==============================================================================
//
// RENDER VIEW
//
//==============================================================================

/*
=============
R_DrawEntitiesOnList
=============
*/
void R_DrawEntitiesOnList(bool alphapass) //johnfitz -- added parameter
{
    int i;

    if (!r_drawentities.value)
        return;

    //johnfitz -- sprites are not a special case
    for (i = 0; i < cl_numvisedicts; i++) {
        currententity = cl_visedicts[i];

        //johnfitz -- if alphapass is true, draw only alpha entites this time
        //if alphapass is false, draw only nonalpha entities this time
        if ((ENTALPHA_DECODE(currententity->alpha) < 1 && !alphapass) ||
            (ENTALPHA_DECODE(currententity->alpha) == 1 && alphapass))
            continue;

        //johnfitz -- chasecam
        if (currententity == &cl_entities[cl.viewentity])
            currententity->angles[0] *= 0.3;
        //johnfitz

        switch (currententity->model->type) {
            case mod_alias:
                R_DrawAliasModel(currententity);
                break;
            case mod_brush:
                R_DrawBrushModel(currententity);
                break;
            case mod_sprite:
                R_DrawSpriteModel(currententity);
                break;
        }
    }
}

/*
=============
R_DrawViewModel -- johnfitz -- gutted
=============
*/
void R_DrawViewModel(void) {
    if (!r_drawviewmodel.value || !r_drawentities.value || chase_active.value)
        return;

    if (cl.items & IT_INVISIBILITY || cl.stats[STAT_HEALTH] <= 0)
        return;

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

    //johnfitz -- this fixes a crash
    if (currententity->model->type != mod_alias)
        return;
    //johnfitz

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

/*
================
R_EmitWirePoint -- johnfitz -- draws a wireframe cross shape for point entities
================
*/
void R_EmitWirePoint(vec3_t origin) {
    const int size = 8;

    glBegin(GL_LINES);
    glVertex3f(origin[0] - size, origin[1], origin[2]);
    glVertex3f(origin[0] + size, origin[1], origin[2]);
    glVertex3f(origin[0], origin[1] - size, origin[2]);
    glVertex3f(origin[0], origin[1] + size, origin[2]);
    glVertex3f(origin[0], origin[1], origin[2] - size);
    glVertex3f(origin[0], origin[1], origin[2] + size);
    glEnd();
}

/*
================
R_EmitWireBox -- johnfitz -- draws one axis aligned bounding box
================
*/
void R_EmitWireBox(vec3_t mins, vec3_t maxs) {
    glBegin(GL_QUAD_STRIP);
    glVertex3f(mins[0], mins[1], mins[2]);
    glVertex3f(mins[0], mins[1], maxs[2]);
    glVertex3f(maxs[0], mins[1], mins[2]);
    glVertex3f(maxs[0], mins[1], maxs[2]);
    glVertex3f(maxs[0], maxs[1], mins[2]);
    glVertex3f(maxs[0], maxs[1], maxs[2]);
    glVertex3f(mins[0], maxs[1], mins[2]);
    glVertex3f(mins[0], maxs[1], maxs[2]);
    glVertex3f(mins[0], mins[1], mins[2]);
    glVertex3f(mins[0], mins[1], maxs[2]);
    glEnd();
}

/*
================
R_ShowBoundingBoxes -- johnfitz

draw bounding boxes -- the server-side boxes, not the renderer cullboxes
================
*/
void R_ShowBoundingBoxes(void) {
    extern edict_t *sv_player;
    vec3_t mins, maxs;
    edict_t *ed;
    int i;

    if (!r_showbboxes.value || cl.maxclients > 1 || !r_drawentities.value || !sv.active)
        return;

    glDisable(GL_DEPTH_TEST);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    GL_PolygonOffset(OFFSET_SHOWTRIS);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_CULL_FACE);
    glColor3f(1, 1, 1);

    for (i = 1, ed = NEXT_EDICT(sv.edicts); i < sv.num_edicts; i++, ed = NEXT_EDICT(ed)) {
        if (ed == sv_player || ed->free)
            continue; //don't draw player's own bbox or freed edicts

        //		if (r_showbboxes.value != 2)
        //			if (!SV_VisibleToClient (sv_player, ed, sv.worldmodel))
        //				continue; //don't draw if not in pvs

        if (ed->v.mins[0] == ed->v.maxs[0] && ed->v.mins[1] == ed->v.maxs[1] && ed->v.mins[2] == ed->v.maxs[2]) {
            //point entity
            R_EmitWirePoint(ed->v.origin);
        } else {
            //box entity
            VectorAdd(ed->v.mins, ed->v.origin, mins);
            VectorAdd(ed->v.maxs, ed->v.origin, maxs);
            R_EmitWireBox(mins, maxs);
        }
    }

    glColor3f(1, 1, 1);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_CULL_FACE);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    GL_PolygonOffset(OFFSET_NONE);
    glEnable(GL_DEPTH_TEST);

    Sbar_Changed(); //so we don't get dots collecting on the statusbar
}

/*
================
R_ShowTris -- johnfitz
================
*/
void R_ShowTris(void) {
    extern convar r_particles;
    int i;

    if (r_showtris.value < 1 || r_showtris.value > 2 || cl.maxclients > 1)
        return;

    if (r_showtris.value == 1)
        glDisable(GL_DEPTH_TEST);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    GL_PolygonOffset(OFFSET_SHOWTRIS);
    glDisable(GL_TEXTURE_2D);
    glColor3f(1, 1, 1);
    //	glEnable (GL_BLEND);
    //	glBlendFunc (GL_ONE, GL_ONE);

    if (r_drawworld.value) {
        R_DrawWorld_ShowTris();
    }

    if (r_drawentities.value) {
        for (i = 0; i < cl_numvisedicts; i++) {
            currententity = cl_visedicts[i];

            if (currententity == &cl_entities[cl.viewentity]) // chasecam
                currententity->angles[0] *= 0.3;

            switch (currententity->model->type) {
                case mod_brush:
                    R_DrawBrushModel_ShowTris(currententity);
                    break;
                case mod_alias:
                    R_DrawAliasModel_ShowTris(currententity);
                    break;
                case mod_sprite:
                    R_DrawSpriteModel(currententity);
                    break;
                default:
                    break;
            }
        }

        // viewmodel
        currententity = &cl.viewent;
        if (r_drawviewmodel.value
            && !chase_active.value
            && cl.stats[STAT_HEALTH] > 0
            && !(cl.items & IT_INVISIBILITY)
            && currententity->model
            && currententity->model->type == mod_alias) {
            glDepthRange(0, 0.3);
            R_DrawAliasModel_ShowTris(currententity);
            glDepthRange(0, 1);
        }
    }

    if (r_particles.value) {
        R_DrawParticles_ShowTris();
    }

    //	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //	glDisable (GL_BLEND);
    glColor3f(1, 1, 1);
    glEnable(GL_TEXTURE_2D);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    GL_PolygonOffset(OFFSET_NONE);
    if (r_showtris.value == 1)
        glEnable(GL_DEPTH_TEST);

    Sbar_Changed(); //so we don't get dots collecting on the statusbar
}

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

    if (!r_shadows.value || !r_drawentities.value || r_drawflat_cheatsafe || r_lightmap_cheatsafe)
        return;

    // Use stencil buffer to prevent self-intersecting shadows, from Baker (MarkV)
    if (gl_stencilbits) {
        glClear(GL_STENCIL_BUFFER_BIT);
        glStencilFunc(GL_EQUAL, 0, ~0);
        glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
        glEnable(GL_STENCIL_TEST);
    }

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

        if (currententity->model->type != mod_alias)
            continue;

        if (currententity == &cl.viewent)
            return;

        GL_DrawAliasShadow(currententity);
    }

    if (gl_stencilbits) {
        glDisable(GL_STENCIL_TEST);
    }
}

/*
================
R_RenderScene
================
*/
void R_RenderScene(void) {
    R_SetupScene(); //johnfitz -- this does everything that should be done once per call to RenderScene

    Fog_EnableGFog(); //johnfitz

    Sky_DrawSky(); //johnfitz

    R_DrawWorld();

    S_ExtraUpdate(); // don't let sound get messed up if going slow

    R_DrawShadows(); //johnfitz -- render entity shadows

    R_DrawEntitiesOnList(false); //johnfitz -- false means this is the pass for nonalpha entities

    R_DrawWorld_Water(); //johnfitz -- drawn here since they might have transparency

    R_DrawEntitiesOnList(true); //johnfitz -- true means this is the pass for alpha entities

    R_RenderDlights(); //triangle fan dlights -- johnfitz -- moved after water

    R_DrawParticles();

    Fog_DisableGFog(); //johnfitz

    R_DrawViewModel(); //johnfitz -- moved here from R_RenderView

    R_ShowTris(); //johnfitz

    R_ShowBoundingBoxes(); //johnfitz
}

static GLuint r_scaleview_texture;
static int r_scaleview_texture_width, r_scaleview_texture_height;

/*
=============
R_ScaleView_DeleteTexture
=============
*/
void R_ScaleView_DeleteTexture(void) {
    glDeleteTextures(1, &r_scaleview_texture);
    r_scaleview_texture = 0;
}

/*
================
R_ScaleView

The r_scale cvar allows rendering the 3D view at 1/2, 1/3, or 1/4 resolution.
This function scales the reduced resolution 3D view back up to fill
r_refdef.vrect. This is for emulating a low-resolution pixellated look,
or possibly as a perforance boost on slow graphics cards.
================
*/
void R_ScaleView(void) {
    float smax, tmax;
    int scale;
    int srcx, srcy, srcw, srch;

    // copied from R_SetupGL()
    scale = std::clamp((int)r_scale.value, 1, 4);
    srcx = glx + r_refdef.vrect.x;
    srcy = gly + glheight - r_refdef.vrect.y - r_refdef.vrect.height;
    srcw = r_refdef.vrect.width / scale;
    srch = r_refdef.vrect.height / scale;

    if (scale == 1)
        return;

    // make sure texture unit 0 is selected
    GL_DisableMultitexture();

    // create (if needed) and bind the render-to-texture texture
    if (!r_scaleview_texture) {
        glGenTextures(1, &r_scaleview_texture);

        r_scaleview_texture_width = 0;
        r_scaleview_texture_height = 0;
    }
    glBindTexture(GL_TEXTURE_2D, r_scaleview_texture);

    // resize render-to-texture texture if needed
    if (r_scaleview_texture_width < srcw
        || r_scaleview_texture_height < srch) {
        r_scaleview_texture_width = srcw;
        r_scaleview_texture_height = srch;

        if (!gl_texture_NPOT) {
            r_scaleview_texture_width = TexMgr_Pad(r_scaleview_texture_width);
            r_scaleview_texture_height = TexMgr_Pad(r_scaleview_texture_height);
        }

        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, r_scaleview_texture_width, r_scaleview_texture_height, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    }

    // copy the framebuffer to the texture
    glBindTexture(GL_TEXTURE_2D, r_scaleview_texture);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, srcx, srcy, srcw, srch);

    // draw the texture back to the framebuffer
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glDisable(GL_BLEND);

    glViewport(srcx, srcy, r_refdef.vrect.width, r_refdef.vrect.height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // correction factor if we lack NPOT textures, normally these are 1.0f
    smax = srcw / (float) r_scaleview_texture_width;
    tmax = srch / (float) r_scaleview_texture_height;

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0);
    glVertex2f(-1, -1);
    glTexCoord2f(smax, 0);
    glVertex2f(1, -1);
    glTexCoord2f(smax, tmax);
    glVertex2f(1, 1);
    glTexCoord2f(0, tmax);
    glVertex2f(-1, 1);
    glEnd();

    // clear cached binding
    GL_ClearBindings();
}

/*
================
R_RenderView
================
*/
void R_RenderView(void) {
    double time1, time2;

    if (r_norefresh.value)
        return;

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

    time1 = 0; /* avoid compiler warning */
    if (r_speeds.value) {
        glFinish();
        time1 = Sys_DoubleTime();

        //johnfitz -- rendering statistics
        rs_brushpolys = rs_aliaspolys = rs_skypolys =
                                        rs_dynamiclightmaps = rs_aliaspasses = rs_skypasses = rs_brushpasses = 0;
    } else if (gl_finish.value)
        glFinish();

    R_SetupView(); //johnfitz -- this does everything that should be done once per frame

    //johnfitz -- stereo rendering -- full of hacky goodness
    if (r_stereo.value) {
        float eyesep = std::clamp(r_stereo.value, -8.0f, 8.0f);
        float fdepth = std::clamp(r_stereodepth.value, 32.0f, 1024.0f);

        AngleVectors(r_refdef.viewangles, vpn, vright, vup);

        //render left eye (red)
        glColorMask(1, 0, 0, 1);
        VectorMA(r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg);
        frustum_skew = 0.5 * eyesep * NEARCLIP / fdepth;
        srand((int) (cl.time * 1000)); //sync random stuff between eyes

        R_RenderScene();

        //render right eye (cyan)
        glClear(GL_DEPTH_BUFFER_BIT);
        glColorMask(0, 1, 1, 1);
        VectorMA(r_refdef.vieworg, 1.0f * eyesep, vright, r_refdef.vieworg);
        frustum_skew = -frustum_skew;
        srand((int) (cl.time * 1000)); //sync random stuff between eyes

        R_RenderScene();

        //restore
        glColorMask(1, 1, 1, 1);
        VectorMA(r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg);
        frustum_skew = 0.0f;
    } else {
        R_RenderScene();
    }
    //johnfitz

    R_ScaleView();

    //johnfitz -- modified r_speeds output
    time2 = Sys_DoubleTime();
    if (r_pos.value)
        console::info("x %i y %i z %i (pitch %i yaw %i roll %i)\n",
                   (int) cl_entities[cl.viewentity].origin[0],
                   (int) cl_entities[cl.viewentity].origin[1],
                   (int) cl_entities[cl.viewentity].origin[2],
                   (int) cl.viewangles[PITCH],
                   (int) cl.viewangles[YAW],
                   (int) cl.viewangles[ROLL]);
    else if (r_speeds.value == 2)
        console::info("%3i ms  %4i/%4i wpoly %4i/%4i epoly %3i lmap %4i/%4i sky %1.1f mtex\n",
                   (int) ((time2 - time1) * 1000),
                   rs_brushpolys,
                   rs_brushpasses,
                   rs_aliaspolys,
                   rs_aliaspasses,
                   rs_dynamiclightmaps,
                   rs_skypolys,
                   rs_skypasses,
                   TexMgr_FrameUsage());
    else if (r_speeds.value)
        console::info("%3i ms  %4i wpoly %4i epoly %3i lmap\n",
                   (int) ((time2 - time1) * 1000),
                   rs_brushpolys,
                   rs_aliaspolys,
                   rs_dynamiclightmaps);
    //johnfitz
}
