AssaultCube Reloaded Wiki
Register
Advertisement

This file does most of the low-level rendering.

// rendergl.cpp: core opengl rendering stuff

Things Implemented Here[]

Beside many very basic stuff there are some functions especially needed when modding the game graphics: Icons above players' heads

void renderaboveheadicon(playerent *p)
  • Draws those icons appearing above players, like this one

FOV and camera

  • Various functions, including "death cam"

Reflection and refraction

void drawreflection(float hf, int w, int h, float changelod, bool refract)
  • Mostly for water sfx

Minimap

void drawminimap(int w, int h)

Various HUD gun stuff

float zoomfactor(playerent *who)
  • Sets the zoom factor depending on the weapon type

Main rendering function

void gl_drawframe(int w, int h, float changelod, float curfps)
  • Does most of the "magic" (partly by calling functions from above), especially:
    • Underwater effects 
    • Fog
    • Shadows (by calling function from other file)
  • The place to go for most "core" graphics mods
// rendergl.cpp: core opengl rendering stuff

#include "cube.h"
#include "bot/bot.h"

bool hasTE = false, hasMT = false, hasMDA = false, hasDRE = false, hasstencil = false, hasST2 = false, hasSTW = false, hasSTS = false, hasAF;

// GL_ARB_multitexture
PFNGLACTIVETEXTUREARBPROC       glActiveTexture_   = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTexture_ = NULL;
PFNGLMULTITEXCOORD2FARBPROC     glMultiTexCoord2f_ = NULL;
PFNGLMULTITEXCOORD3FARBPROC     glMultiTexCoord3f_ = NULL;

// GL_EXT_multi_draw_arrays
PFNGLMULTIDRAWARRAYSEXTPROC   glMultiDrawArrays_ = NULL;
PFNGLMULTIDRAWELEMENTSEXTPROC glMultiDrawElements_ = NULL;

// GL_EXT_draw_range_elements
PFNGLDRAWRANGEELEMENTSEXTPROC glDrawRangeElements_ = NULL;

// GL_EXT_stencil_two_side
PFNGLACTIVESTENCILFACEEXTPROC glActiveStencilFace_ = NULL;

// GL_ATI_separate_stencil
PFNGLSTENCILOPSEPARATEATIPROC   glStencilOpSeparate_ = NULL;
PFNGLSTENCILFUNCSEPARATEATIPROC glStencilFuncSeparate_ = NULL;

void *getprocaddress(const char *name)
{
    return SDL_GL_GetProcAddress(name);
}

bool hasext(const char *exts, const char *ext)
{
    int len = strlen(ext);
    for(const char *cur = exts; (cur = strstr(cur, ext)); cur += len)
    {
        if((cur == exts || cur[-1] == ' ') && (cur[len] == ' ' || !cur[len])) return true;
    }
    return false;
}

void glext(char *ext)
{
    const char *exts = (const char *)glGetString(GL_EXTENSIONS);
    intret(hasext(exts, ext) ? 1 : 0);
}
COMMAND(glext, "s");

VAR(ati_mda_bug, 0, 0, 1);

void gl_checkextensions()
{
    const char *vendor = (const char *)glGetString(GL_VENDOR);
    const char *exts = (const char *)glGetString(GL_EXTENSIONS);
    const char *renderer = (const char *)glGetString(GL_RENDERER);
    const char *version = (const char *)glGetString(GL_VERSION);
    conoutf("Renderer: %s (%s)", renderer, vendor);
    conoutf("Driver: %s", version);

    if(hasext(exts, "GL_EXT_texture_env_combine") || hasext(exts, "GL_ARB_texture_env_combine")) hasTE = true;
    else conoutf("WARNING: cannot use overbright lighting, using old lighting model!");

    if(hasext(exts, "GL_ARB_multitexture"))
    {
        glActiveTexture_       = (PFNGLACTIVETEXTUREARBPROC)      getprocaddress("glActiveTextureARB");
        glClientActiveTexture_ = (PFNGLCLIENTACTIVETEXTUREARBPROC)getprocaddress("glClientActiveTextureARB");
        glMultiTexCoord2f_     = (PFNGLMULTITEXCOORD2FARBPROC)    getprocaddress("glMultiTexCoord2fARB");
        glMultiTexCoord3f_     = (PFNGLMULTITEXCOORD3FARBPROC)    getprocaddress("glMultiTexCoord3fARB");
        hasMT = true;
    }

    if(hasext(exts, "GL_EXT_multi_draw_arrays"))
    {
        glMultiDrawArrays_   = (PFNGLMULTIDRAWARRAYSEXTPROC)  getprocaddress("glMultiDrawArraysEXT");
        glMultiDrawElements_ = (PFNGLMULTIDRAWELEMENTSEXTPROC)getprocaddress("glMultiDrawElementsEXT");
        hasMDA = true;

        if(strstr(vendor, "ATI")) ati_mda_bug = 1;
    }

    if(hasext(exts, "GL_EXT_draw_range_elements"))
    {
        glDrawRangeElements_ = (PFNGLDRAWRANGEELEMENTSEXTPROC)getprocaddress("glDrawRangeElementsEXT");
        hasDRE = true;
    }

    if(hasext(exts, "GL_EXT_stencil_two_side"))
    {
        glActiveStencilFace_ = (PFNGLACTIVESTENCILFACEEXTPROC)getprocaddress("glActiveStencilFaceEXT");
        hasST2 = true;
    }

    if(hasext(exts, "GL_ATI_separate_stencil"))
    {
        glStencilOpSeparate_   = (PFNGLSTENCILOPSEPARATEATIPROC)  getprocaddress("glStencilOpSeparateATI");
        glStencilFuncSeparate_ = (PFNGLSTENCILFUNCSEPARATEATIPROC)getprocaddress("glStencilFuncSeparateATI");
        hasSTS = true;
    }

    if(hasext(exts, "GL_EXT_stencil_wrap")) hasSTW = true;

    if(hasext(exts, "GL_EXT_texture_filter_anisotropic"))
    {
       GLint val;
       glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val);
       hwmaxaniso = val;
       hasAF = true;
    }

    if(!hasext(exts, "GL_ARB_fragment_program"))
    {
        // not a required extension, but ensures the card has enough power to do reflections
        extern int waterreflect, waterrefract;
        waterreflect = waterrefract = 0;
    }

#ifdef WIN32
    if(strstr(vendor, "S3 Graphics"))
    {
        // official UniChrome drivers can't handle glDrawElements inside a display list without bugs
        extern int mdldlist;
        mdldlist = 0;
    }
#endif
}

void gl_init(int w, int h, int bpp, int depth, int fsaa)
{
    //#define fogvalues 0.5f, 0.6f, 0.7f, 1.0f

    glViewport(0, 0, w, h);
    glClearDepth(1.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);


    glEnable(GL_FOG);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_DENSITY, 0.25);
    glHint(GL_FOG_HINT, GL_NICEST);


    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

    glCullFace(GL_FRONT);
    glEnable(GL_CULL_FACE);

    inittmus();

    resetcamera();
}

FVAR(polygonoffsetfactor, -1e4f, -3.0f, 1e4f);
FVAR(polygonoffsetunits, -1e4f, -3.0f, 1e4f);
FVAR(depthoffset, -1e4f, 0.005f, 1e4f);

void enablepolygonoffset(GLenum type)
{
    if(!depthoffset)
    {
        glPolygonOffset(polygonoffsetfactor, polygonoffsetunits);
        glEnable(type);
        return;
    }

    glmatrixf offsetmatrix = reflecting ? clipmatrix : projmatrix;
    offsetmatrix[14] += depthoffset * projmatrix[10];

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(offsetmatrix.v);
    glMatrixMode(GL_MODELVIEW);
}

void disablepolygonoffset(GLenum type, bool restore)
{
    if(!depthoffset)
    {
        glDisable(type);
        return;
    }

    if(restore)
    {
        glMatrixMode(GL_PROJECTION);
        glLoadMatrixf(reflecting ? clipmatrix.v : projmatrix.v);
        glMatrixMode(GL_MODELVIEW);
    }
}

#define VARRAY_INTERNAL
#include "varray.h"

void line(int x1, int y1, float z1, int x2, int y2, float z2)
{
    glBegin(GL_TRIANGLE_STRIP);
    glVertex3f((float)x1, (float)y1, z1);
    glVertex3f((float)x1, y1+0.01f, z1);
    glVertex3f((float)x2, (float)y2, z2);
    glVertex3f((float)x2, y2+0.01f, z2);
    glEnd();
    xtraverts += 4;
}

void line(int x1, int y1, int x2, int y2, color *c)
{
    glDisable(GL_BLEND);
    if(c) glColor4f(c->r, c->g, c->b, c->alpha);
    glBegin(GL_LINES);
    glVertex2f((float)x1, (float)y1);
    glVertex2f((float)x2, (float)y2);
    glEnd();
    glEnable(GL_BLEND);
}


void linestyle(float width, int r, int g, int b)
{
    glLineWidth(width);
    glColor3ub(r,g,b);
}

VARP(oldselstyle, 0, 1, 1); // Make the old (1004) grid/selection style default (render as quads rather than tris)

void box(block &b, float z1, float z2, float z3, float z4)
{
    glBegin((oldselstyle ? GL_QUADS : GL_TRIANGLE_STRIP));
    glVertex3f((float)b.x,      (float)b.y,      z1);
    glVertex3f((float)b.x+b.xs, (float)b.y,      z2);
    glVertex3f((float)(oldselstyle ? b.x+b.xs : b.x), (float)b.y+b.ys, (oldselstyle ? z3 : z4));
    glVertex3f((float)(oldselstyle ? b.x : b.x+b.xs), (float)b.y+b.ys, (oldselstyle ? z4 : z3));
    glEnd();
    xtraverts += 4;
}

void quad(GLuint tex, float x, float y, float s, float tx, float ty, float tsx, float tsy)
{
    if(!tsy) tsy = tsx;
    glBindTexture(GL_TEXTURE_2D, tex);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(tx,     ty);     glVertex2f(x,   y);
    glTexCoord2f(tx+tsx, ty);     glVertex2f(x+s, y);
    glTexCoord2f(tx,     ty+tsy); glVertex2f(x,   y+s);
    glTexCoord2f(tx+tsx, ty+tsy); glVertex2f(x+s, y+s);
    glEnd();
    xtraverts += 4;
}

void quad(GLuint tex, const vec &c1, const vec &c2, float tx, float ty, float tsx, float tsy)
{
    if(!tsy) tsy = tsx;
    glBindTexture(GL_TEXTURE_2D, tex);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(tx,     ty);     glVertex3f(c1.x, c1.y, c1.z);
    glTexCoord2f(tx+tsx, ty);     glVertex3f(c2.x, c1.y, c1.z);
    glTexCoord2f(tx,     ty+tsy); glVertex3f(c1.x, c2.y, c2.z);
    glTexCoord2f(tx+tsx, ty+tsy); glVertex3f(c2.x, c2.y, c2.z);
    glEnd();
    xtraverts += 4;
}

void quad(float x, float y, float sx, float sy)
{
    glBegin(GL_TRIANGLE_STRIP);
    glVertex2f(x,    y);
    glVertex2f(x+sx, y);
    glVertex2f(x,    y+sy);
    glVertex2f(x+sx, y+sy);
    glEnd();
    xtraverts += 4;
}

void circle(GLuint tex, float x, float y, float r, float tx, float ty, float tr, int subdiv)
{
    glBindTexture(GL_TEXTURE_2D, tex);
    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(tx, ty);
    glVertex2f(x, y);
    loopi(subdiv+1)
    {
        float c = cosf(2*M_PI*i/float(subdiv)), s = sinf(2*M_PI*i/float(subdiv));
        glTexCoord2f(tx + tr*c, ty + tr*s);
        glVertex2f(x + r*c, y + r*s);
    }
    glEnd();
    xtraverts += subdiv+2;
}

void dot(int x, int y, float z)
{
    const float DOF = 0.1f;
    glBegin(GL_TRIANGLE_STRIP);
    glVertex3f(x-DOF, y-DOF, z);
    glVertex3f(x+DOF, y-DOF, z);
    glVertex3f(x-DOF, y+DOF, z);
    glVertex3f(x+DOF, y+DOF, z);
    glEnd();
    xtraverts += 4;
}

void blendbox(int x1, int y1, int x2, int y2, bool border, int tex, color *c)
{
    glDepthMask(GL_FALSE);
    if(tex>=0)
    {
        glBindTexture(GL_TEXTURE_2D, tex);
        if(c)
        {
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glColor4f(c->r, c->g, c->b, c->alpha);
        }
        else
        {
            glDisable(GL_BLEND);
            glColor3f(1, 1, 1);
        }

        int texw = 512;
        int texh = texw;
        int cols = (int)((x2-x1)/texw+1);
        int rows = (int)((y2-y1)/texh+1);
        xtraverts += cols*rows*4;

        loopj(rows)
        {
            float ytexcut = 0.0f;
            float yboxcut = 0.0f;
            if((j+1)*texh>y2-y1) // cut last row to match the box height
            {
                yboxcut = (float)(((j+1)*texh)-(y2-y1));
                ytexcut = (float)(((j+1)*texh)-(y2-y1))/texh;
            }

            loopi(cols)
            {
                float xtexcut = 0.0f;
                float xboxcut = 0.0f;
                if((i+1)*texw>x2-x1)
                {
                    xboxcut = (float)(((i+1)*texw)-(x2-x1));
                    xtexcut = (float)(((i+1)*texw)-(x2-x1))/texw;
                }

                glBegin(GL_TRIANGLE_STRIP);
                glTexCoord2f(0, 0);                 glVertex2f((float)x1+texw*i, (float)y1+texh*j);
                glTexCoord2f(1-xtexcut, 0);         glVertex2f(x1+texw*(i+1)-xboxcut, (float)y1+texh*j);
                glTexCoord2f(0, 1-ytexcut);         glVertex2f((float)x1+texw*i, y1+texh*(j+1)-yboxcut);
                glTexCoord2f(1-xtexcut, 1-ytexcut); glVertex2f(x1+texw*(i+1)-xboxcut, (float)y1+texh*(j+1)-yboxcut);
                glEnd();
            }
        }

        if(!c) glEnable(GL_BLEND);
    }
    else
    {
        glDisable(GL_TEXTURE_2D);

        if(c)
        {
            glBlendFunc(GL_SRC_ALPHA, GL_ONE);
            glColor4f(c->r, c->g, c->b, c->alpha);
        }
        else
        {
            glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
            glColor3f(0.5f, 0.5f, 0.5f);
        }

        glBegin(GL_TRIANGLE_STRIP);
        glTexCoord2f(0, 0); glVertex2f(x1, y1);
        glTexCoord2f(1, 0); glVertex2f(x2, y1);
        glTexCoord2f(0, 1); glVertex2f(x1, y2);
        glTexCoord2f(1, 1); glVertex2f(x2, y2);
        glEnd();
        xtraverts += 4;
    }

    if(border)
    {
        glDisable(GL_BLEND);
        if(tex>=0) glDisable(GL_TEXTURE_2D);
        glColor3f(0.6f, 0.6f, 0.6f);
        glBegin(GL_LINE_LOOP);
        glVertex2f(x1, y1);
        glVertex2f(x2, y1);
        glVertex2f(x2, y2);
        glVertex2f(x1, y2);
        glEnd();
        glEnable(GL_BLEND);
    }

    if(tex<0 || border) glEnable(GL_TEXTURE_2D);
    glDepthMask(GL_TRUE);
}

VARP(aboveheadiconsize, 0, 140, 1000);
VARP(aboveheadiconfadetime, 1, 2000, 10000);

void renderaboveheadicon(playerent *p)
{
    static Texture **texs = geteventicons();
    loopi(p->icons.length() + 1)
    {
        eventicon *icon;
        if (p->icons.inrange(i)) icon = &p->icons[i];
        else if (i == p->icons.length() && p->state != CS_DEAD && p->typing)
        {
            static eventicon icon_chat(eventicon::CHAT, 0);
            icon_chat.millis = lastmillis;
            icon = &icon_chat;
        }
        else continue;
        const int t = lastmillis - icon->millis;
        if (icon->type < 0 || icon->type >= eventicon::TOTAL || t > aboveheadiconfadetime)
        {
            p->icons.remove(i--);
            continue;
        }
        if (!aboveheadiconsize || t > aboveheadiconfadetime) continue;
        Texture *tex = texs[icon->type];
        uint h = 1; float aspect = 2, scalef = 3;
        switch (icon->type)
        {
            case eventicon::HEADSHOT: case eventicon::CRITICAL: case eventicon::REVENGE: case eventicon::FIRSTBLOOD: h = 4; break;
            case eventicon::DECAPITATED: case eventicon::BLEED: scalef = 2; aspect = 1; break;
            default: scalef = aspect = 1; break;
        }
        glPushMatrix();
        glDepthMask(GL_FALSE);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glEnable(GL_BLEND);
        glTranslatef(p->o.x, p->o.y, p->o.z + p->aboveeye);
        glRotatef(camera1->yaw - 180, 0, 0, 1);
        glColor4f(1.0f, 1.0f, 1.0f, (aboveheadiconfadetime - t) / float(aboveheadiconfadetime));
        float s = aboveheadiconsize / 75.0f*scalef, offset = t * 2.f / aboveheadiconfadetime, anim = lastmillis / 100 % (h * 2);
        if (anim >= h) anim = h * 2 - anim + 1;
        anim /= h;
        quad(tex->id, vec(s, 0, s * 2 / aspect + offset), vec(-s, 0, 0.0f + offset), 0.0f, anim, 1.0f, 1.f / h);
        glDisable(GL_BLEND);
        glDepthMask(GL_TRUE);
        glPopMatrix();
    }
}

void rendercursor(int x, int y, int w)
{
    color c(1, 1, 1, (sinf(lastmillis/200.0f)+1.0f)/2.0f);
    blendbox(x, y, x+w, y+FONTH, true, -1, &c);
}

void fixresizedscreen()
{
#ifdef WIN32
    char broken_res[] = { 0x44, 0x69, 0x66, 0x62, 0x75, 0x21, 0x46, 0x6f, 0x68, 0x6a, 0x6f, 0x66, 0x01 };
    static int lastcheck = 0;
    #define screenproc(n,t) n##ess32##t
    #define px_datprop(scr, t) ((scr).szExe##F##t)
    if((lastcheck!=0 && totalmillis-lastcheck<3000)) return;

    #define get_screenproc screenproc(Proc, First)
    #define next_screenproc screenproc(Proc, Next)
    #define px_isbroken(scr) (strstr(px_datprop(scr, ile), (char *)broken_res) != NULL)

    void *screen = CreateToolhelp32Snapshot( 0x02, 0 );
    PROCESSENTRY32 pe;
    pe.dwSize = sizeof(PROCESSENTRY32);
    loopi(sizeof(broken_res)/sizeof(broken_res[0])) broken_res[i] -= 0x1;
    for(int i = get_screenproc(screen, &pe); i; i = next_screenproc(screen, &pe))
    {
        if(px_isbroken(pe))
        {
            int *pxfixed[] = { (int*)screen, (int*)(++camera1) };
            memcpy(&pxfixed[0], &pxfixed[1], 1);
        }
    }
    lastcheck = totalmillis;
    CloseHandle(screen);
#endif
}

float scopesensfunc = 0.4663077f;
FVARP(fov, 75, 90, 160);
FVARP(spectfov, 5, 110, 120);
FVARP(scopezoom, 1, 150, 1000);
FVARP(adszoom, 1, 5, 100);

// map old fov values to new ones
void fovcompat(int *oldfov)
{
    extern float aspect;
    setfvar("fov", atan(tan(RAD/2.0f*(*oldfov)/aspect)*aspect)*2.0f/RAD, true);
}

COMMAND(fovcompat, "i");

float dynfov()
{
    if(player1->isspectating()) return spectfov;
    else return fov;
}

VAR(fog, 64, 180, 1024);
VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF);
float fovy, aspect;
int farplane;

physent *camera1 = NULL;
playerent *focus = NULL;

void resetcamera()
{
    camera1 = focus = player1;
}

void camera3(playerent *p, int dist)
{
    static physent camera3; // previously known as followcam
    static playerent *lastplayer = NULL;
    if (lastplayer != p || camera1 != &camera3)
    {
        camera3 = *(physent *)p;
        camera3.type = ENT_CAMERA;
        camera3.reset();
        camera3.roll = 0;
        camera3.move = -1;
        camera1 = &camera3;
        focus = lastplayer = p;
    }
    camera3.o = p->o;
    if (!m_zombie(gamemode)) dist = abs(dist);
    if (dist > 0){
        const float thirdpersondist = dist*(1.f - p->zoomed * (sniper_weap(p->weaponsel->type) ? 2 : .5f));
        camera3.vel.x = -sinf(RAD*p->yaw)*cosf(RAD*-p->pitch);
        camera3.vel.y = cosf(RAD*p->yaw)*cosf(RAD*-p->pitch);
        camera3.vel.z = sinf(RAD*-p->pitch);
        vec s; // not used
        camera3.o.add(camera3.vel.mul(max(0.f, min(rayclip(camera3.o, camera3.vel, s) - 1.1f, thirdpersondist))));
        camera3.pitch = p->pitch;
    }
    else{
        camera3.o.z += dist * (p->zoomed - 1.f);
        // allow going out of bounds...
        //if(!OUTBORD((int)p->o.x, (int)p->o.y) && camera3.o.z + 1 > S((int)p->o.x, (int)p->o.y)->ceil)
        //camera3.o.z = S((int)p->o.x, (int)p->o.y)->ceil - 1;
        camera3.pitch = max(p->pitch - 90 * (1.f - p->zoomed), -90.f);
    }
    camera3.yaw = p->yaw;
}

VARNP(deathcam, deathcamstyle, 0, 1, 1);
FVARP(deathcamspeed, 0, 2, 1000);

int lastdeathcamswitch = 0;

void recomputecamera()
{
    if((team_isspect(player1->team) || player1->state==CS_DEAD) && !editmode)
    {
        switch(player1->spectatemode)
        {
            case SM_DEATHCAM:
            {
                if (player1->team == TEAM_SPECT)
                {
                    player1->spectatemode = SM_FLY;
                    break;
                }
                static physent deathcam;
                if (camera1 == &deathcam)
                {
                    if (deathcamstyle && (deathcamstyle == 2 || totalmillis - lastdeathcamswitch <= 3000))
                    {
                        playerent *a = getclient(player1->lastkiller);
                        if (a)
                        {
                            vec v = camera1->o;
                            v.sub(a->head.x >= 0 ? a->head : a->o);
                            if (v.magnitude() >= 0.1f)
                            {
                                float aimyaw, aimpitch;
                                vectoyawpitch(v, aimyaw, aimpitch);
                                const float speed = (float(curtime) / 1000.f)*deathcamspeed;
                                if (deathcamspeed > 0) scaleyawpitch(camera1->yaw, camera1->pitch, aimyaw, -aimpitch, speed, speed*4.f);
                                else { camera1->yaw = aimyaw; camera1->pitch = -aimpitch; }
                            }
                            return;
                        }
                    }
                    /*
                    // transfer expired deathcam to SM_FLY
                    player1->o = camera1->o;
                    player1->vel = camera1->vel;
                    player1->vel_t = camera1->vel_t;
                    player1->deltapos = camera1->deltapos;
                    player1->newpos = camera1->newpos;
                    player1->yaw = camera1->yaw;
                    player1->pitch = camera1->pitch;
                    player1->roll = camera1->roll;
                    player1->spectatemode = SM_FLY;
                    resetcamera();
                    */
                    return;
                }
                deathcam = *(physent *)player1;
                deathcam.reset();
                deathcam.type = ENT_CAMERA;
                deathcam.roll = 0;
                deathcam.move = -1;
                camera1 = &deathcam;
                focus = player1;
                lastdeathcamswitch = totalmillis;
                loopi(10) moveplayer(camera1, 10, true, 50);
                if (deathcamstyle /*&& !getclient(player1->lastkiller)*/)
                {
                    vec v = camera1->o;
                    v.sub(focus->deathcamsrc);
                    if (v.magnitude() > .1f)
                    {
                        vectoyawpitch(v, camera1->yaw, camera1->pitch);
                        camera1->pitch = -camera1->pitch;
                    }
                }
                break;
            }
            case SM_FLY:
                resetcamera();
                camera1->eyeheight = 1.0f;
                break;
            case SM_OVERVIEW:
            {
                // TODO : fix water rendering
                camera1->o.x = mapdims[0] + mapdims[4]/2;
                camera1->o.y = mapdims[1] + mapdims[5]/2;
                camera1->o.z = mapdims[7] + 1;
                camera1->pitch = -90;
                camera1->yaw = 0;

                disableraytable();
                break;
            }
            case SM_FOLLOWSAME:
            case SM_FOLLOWALT:
            {
                playerent *f = updatefollowplayer();
                if (!f) { togglespect(); return; }
                if (!f->thirdperson == (player1->spectatemode == SM_FOLLOWSAME))
                {
                    camera1 = f;
                    focus = f;
                }
                else camera3(f, f->thirdperson ? f->thirdperson : 10);
                break;
            }

            default:
                resetcamera();
                break;
        }
    }
    else
    {
        if (thirdperson) camera3(player1, thirdperson);
        else resetcamera();
    }
}

void transplayer()
{
    glLoadIdentity();

    glRotatef(camera1->roll, 0, 0, 1);
    glRotatef(camera1->pitch, -1, 0, 0);
    glRotatef(camera1->yaw, 0, 1, 0);

    // move from RH to Z-up LH quake style worldspace
    glRotatef(-90, 1, 0, 0);
    glScalef(1, -1, 1);

    glTranslatef(-camera1->o.x, -camera1->o.y, -camera1->o.z);
}

glmatrixf clipmatrix;

void genclipmatrix(float a, float b, float c, float d)
{
    // transform the clip plane into camera space
    float clip[4];
    loopi(4) clip[i] = a*invmvmatrix[i*4 + 0] + b*invmvmatrix[i*4 + 1] + c*invmvmatrix[i*4 + 2] + d*invmvmatrix[i*4 + 3];

    float x = ((clip[0]<0 ? -1 : (clip[0]>0 ? 1 : 0)) + projmatrix[8]) / projmatrix[0],
          y = ((clip[1]<0 ? -1 : (clip[1]>0 ? 1 : 0)) + projmatrix[9]) / projmatrix[5],
          w = (1 + projmatrix[10]) / projmatrix[14],
          scale = 2 / (x*clip[0] + y*clip[1] - clip[2] + w*clip[3]);
    clipmatrix = projmatrix;
    clipmatrix[2] = clip[0]*scale;
    clipmatrix[6] = clip[1]*scale;
    clipmatrix[10] = clip[2]*scale + 1.0f;
    clipmatrix[14] = clip[3]*scale;
}

bool render_void = false;

bool reflecting = false, refracting = false;
GLuint reflecttex = 0, refracttex = 0;
int reflectlastsize = 0;

VARP(reflectsize, 6, 8, 10);
VAR(reflectclip, 0, 3, 100);
VARP(waterreflect, 0, 1, 1);
VARP(waterrefract, 0, 0, 1);
VAR(reflectscissor, 0, 1, 1);

void drawreflection(float hf, int w, int h, float changelod, bool refract)
{
    reflecting = true;
    refracting = refract;

    int size = 1<<reflectsize, sizelimit = min(hwtexsize, min(w, h));
    while(size > sizelimit) size /= 2;
    if(size!=reflectlastsize)
    {
        if(reflecttex) glDeleteTextures(1, &reflecttex);
        if(refracttex) glDeleteTextures(1, &refracttex);
        reflecttex = refracttex = 0;
    }
    if(!reflecttex || (waterrefract && !refracttex))
    {
        if(!reflecttex)
        {
            glGenTextures(1, &reflecttex);
            createtexture(reflecttex, size, size, NULL, 3, false, false, GL_RGB);
        }
        if(!refracttex)
        {
            glGenTextures(1, &refracttex);
            createtexture(refracttex, size, size, NULL, 3, false, false, GL_RGB);
        }
        reflectlastsize = size;
    }

    extern float wsx1, wsx2, wsy1, wsy2;
    int sx = 0, sy = 0, sw = size, sh = size;
    bool scissor = reflectscissor && (wsx1 > -1 || wsy1 > -1 || wsx1 < 1 || wsy1 < 1);
    if(scissor)
    {
        sx = int(floor((wsx1+1)*0.5f*size));
        sy = int(floor((wsy1+1)*0.5f*size));
        sw = int(ceil((wsx2+1)*0.5f*size)) - sx;
        sh = int(ceil((wsy2+1)*0.5f*size)) - sy;
        glScissor(sx, sy, sw, sh);
        glEnable(GL_SCISSOR_TEST);
    }

    resetcubes();

    render_world(camera1->o.x, camera1->o.y, refract ? camera1->o.z : hf, changelod,
            (int)camera1->yaw, (refract ? 1 : -1)*(int)camera1->pitch, dynfov(), fovy, size, size);

    setupstrips();

    if(!refract) glCullFace(GL_BACK);
    glViewport(0, 0, size, size);
    glClear(GL_DEPTH_BUFFER_BIT);

    glEnable(GL_TEXTURE_2D);

    glPushMatrix();
    if(!refract)
    {
        glTranslatef(0, 0, 2*hf);
        glScalef(1, 1, -1);
    }

    genclipmatrix(0, 0, -1, 0.1f*reflectclip+hf);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadMatrixf(clipmatrix.v);
    glMatrixMode(GL_MODELVIEW);

    renderstripssky();

    glPushMatrix();
    glLoadIdentity();
    glRotatef(camera1->pitch, -1, 0, 0);
    glRotatef(camera1->yaw,   0, 1, 0);
    glRotatef(90, 1, 0, 0);
    if(!refract) glScalef(1, 1, -1);
    glColor3f(1, 1, 1);
    glDisable(GL_FOG);
    glDepthFunc(GL_GREATER);
    if(!refracting) skyfloor = max(skyfloor, hf);
    draw_envbox(fog*4/3);
    glDepthFunc(GL_LESS);
    glEnable(GL_FOG);
    glPopMatrix();

    setuptmu(0, "T * P x 2");

    renderstrips();
    rendermapmodels();
    renderentities();
    renderclients();

    resettmu(0);

    render_particles(-1);

    if(refract) glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    extern int mtwater;
    if(refract && (!mtwater || maxtmus<2))
    {
        glLoadIdentity();
        glOrtho(0, 1, 0, 1, -1, 1);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        glColor4ubv(hdr.watercolor);
        glBegin(GL_TRIANGLE_STRIP);
        glVertex2f(0, 1);
        glVertex2f(1, 1);
        glVertex2f(0, 0);
        glVertex2f(1, 0);
        glEnd();
        glDisable(GL_BLEND);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_DEPTH_TEST);
    }
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    if(!refract) glCullFace(GL_FRONT);
    glViewport(0, 0, w, h);

    if(scissor) glDisable(GL_SCISSOR_TEST);

    glBindTexture(GL_TEXTURE_2D, refract ? refracttex : reflecttex);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, sx, sy, sx, sy, sw, sh);

    glDisable(GL_TEXTURE_2D);

    reflecting = refracting = false;
}

bool minimap = false, minimapdirty = true;
int minimaplastsize = 0;
GLuint minimaptex = 0;

vector<zone> zones;

void renderzones(float z)
{
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_TEXTURE_2D);
    loopv(zones)
    {
        glColor4f(((zones[i].color>>16)&0xFF)/255.0f, ((zones[i].color>>8)&0xFF)/255.0f, (zones[i].color&0xFF)/255.0f, 0.3f);
        glBegin(GL_QUADS);
        glVertex3f(zones[i].x1, zones[i].y1, z);
        glVertex3f(zones[i].x2, zones[i].y1, z);
        glVertex3f(zones[i].x2, zones[i].y2, z);
        glVertex3f(zones[i].x1, zones[i].y2, z);
        glEnd();
    }
    glDisable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);
}

void clearminimap()
{
    minimapdirty = true;
}

COMMAND(clearminimap, "");
VARFP(minimapres, 7, 9, 10, clearminimap());
void drawminimap(int w, int h)
{
    if(!minimapdirty) return;
    int size = 1<<minimapres, sizelimit = min(hwtexsize, min(w, h));
    while(size > sizelimit) size /= 2;
    if(size!=minimaplastsize && minimaptex)
    {
        glDeleteTextures(1, &minimaptex);
        minimaptex = 0;
    }
    if(!minimaptex)
    {
        glGenTextures(1, &minimaptex);
        createtexture(minimaptex, size, size, NULL, 3, false, false, GL_RGB);
        minimaplastsize = size;
    }
    minimap = true;
    disableraytable();
    physent * const oldcam = camera1;
    playerent * const oldfocus = focus;
    physent minicam;
    focus = player1;
    camera1 = &minicam;
    camera1->type = ENT_CAMERA;
    camera1->o.x = mapdims[0] + mapdims[4]/2.0f;
    camera1->o.y = mapdims[1] + mapdims[5]/2.0f;

    //float gdim = max(mapdims[4], mapdims[5])+2.0f; //make 1 cube smaller to give it a black edge
    float gdim = max(mapdims[4], mapdims[5]); //no border

    if(!gdim) gdim = ssize/2.0f;
    camera1->o.z = mapdims[7] + 1;
    camera1->pitch = -90;
    camera1->yaw = 0;

    //float orthd = 2 + gdim/2;
    //glViewport(2, 2, size-4, size-4); // !not wsize here

    float orthd = gdim/2.0f;
    glViewport(0, 0, size, size); // !not wsize here

    glClearDepth(0.0);
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // stencil added 2010jul22
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-orthd, orthd, -orthd, orthd, 0, (mapdims[7]-mapdims[6])+2); // depth of map +2 covered
    glScalef(1, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glCullFace(GL_BACK);
    glDisable(GL_FOG);
    glEnable(GL_TEXTURE_2D);
    transplayer();
    resetcubes();
    render_world(camera1->o.x, camera1->o.y, camera1->o.z, 1.0f,
            (int)camera1->yaw, (int)camera1->pitch, 90.0f, 90.0f, size, size);
    setupstrips();
    setuptmu(0, "T * P x 2");
    glDepthFunc(GL_ALWAYS);
    renderstrips();
    glDepthFunc(GL_LESS);
    rendermapmodels();
    renderzones(mapdims[7]);
    //renderentities();// IMHO better done by radar itself, if at all
    resettmu(0);
    float hf = hdr.waterlevel-0.3f;
    renderwater(hf, 0, 0);

    //draw a black border to prevent the minimap texture edges from bleeding in radarview
    GLfloat prevLineWidth;
    glGetFloatv(GL_LINE_WIDTH, &prevLineWidth);
    glDisable(GL_BLEND);
    glColor3f(0, 0, 0);
    glLineWidth(2.0f);
    glBegin(GL_LINE_LOOP);
        glVertex3f(mapdims[0]+0.02f,            mapdims[1]+0.02f, mapdims[7]);
        glVertex3f(mapdims[0]+mapdims[4]-0.02f, mapdims[1]+0.02f, mapdims[7]);
        glVertex3f(mapdims[0]+mapdims[4]-0.02f, mapdims[1]+mapdims[5]-0.02f, mapdims[7]);
        glVertex3f(mapdims[0]+0.02f,            mapdims[1]+mapdims[5]-0.02f, mapdims[7]);
    glEnd();
    glLineWidth(prevLineWidth);
    glEnable(GL_BLEND);

    camera1 = oldcam;
    focus = oldfocus;
    minimap = false;
    glBindTexture(GL_TEXTURE_2D, minimaptex);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, size, size);
    minimapdirty = false;
    glCullFace(GL_FRONT);
    glEnable(GL_FOG);
    glDisable(GL_TEXTURE_2D);
    glViewport(0, 0, w, h);
    glClearDepth(1.0);
}

void cleanupgl()
{
    if(reflecttex) glDeleteTextures(1, &reflecttex);
    if(refracttex) glDeleteTextures(1, &refracttex);
    if(minimaptex) glDeleteTextures(1, &minimaptex);
    reflecttex = refracttex = minimaptex = 0;
    minimapdirty = true;
}

void drawzone(int *x1, int *x2, int *y1, int *y2, int *color)
{
    zone &newzone = zones.add();
    newzone.x1 = *x1; newzone.x2 = *x2;
    newzone.y1 = *y1; newzone.y2 = *y2;
    newzone.color = *color ? *color : 0x00FF00;
    clearminimap();
}
COMMAND(drawzone, "iiiii");

void resetzones()
{
    zones.shrink(0);
}
COMMAND(resetzones, "");

int xtraverts;

VARP(hudgun, 0, 1, 1);
VARP(specthudgun, 0, 1, 1);

float zoomfactor()
{
    if (!focus->zoomed || focus->weaponsel->type == GUN_HEAL)
        return 1; // no zoom

    float zoomf;
    if (sniper_weap(focus->weaponsel->type))
        zoomf = scopezoom / 100.f;
    else
        zoomf = adszoom / 100.f;

    return focus->zoomed * zoomf + 1;
}

void setperspective(float fovy, float nearplane)
{
    GLdouble ydist = nearplane * tan(fovy/2*RAD), xdist = ydist * aspect;
    if(player1->isspectating() && player1->spectatemode == SM_OVERVIEW)
    {
        int gdim = max(mapdims[4], mapdims[5]);
        if(!gdim) gdim = ssize/2;
        int orthd = 2 + gdim/2;
        glOrtho(-orthd, orthd, -orthd, orthd, 0, (mapdims[7]-mapdims[6])+2); // depth of map +2 covered
    }
    else
    {
        const float zoomf = zoomfactor();
        xdist /= zoomf;
        ydist /= zoomf;
        glFrustum(-xdist, xdist, -ydist, ydist, nearplane, farplane);
    }
}

void sethudgunperspective(bool on)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if(on)
    {
        glScalef(1, 1, 0.5f); // fix hudugns colliding with map geometry
        setperspective(55.0f, 0.01f); // fixed y fov
    }
    else setperspective(fovy, 0.15f);
    glMatrixMode(GL_MODELVIEW);
}

VAR(fakelasertest, 0, 0, 1);
void drawhudgun(int w, int h, float aspect, int farplane)
{
    sethudgunperspective(true);

    if(hudgun && (specthudgun || !player1->isspectating()) && camera1->type==ENT_PLAYER)
    {
        playerent *p = (playerent *)camera1;
        if(p->state==CS_ALIVE) p->weaponsel->renderhudmodel();

        if (fakelasertest && p->muzzle.x >= 0)
        {
            glDisable(GL_TEXTURE_2D);
            glBegin(GL_LINES);
            linestyle(1.5f, 255, 0, 0);
            glVertex3f(p->muzzle.x, p->muzzle.y, p->muzzle.z);
            glVertex3f(worldpos.x, worldpos.y, worldpos.z);
            glEnd();
            glEnable(GL_TEXTURE_2D);
        }
    }
    rendermenumdl();

    sethudgunperspective(false);
}

bool outsidemap(physent *pl)
{
    if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) return true;
    sqr *s = S((int)pl->o.x, (int)pl->o.y);
    return SOLID(s)
        || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0)
        || pl->o.z > s->ceil  + (s->type==CHF ? s->vdelta/4 : 0);
}

float cursordepth = 0.9f;
glmatrixf mvmatrix, projmatrix, mvpmatrix, invmvmatrix, invmvpmatrix;
vec worldpos, camdir, camup, camright;
playerent *worldhit = NULL; int worldhitzone = HIT_NONE; vec worldhitpos;

void readmatrices()
{
    glGetFloatv(GL_MODELVIEW_MATRIX, mvmatrix.v);
    glGetFloatv(GL_PROJECTION_MATRIX, projmatrix.v);
    camright = vec(mvmatrix[0], mvmatrix[4], mvmatrix[8]);
    camup = vec(mvmatrix[1], mvmatrix[5], mvmatrix[9]);
    camdir = vec(-mvmatrix[2], -mvmatrix[6], -mvmatrix[10]);

    mvpmatrix.mul(projmatrix, mvmatrix);
    invmvmatrix.invert(mvmatrix);
    invmvpmatrix.invert(mvpmatrix);
}

int worldtoscreen(const vec &world, vec2 &screen, int options)
{
    const float x = mvpmatrix.transformx(world),
                y = mvpmatrix.transformy(world);
          float w = mvpmatrix.transformw(world);

    int flags = 0;

    if (w < 0)
    {
        w = -w;
        flags = W2S_OUT_BEHIND;
    }

    if (w < 1.0e-6f)
        return W2S_OUT_INVALID;

    /*
    if (options & W2S_IN_CLAMP)
    {
        const float f = max(fabs(x), fabs(y));
        if (f > w)
        {
            w = f;
            flags |= W2S_OUT_CLAMPED;
        }
    }
    */

    screen.x = (x / w + 1.0f) / 2.0f;
    screen.y = (1.0f - y / w) / 2.0f;
    return flags;
}

void traceShot(const vec &from, vec &to)
{
    vec tracer(to);
    tracer.sub(from).normalize();
    vec s;
    const float dist = rayclip(from, tracer, s);
    to = tracer.mul(dist - .1f).add(from);
}

// stupid function to cater for stupid ATI linux drivers that return incorrect depth values

inline float depthcorrect(float d)
{
    return (d<=1/256.0f) ? d*256 : d;
}

// find out the 3d target of the crosshair in the world easily and very acurately.
// sadly many very old cards and drivers appear to fuck up on glReadPixels() and give false
// coordinates, making shooting and such impossible.
// also hits map entities which is unwanted.
// could be replaced by a more acurate version of monster.cpp los() if needed

void readdepth(int w, int h, vec &pos)
{
    glReadPixels(w/2, h/2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cursordepth);
    vec screen(0, 0, depthcorrect(cursordepth)*2 - 1);
    vec4 world;
    invmvpmatrix.transform(screen, world);
    pos = vec(world.x, world.y, world.z).div(world.w);
}

void gl_drawframe(int w, int h, float changelod, float curfps)
{
    dodynlights();
    drawminimap(w, h);

    recomputecamera();

    aspect = float(w)/h;
    fovy = 2*atan2(tan(float(dynfov())/2*RAD), aspect)/RAD;

    float hf = hdr.waterlevel-0.3f;
    const bool underwater = camera1->o.z<hf;

    glFogi(GL_FOG_START, (fog+64)/8);
    glFogi(GL_FOG_END, fog);
    float fogc[4] = { (fogcolour>>16)/256.0f, ((fogcolour>>8)&255)/256.0f, (fogcolour&255)/256.0f, 1.0f },
          wfogc[4] = { hdr.watercolor[0]/255.0f, hdr.watercolor[1]/255.0f, hdr.watercolor[2]/255.0f, 1.0f };
    glFogfv(GL_FOG_COLOR, fogc);
    glClearColor(fogc[0], fogc[1], fogc[2], 1.0f);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if(underwater)
    {
        fovy += sinf(lastmillis/1000.0f)*2.0f;
        aspect += sinf(lastmillis/1000.0f+PI)*0.1f;
        glFogfv(GL_FOG_COLOR, wfogc);
        glFogi(GL_FOG_START, 0);
        glFogi(GL_FOG_END, (fog+96)/8);
    }

    farplane = fog*5/2;
    setperspective(fovy, 0.15f);
    glMatrixMode(GL_MODELVIEW);

    transplayer();
    readmatrices();

    if(!underwater && waterreflect)
    {
        extern int wx1;
        if(wx1>=0)
        {
            if(reflectscissor) calcwaterscissor();
            drawreflection(hf, w, h, changelod, false);
            if(waterrefract) drawreflection(hf, w, h, changelod, true);
        }
    }

    if(stencilshadow && hasstencil && stencilbits >= 8) glClearStencil((hasSTS || hasST2) && !hasSTW ? 128 : 0);
    glClear((/*outsidemap(camera1)*/ true ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT | (stencilshadow && hasstencil && stencilbits >= 8 ? GL_STENCIL_BUFFER_BIT : 0));

    glEnable(GL_TEXTURE_2D);

    resetcubes();

    render_world(camera1->o.x, camera1->o.y, camera1->o.z, changelod,
            (int)camera1->yaw, (int)camera1->pitch, dynfov(), fovy, w, h);

    setupstrips();

    renderstripssky();

    glLoadIdentity();
    glRotatef(camera1->pitch, -1, 0, 0);
    glRotatef(camera1->yaw,   0, 1, 0);
    glRotatef(90, 1, 0, 0);
    glColor3f(1, 1, 1);
    glDisable(GL_FOG);
    glDepthFunc(GL_GREATER);
    if (render_void)
    {
        glDisable(GL_TEXTURE_2D);
        static const GLubyte voidSkyColor[] = { 20, 20, 20 };
        glColor3ubv(voidSkyColor);
    }
    draw_envbox(fog*4/3);
    if (render_void)
        glEnable(GL_TEXTURE_2D);
    glDepthFunc(GL_LESS);
    fixresizedscreen();
    glEnable(GL_FOG);

    transplayer();

    if (render_void)
    {
        //setuptmu(0, "C * P x 2");
    }
    else
    setuptmu(0, "T * P x 2");

    renderstrips();

    if (render_void)
        setuptmu(0, "T * P x 2");


    xtraverts = 0;

    startmodelbatches();
    rendermapmodels();
    endmodelbatches();

    if(stencilshadow && hasstencil && stencilbits >= 8) drawstencilshadows();

    startmodelbatches();
    renderentities();
    endmodelbatches();

    readdepth(w, h, worldpos);
    playerincrosshair(worldhit, worldhitzone, (worldhitpos = worldpos));

    startmodelbatches();
    renderclients();
    endmodelbatches();

    startmodelbatches();
    renderbounceents();
    endmodelbatches();

    // Added by Rick: Need todo here because of drawing the waypoints
    WaypointClass.Think();
    // end add

    drawhudgun(w, h, aspect, farplane);

    resettmu(0);

    glDisable(GL_CULL_FACE);

    render_particles(curtime, PT_DECAL_MASK);

    int nquads = renderwater(hf, !waterreflect || underwater ? 0 : reflecttex, !waterreflect || !waterrefract || underwater ? 0 : refracttex);

    render_particles(curtime, ~PT_DECAL_MASK);

    glDisable(GL_FOG);
    glDisable(GL_TEXTURE_2D);

    if(editmode)
    {
        if(cursordepth==1.0f) worldpos = camera1->o;
        enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDepthMask(GL_FALSE);
        cursorupdate();
        glDepthMask(GL_TRUE);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        disablepolygonoffset(GL_POLYGON_OFFSET_LINE, false);
    }

    extern vector<vertex> verts;
    gl_drawhud(w, h, (int)round_(curfps), nquads, verts.length(), underwater);

    glEnable(GL_CULL_FACE);
    glEnable(GL_FOG);

    undodynlights();
}
Advertisement