Subversion Repositories eduke32

Rev

Rev 5024 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/**************************************************************************************************
"POLYMOST" code originally written by Ken Silverman
Ken Silverman's official web site: http://www.advsys.net/ken
**************************************************************************************************/



#ifdef USE_OPENGL

#include "compat.h"
#include "build.h"
#include "glbuild.h"
#include "mdsprite.h"
#include "pragmas.h"
#include "baselayer.h"
#include "osd.h"
#include "engine_priv.h"
#include "hightile.h"
#include "polymost.h"
#include "polymer.h"
#include "cache1d.h"
#include "kplib.h"
#include "texcache.h"
#include "common.h"

#ifdef EDUKE32_GLES
#include "jwzgles.h"
#endif

#ifndef _WIN32
extern int32_t filelength(int h); // kplib.c
#endif

extern char textfont[2048], smalltextfont[2048];

int32_t rendmode=0;
#ifdef __ANDROID__
int32_t usemodels=0;
#else
int32_t usemodels=1;
#endif
int32_t usehightile=1;
int32_t vsync=0;

#include <math.h> //<-important!
#include <float.h>

typedef struct { float x, cy[2], fy[2]; int32_t tag; int16_t n, p, ctag, ftag; } vsptyp;
#define VSPMAX 4096 //<- careful!
static vsptyp vsp[VSPMAX];
static int32_t gtag;

static float dxb1[MAXWALLSB], dxb2[MAXWALLSB];

#define SCISDIST .9999f  //1.0: Close plane clipping distance

float shadescale = 1.0f;
int32_t shadescale_unbounded = 0;

int32_t r_usenewshading = 3;
int32_t r_usetileshades = 2;
int32_t r_npotwallmode = 0;

static float gviewxrange;
static float ghoriz;
float gxyaspect;
float gyxscale, ghalfx, grhalfxdown10, grhalfxdown10x;
float gcosang, gsinang, gcosang2, gsinang2;
float gchang, gshang, gctang, gstang, gvisibility;
float gtang = 0.f;
static double guo, gux, guy; //Screen-based texture mapping parameters
static double gvo, gvx, gvy;
static double gdo, gdx, gdy;
float fcosglobalang, fsinglobalang;
float fxdim, fydim, fydimen, fviewingrange;
static int32_t preview_mouseaim=0;  // when 1, displays a CROSSHAIR tsprite at the _real_ aimed position

#ifdef USE_OPENGL
static int32_t srepeat = 0, trepeat = 0;

#ifdef REDBLUEMODE
int32_t glredbluemode = 0;
static int32_t lastglredbluemode = 0, redblueclearcnt = 0;
#endif

struct glfiltermodes glfiltermodes[NUMGLFILTERMODES] =
{
    {"GL_NEAREST",GL_NEAREST,GL_NEAREST},
    {"GL_LINEAR",GL_LINEAR,GL_LINEAR},
    {"GL_NEAREST_MIPMAP_NEAREST",GL_NEAREST_MIPMAP_NEAREST,GL_NEAREST},
    {"GL_LINEAR_MIPMAP_NEAREST",GL_LINEAR_MIPMAP_NEAREST,GL_LINEAR},
    {"GL_NEAREST_MIPMAP_LINEAR",GL_NEAREST_MIPMAP_LINEAR,GL_NEAREST},
    {"GL_LINEAR_MIPMAP_LINEAR",GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR}
};

int32_t glanisotropy = 1;            // 0 = maximum supported by card
int32_t gltexfiltermode = 2; // GL_NEAREST_MIPMAP_NEAREST

#ifdef EDUKE32_GLES
int32_t glusetexcompr = 0;
int32_t glusetexcache = 0, glusememcache = 0;
#else
int32_t glusetexcompr = 1;
int32_t glusetexcache = 2, glusememcache = 1;
int32_t glpolygonmode = 0;     // 0:GL_FILL,1:GL_LINE,2:GL_POINT //FUK
int32_t glmultisample = 0, glnvmultisamplehint = 0;
static int32_t lastglpolygonmode = 0; //FUK
int32_t r_detailmapping = 1;
int32_t r_glowmapping = 1;
#endif

int32_t gltexmaxsize = 0;      // 0 means autodetection on first run
int32_t gltexmiplevel = 0;              // discards this many mipmap levels
int32_t glprojectionhacks = 1;
static GLuint polymosttext = 0;
int32_t glrendmode = REND_POLYMOST;

// This variable, and 'shadeforfullbrightpass' control the drawing of
// fullbright tiles.  Also see 'fullbrightloadingpass'.
static int32_t fullbrightdrawingpass = 0;

int32_t r_vertexarrays = 1;
int32_t r_vbos = 1;
int32_t r_vbocount = 64;
int32_t r_animsmoothing = 1;
int32_t r_fullbrights = 1;
int32_t r_downsize = 0;
int32_t r_downsizevar = -1;

// used for fogcalc
static float fogresult, fogresult2, fogcol[4], fogtable[4*MAXPALOOKUPS];
#endif

static const float float_trans[4] = { 1.0f, 1.0f, 0.66f, 0.33f };

char ptempbuf[MAXWALLSB<<1];

// polymost ART sky control
int32_t r_parallaxskyclamping = 1;
int32_t r_parallaxskypanning = 0;

#define MIN_CACHETIME_PRINT 10

// this was faster in MSVC but slower with GCC... currently unknown on ARM where both
// the FPU and possibly the optimization path in the compiler need improvement
#if 0
static inline int32_t __float_as_int(float f) { return *(int32_t *) &f; }
static inline float __int_as_float(int32_t d) { return *(float *) &d; }
static inline float Bfabsf(float f) { return __int_as_float(__float_as_int(f)&0x7fffffff); }
#else
#define Bfabsf fabsf
#endif

#ifdef USE_OPENGL
int32_t mdtims, omdtims;
float alphahackarray[MAXTILES];
int32_t drawingskybox = 0;
int32_t hicprecaching = 0;

#if 0
static inline int32_t gltexmayhavealpha(int32_t dapicnum, int32_t dapalnum)
{
    const int32_t j = (dapicnum&(GLTEXCACHEADSIZ-1));
    pthtyp *pth;

    for (pth=texcache.list[j]; pth; pth=pth->next)
        if (pth->picnum == dapicnum && pth->palnum == dapalnum)
            return ((pth->flags&PTH_HASALPHA) != 0);

    return 1;
}
#endif

void gltexinvalidate(int32_t dapicnum, int32_t dapalnum, int32_t dameth)
{
    const int32_t j = (dapicnum&(GLTEXCACHEADSIZ-1));
    pthtyp *pth;

    for (pth=texcache.list[j]; pth; pth=pth->next)
        if (pth->picnum == dapicnum && pth->palnum == dapalnum &&
            (pth->flags & PTH_CLAMPED) == TO_PTH_CLAMPED(dameth))
        {
            pth->flags |= PTH_INVALIDATED;
            if (pth->flags & PTH_HASFULLBRIGHT)
                pth->ofb->flags |= PTH_INVALIDATED;
        }
}

//Make all textures "dirty" so they reload, but not re-allocate
//This should be much faster than polymost_glreset()
//Use this for palette effects ... but not ones that change every frame!
void gltexinvalidatetype(int32_t type)
{
    int32_t j;
    pthtyp *pth;

    for (j=0; j<=GLTEXCACHEADSIZ-1; j++)
    {
        for (pth=texcache.list[j]; pth; pth=pth->next)
        {
            if (type == INVALIDATE_ALL || (type == INVALIDATE_ART && pth->hicr == NULL))
            {
                pth->flags |= PTH_INVALIDATED;
                if (pth->flags & PTH_HASFULLBRIGHT)
                    pth->ofb->flags |= PTH_INVALIDATED;
            }
        }
    }

    if (type == INVALIDATE_ALL)
        clearskins();
#ifdef DEBUGGINGAIDS
    OSD_Printf("gltexinvalidateall()\n");
#endif
}

static void bind_2d_texture(GLuint texture, int filter)
{
    if (filter == -1)
        filter = gltexfiltermode;

    bglBindTexture(GL_TEXTURE_2D, texture);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glfiltermodes[filter].mag);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glfiltermodes[filter].min);
#ifndef EDUKE32_GLES
    if (glinfo.maxanisotropy > 1.f)
        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glanisotropy);
#endif
}

void gltexapplyprops(void)
{
    int32_t i;
    pthtyp *pth;

    if (getrendermode() == REND_CLASSIC)
        return;

    if (glinfo.maxanisotropy > 1.f)
    {
        if (glanisotropy <= 0 || glanisotropy > glinfo.maxanisotropy)
            glanisotropy = (int32_t)glinfo.maxanisotropy;
    }

    gltexfiltermode = clamp(gltexfiltermode, 0, NUMGLFILTERMODES-1);

    for (i=0; i<=GLTEXCACHEADSIZ-1; i++)
    {
        for (pth=texcache.list[i]; pth; pth=pth->next)
        {
#ifndef EDUKE32_TOUCH_DEVICES
            bind_2d_texture(pth->glpic, -1);

            if (r_fullbrights && pth->flags & PTH_HASFULLBRIGHT)
                bind_2d_texture(pth->ofb->glpic, -1);
#else
            bind_2d_texture(pth->glpic, pth->flags & PTH_HIGHTILE ? 5 : -1);

            if (r_fullbrights && pth->flags & PTH_HASFULLBRIGHT)
                bind_2d_texture(pth->ofb->glpic, pth->flags & PTH_HIGHTILE ? 5 : -1);
#endif
        }
    }

    int32_t j;
    mdskinmap_t *sk;
    md2model_t *m;

    for (i=0; i<nextmodelid; i++)
    {
        m = (md2model_t *)models[i];
        if (m->mdnum < 2) continue;
        for (j=0; j<m->numskins*(HICEFFECTMASK+1); j++)
        {
            if (!m->texid[j]) continue;
            bind_2d_texture(m->texid[j], -1);
        }

        for (sk=m->skinmap; sk; sk=sk->next)
            for (j=0; j<(HICEFFECTMASK+1); j++)
            {
                if (!sk->texid[j]) continue;
                bind_2d_texture(sk->texid[j], -1);
            }
    }
}

//--------------------------------------------------------------------------------------------------

float glox1, gloy1, glox2, gloy2;

//Use this for both initialization and uninitialization of OpenGL.
static int32_t gltexcacnum = -1;

void polymost_glreset()
{
    int32_t i;
    pthtyp *pth, *next;

    for (i=0; i<=MAXPALOOKUPS-1; i++)
    {
        fogtable[i<<2] = palookupfog[i].r * (1.f/63.f);
        fogtable[(i<<2)+1] = palookupfog[i].g * (1.f/63.f);
        fogtable[(i<<2)+2] = palookupfog[i].b * (1.f/63.f);
        fogtable[(i<<2)+3] = 0;
    }

    //Reset if this is -1 (meaning 1st texture call ever), or > 0 (textures in memory)
    if (gltexcacnum < 0)
    {
        gltexcacnum = 0;

        //Hack for polymost_dorotatesprite calls before 1st polymost_drawrooms()
        gcosang = gcosang2 = 16384.f/262144.f;
        gsinang = gsinang2 = 0.f/262144.f;
    }
    else
    {
        for (i=0; i<=GLTEXCACHEADSIZ-1; i++)
        {
            for (pth=texcache.list[i]; pth;)
            {
                next = pth->next;
                if (pth->flags & PTH_HASFULLBRIGHT)
                {
                    bglDeleteTextures(1,&pth->ofb->glpic);
                    Bfree(pth->ofb);
                }

                bglDeleteTextures(1,&pth->glpic);
                Bfree(pth);
                pth = next;
            }

            texcache.list[i] = NULL;
        }
        clearskins();
    }

    if (polymosttext)
        bglDeleteTextures(1,&polymosttext);
    polymosttext=0;

    md_freevbos();

    Bmemset(texcache.list,0,sizeof(texcache.list));
    glox1 = -1;

    texcache_freeptrs();
    texcache_syncmemcache();

#ifdef DEBUGGINGAIDS
    OSD_Printf("polymost_glreset()\n");
#endif
}


// one-time initialization of OpenGL for polymost
void polymost_glinit()
{
    bglHint(GL_FOG_HINT, GL_NICEST);
    bglFogi(GL_FOG_MODE, (r_usenewshading < 2) ? GL_EXP2 : GL_LINEAR);
    bglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    bglPixelStorei(GL_PACK_ALIGNMENT, 1);
    bglPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    //bglHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    //bglEnable(GL_LINE_SMOOTH);

#ifndef EDUKE32_GLES
    if (glmultisample > 0 && glinfo.multisample)
    {
        if (glinfo.nvmultisamplehint)
            bglHint(GL_MULTISAMPLE_FILTER_HINT_NV, glnvmultisamplehint ? GL_NICEST:GL_FASTEST);
        bglEnable(GL_MULTISAMPLE_ARB);
    }

    if (r_detailmapping && (!glinfo.multitex || !glinfo.envcombine))
    {
        OSD_Printf("Your OpenGL implementation doesn't support detail mapping. Disabling...\n");
        r_detailmapping = 0;
    }

    if (r_glowmapping && (!glinfo.multitex || !glinfo.envcombine))
    {
        OSD_Printf("Your OpenGL implementation doesn't support glow mapping. Disabling...\n");
        r_glowmapping = 0;
    }
#endif

    if (r_vbos && (!glinfo.vbos))
    {
        OSD_Printf("Your OpenGL implementation doesn't support Vertex Buffer Objects. Disabling...\n");
        r_vbos = 0;
    }

    bglEnableClientState(GL_VERTEX_ARRAY);
    bglEnableClientState(GL_TEXTURE_COORD_ARRAY);

    texcache_init();
    texcache_loadoffsets();
    texcache_openfiles();
   
    texcache_setupmemcache();
    texcache_checkgarbage();
}

////////// VISIBILITY FOG ROUTINES //////////
extern int32_t nofog;  // in windows/SDL layers

// only for r_usenewshading < 2 (not preferred)
static void fogcalc_old(int32_t shade, int32_t vis)
{
    float f;

    if (r_usenewshading==1)
    {
        f = 0.9f * shade;
        f = (vis > 239) ? (float)(gvisibility*((vis-240+f))) :
            (float)(gvisibility*(vis+16+f));
    }
    else
    {
        f = (shade < 0) ? shade * 3.5f : shade * .66f;
        f = (vis > 239) ? (float)(gvisibility*((vis-240+f)/(klabs(vis-256)))) :
            (float)(gvisibility*(vis+16+f));
    }

    if (f < 0.001f)
        f = 0.001f;
    else if (f > 100.0f)
        f = 100.0f;

    fogresult = f;
}

// For GL_LINEAR fog:
#define FOGDISTCONST 600
#define FULLVIS_BEGIN 2.9e30
#define FULLVIS_END 3.0e30

static inline void fogcalc(int32_t tile, int32_t shade, int32_t vis, int32_t pal)
{
    if (shade > 0 && getrendermode() == REND_POLYMOST && r_usetileshades == 1 &&
        (!usehightile || !hicfindsubst(tile, pal)) &&
        (!usemodels || md_tilehasmodel(tile, pal) < 0))
        shade >>= 1;

    Bmemcpy(fogcol, &fogtable[pal<<2], sizeof(fogcol));

    if (r_usenewshading < 2)
        fogcalc_old(shade, vis);
    else
    {
        float combvis = (float) globalvisibility * (uint8_t) (vis+16);

        if (combvis == 0)
        {
            if (r_usenewshading == 2 && shade > 0)
            {
                // beg = -D*shade, end = D*(NUMSHADES-1-shade)
                //  => end/beg = -(NUMSHADES-1-shade)/shade
                fogresult = (float) -FULLVIS_BEGIN;
                fogresult2 = FULLVIS_BEGIN * (float) (numshades-1-shade)/shade;
            }
            else
            {
                fogresult = (float) FULLVIS_BEGIN;
                fogresult2 = (float) FULLVIS_END;
            }
        }
        else if (r_usenewshading == 3 && shade >= numshades-1)
        {
            fogresult = -1;
            fogresult2 = 0;
        }
        else
        {
            combvis = 1.f/combvis;
            fogresult = (r_usenewshading == 3 && shade > 0) ? 0 : -(FOGDISTCONST * shade) * combvis;
            fogresult2 = (FOGDISTCONST * (numshades-1-shade)) * combvis;
        }
    }
}

void calc_and_apply_fog(int32_t tile, int32_t shade, int32_t vis, int32_t pal)
{
    fogcalc(tile, shade, vis, pal);
    bglFogfv(GL_FOG_COLOR, fogcol);

    if (r_usenewshading < 2)
        bglFogf(GL_FOG_DENSITY, fogresult);
    else
    {
        bglFogf(GL_FOG_START, fogresult);
        bglFogf(GL_FOG_END, fogresult2);
    }
}

void calc_and_apply_fog_factor(int32_t tile, int32_t shade, int32_t vis, int32_t pal, float factor)
{
    // NOTE: for r_usenewshading >= 2, the fog beginning/ending distance results are
    // unused.
    fogcalc(tile, shade, vis, pal);
    bglFogfv(GL_FOG_COLOR, fogcol);

    if (r_usenewshading < 2)
        bglFogf(GL_FOG_DENSITY, fogresult*factor);
    else
    {
        bglFogf(GL_FOG_START, (GLfloat) FULLVIS_BEGIN);
        bglFogf(GL_FOG_END, (GLfloat) FULLVIS_END);
    }
}
////////////////////


static float get_projhack_ratio(void)
{
    float rv;

    if (glprojectionhacks == 1)
    {
        float const mul = (gshang * gshang);
        rv = 1.05f + mul * mul * mul * mul;
    }
    else if (glprojectionhacks == 2)
    {
        float const abs_shang = Bfabsf(gshang);
        rv = (abs_shang > 0.7f) ?
            1.05f + 4.f * (abs_shang - 0.7f) : 1.f;
    }
    else
        rv = 1.f;

    // No projection hacks (legacy or new-aspect)
    return rv;
}

static void resizeglcheck(void)
{
#ifdef REDBLUEMODE
    if (glredbluemode < lastglredbluemode)
    {
        glox1 = -1;
        bglColorMask(1,1,1,1);
    }
    else if (glredbluemode != lastglredbluemode)
    {
        redblueclearcnt = 0;
    }
    lastglredbluemode = glredbluemode;
#endif

#ifndef EDUKE32_GLES
    //FUK
    if (lastglpolygonmode != glpolygonmode)
    {
        lastglpolygonmode = glpolygonmode;
        switch (glpolygonmode)
        {
        default:
        case 0:
            bglPolygonMode(GL_FRONT_AND_BACK,GL_FILL); break;
        case 1:
            bglPolygonMode(GL_FRONT_AND_BACK,GL_LINE); break;
        case 2:
            bglPolygonMode(GL_FRONT_AND_BACK,GL_POINT); break;
        }
    }
    if (glpolygonmode) //FUK
    {
        bglClearColor(1.0,1.0,1.0,0.0);
        bglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        bglDisable(GL_TEXTURE_2D);
    }
#else
    bglPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
#endif

    if ((glox1 != windowx1) || (gloy1 != windowy1) || (glox2 != windowx2) || (gloy2 != windowy2))
    {
        const int32_t ourxdimen = (windowx2-windowx1+1);
        float ratio = get_projhack_ratio();
        const int32_t fovcorrect = (ratio==0) ? 0 : (int32_t)(ourxdimen*ratio - ourxdimen);
        float m[4][4];

        glox1 = (float)windowx1; gloy1 = (float)windowy1;
        glox2 = (float)windowx2; gloy2 = (float)windowy2;

        bglViewport(windowx1-(fovcorrect/2), yres-(windowy2+1),
                    ourxdimen+fovcorrect, windowy2-windowy1+1);

        bglMatrixMode(GL_PROJECTION);
        Bmemset(m,0,sizeof(m));
        ratio = 1.f/ratio;
        m[0][0] = fydimen * ratio; m[0][2] = 1.f;
        m[1][1] = fxdimen; m[1][2] = 1.f;
        m[2][2] = 1.f; m[2][3] = fydimen * ratio;
        m[3][2] =-1.f;
        bglLoadMatrixf(&m[0][0]);

        bglMatrixMode(GL_MODELVIEW);
        bglLoadIdentity();

#ifdef USE_OPENGL
        if (!nofog) bglEnable(GL_FOG);
#endif

        //bglEnable(GL_TEXTURE_2D);
    }
}

static void fixtransparency(coltype *dapic, vec2_t dasiz, vec2_t dasiz2, int32_t dameth)
{
    int32_t y, naxsiz2;
    vec2_t doxy = { dasiz2.x-1, dasiz2.y-1 };

    if (dameth&4) { doxy.x = min(doxy.x, dasiz.x); doxy.y = min(doxy.y, dasiz.y); }
    else { dasiz = dasiz2; } //Make repeating textures duplicate top/left parts

    dasiz.x--; dasiz.y--; naxsiz2 = -dasiz2.x; //Hacks for optimization inside loop

    //Set transparent pixels to average color of neighboring opaque pixels
    //Doing this makes bilinear filtering look much better for masked textures (I.E. sprites)
    for (y=doxy.y; y>=0; y--)
    {
        int32_t x;
        coltype * wpptr = &dapic[y*dasiz2.x+doxy.x];

        for (x=doxy.x; x>=0; x--,wpptr--)
        {
            int32_t r=0, g=0, b=0, j=0;

            if (wpptr->a) continue;

            r = g = b = j = 0;
            if ((x>     0) && (wpptr[     -1].a)) { r += wpptr[     -1].r; g += wpptr[     -1].g; b += wpptr[     -1].b; j++; }
            if ((x<dasiz.x) && (wpptr[     +1].a)) { r += wpptr[     +1].r; g += wpptr[     +1].g; b += wpptr[     +1].b; j++; }
            if ((y>     0) && (wpptr[naxsiz2].a)) { r += wpptr[naxsiz2].r; g += wpptr[naxsiz2].g; b += wpptr[naxsiz2].b; j++; }
            if ((y<dasiz.y) && (wpptr[dasiz2.x].a)) { r += wpptr[dasiz2.x].r; g += wpptr[dasiz2.x].g; b += wpptr[dasiz2.x].b; j++; }
            switch (j)
            {
            case 1:
                wpptr->r =   r            ; wpptr->g =   g            ; wpptr->b =   b            ; break;
            case 2:
                wpptr->r = ((r   +  1)>>1); wpptr->g = ((g   +  1)>>1); wpptr->b = ((b   +  1)>>1); break;
            case 3:
                wpptr->r = ((r*85+128)>>8); wpptr->g = ((g*85+128)>>8); wpptr->b = ((b*85+128)>>8); break;
            case 4:
                wpptr->r = ((r   +  2)>>2); wpptr->g = ((g   +  2)>>2); wpptr->b = ((b   +  2)>>2); break;
            }
        }
    }
}

void uploadtexture(int32_t doalloc, int32_t xsiz, int32_t ysiz, int32_t intexfmt, int32_t texfmt,
                   coltype *pic, int32_t tsizx, int32_t tsizy, int32_t dameth)
{
    int32_t x2, y2, j, js=0;
    const int32_t hi = (dameth & DAMETH_HI) ? 1 : 0;
    const int32_t nocompress = (dameth & DAMETH_NOCOMPRESS) ? 1 : 0;

    dameth &= ~(DAMETH_HI|DAMETH_NOCOMPRESS);

    if (gltexmaxsize <= 0)
    {
        GLint i = 0;
        bglGetIntegerv(GL_MAX_TEXTURE_SIZE, &i);
        if (!i) gltexmaxsize = 6;   // 2^6 = 64 == default GL max texture size
        else
        {
            gltexmaxsize = 0;
            for (; i>1; i>>=1) gltexmaxsize++;
        }
    }

    js = max(0,min(gltexmaxsize-1,gltexmiplevel));
    gltexmiplevel = js;
    while ((xsiz>>js) > (1<<gltexmaxsize) || (ysiz>>js) > (1<<gltexmaxsize)) js++;

    if (hi && !nocompress) js = r_downsize;

    /*
    OSD_Printf("Uploading %dx%d %s as %s\n", xsiz,ysiz,
            (texfmt==GL_RGBA?"GL_RGBA":
             texfmt==GL_RGB?"GL_RGB":
             texfmt==GL_BGR?"GL_BGR":
             texfmt==GL_BGRA?"GL_BGRA":"other"),
            (intexfmt==GL_RGBA?"GL_RGBA":
             intexfmt==GL_RGB?"GL_RGB":
             intexfmt==GL_COMPRESSED_RGBA_ARB?"GL_COMPRESSED_RGBA_ARB":
             intexfmt==GL_COMPRESSED_RGB_ARB?"GL_COMPRESSED_RGB_ARB":"other"));
    */


    if (js == 0)
    {
        if (doalloc&1)
            bglTexImage2D(GL_TEXTURE_2D,0,intexfmt,xsiz,ysiz,0,texfmt,GL_UNSIGNED_BYTE,pic); //loading 1st time
        else
            bglTexSubImage2D(GL_TEXTURE_2D,0,0,0,xsiz,ysiz,texfmt,GL_UNSIGNED_BYTE,pic); //overwrite old texture
    }

#if 0
    gluBuild2DMipmaps(GL_TEXTURE_2D,GL_RGBA8,xsiz,ysiz,texfmt,GL_UNSIGNED_BYTE,pic); //Needs C++ to link?
#elif 1
    x2 = xsiz; y2 = ysiz;
    for (j=1; (x2 > 1) || (y2 > 1); j++)
    {
        int32_t y;
        int32_t x3 = max(1, x2 >> 1), y3 = max(1, y2 >> 1);  // this came from the GL_ARB_texture_non_power_of_two spec
        //x3 = ((x2+1)>>1); y3 = ((y2+1)>>1);

        for (y=0; y<y3; y++)
        {
            int32_t x;
            coltype *wpptr = &pic[y*x3], *rpptr = &pic[(y<<1)*x2];

            for (x=0; x<x3; x++,wpptr++,rpptr+=2)
            {
                int32_t r=0, g=0, b=0, a=0, k=0;

                if (rpptr[0].a)                  { r += rpptr[0].r; g += rpptr[0].g; b += rpptr[0].b; a += rpptr[0].a; k++; }
                if ((x+x+1 < x2) && (rpptr[1].a)) { r += rpptr[1].r; g += rpptr[1].g; b += rpptr[1].b; a += rpptr[1].a; k++; }
                if (y+y+1 < y2)
                {
                    if ((rpptr[x2].a)) { r += rpptr[x2  ].r; g += rpptr[x2  ].g; b += rpptr[x2  ].b; a += rpptr[x2  ].a; k++; }
                    if ((x+x+1 < x2) && (rpptr[x2+1].a)) { r += rpptr[x2+1].r; g += rpptr[x2+1].g; b += rpptr[x2+1].b; a += rpptr[x2+1].a; k++; }
                }
                switch (k)
                {
                case 0:
                case 1:
                    wpptr->r = r; wpptr->g = g; wpptr->b = b; wpptr->a = a; break;
                case 2:
                    wpptr->r = ((r+1)>>1); wpptr->g = ((g+1)>>1); wpptr->b = ((b+1)>>1); wpptr->a = ((a+1)>>1); break;
                case 3:
                    wpptr->r = ((r*85+128)>>8); wpptr->g = ((g*85+128)>>8); wpptr->b = ((b*85+128)>>8); wpptr->a = ((a*85+128)>>8); break;
                case 4:
                    wpptr->r = ((r+2)>>2); wpptr->g = ((g+2)>>2); wpptr->b = ((b+2)>>2); wpptr->a = ((a+2)>>2); break;
                default:
                    EDUKE32_UNREACHABLE_SECTION(break);
                }
                //if (wpptr->a) wpptr->a = 255;
            }
        }

        if (tsizx >= 0)
        {
            vec2_t tsizzle;
            vec2_t mnizzle = { x3, y3 };

            tsizzle.x = (tsizx+(1<<j)-1)>>j;
            tsizzle.y = (tsizy+(1<<j)-1)>>j;
            fixtransparency(pic, tsizzle, mnizzle, dameth);
        }

        if (j >= js)
        {
            if (doalloc&1)
                bglTexImage2D(GL_TEXTURE_2D,j-js,intexfmt,x3,y3,0,texfmt,GL_UNSIGNED_BYTE,pic); //loading 1st time
            else
                bglTexSubImage2D(GL_TEXTURE_2D,j-js,0,0,x3,y3,texfmt,GL_UNSIGNED_BYTE,pic); //overwrite old texture
        }
        x2 = x3; y2 = y3;
    }
#endif
}


#if 0
// TODO: make configurable
static int32_t tile_is_sky(int32_t tilenum)
{
    return return (tilenum >= 78 /*CLOUDYOCEAN*/ && tilenum <= 99 /*REDSKY2*/);
}
#else
# define tile_is_sky(x) (0)
#endif

static void texture_setup(const int32_t dameth, int filter)
{
    const GLuint clamp_mode = glinfo.clamptoedge ? GL_CLAMP_TO_EDGE : GL_CLAMP;

    if (filter == -1)
        filter = gltexfiltermode;

    gltexfiltermode = clamp(gltexfiltermode, 0, NUMGLFILTERMODES - 1);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, glfiltermodes[filter].mag);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, glfiltermodes[filter].min);

#ifndef EDUKE32_GLES
    if (glinfo.maxanisotropy > 1.f)
    {
        uint32_t i = (unsigned)Blrintf(glinfo.maxanisotropy);

        if ((unsigned)glanisotropy > i)
            glanisotropy = i;

        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, glanisotropy);
    }
#endif

    if (!(dameth & DAMETH_CLAMPED))
    {
        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, !tile_is_sky(dapic) ? GL_REPEAT : clamp_mode);
        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }
    else
    {
        // For sprite textures, clamping looks better than wrapping
        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp_mode);
        bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp_mode);
    }
}

void gloadtile_art(int32_t dapic, int32_t dapal, int32_t dashade, int32_t dameth, pthtyp *pth, int32_t doalloc)
{
    static int32_t fullbrightloadingpass = 0;

    vec2_t siz, tsiz = tilesiz[dapic];

    if (!glinfo.texnpot)
    {
        for (siz.x = 1; siz.x < tsiz.x; siz.x += siz.x);
        for (siz.y = 1; siz.y < tsiz.y; siz.y += siz.y);
    }
    else
    {
        if ((tsiz.x|tsiz.y) == 0)
            siz.x = siz.y = 1;
        else
        {
            siz.x = tsiz.x;
            siz.y = tsiz.y;
        }
    }

    coltype *pic = (coltype *)Xmalloc(siz.x*siz.y*sizeof(coltype));
    char hasalpha = 0, hasfullbright = 0;

    if (!waloff[dapic])
    {
        //Force invalid textures to draw something - an almost purely transparency texture
        //This allows the Z-buffer to be updated for mirrors (which are invalidated textures)
        pic[0].r = pic[0].g = pic[0].b = 0; pic[0].a = 1;
        tsiz.x = tsiz.y = 1; hasalpha = 1;
    }
    else
    {
        const int dofullbright = !(picanm[dapic].sf & PICANM_NOFULLBRIGHT_BIT);

        for (int y = 0; y < siz.y; y++)
        {
            coltype *wpptr = &pic[y * siz.x];
            int32_t y2 = (y < tsiz.y) ? y : y - tsiz.y;

            for (int x = 0; x < siz.x; x++, wpptr++)
            {
                int32_t dacol;
                int32_t x2 = (x < tsiz.x) ? x : x-tsiz.x;

                if ((dameth & DAMETH_CLAMPED) && (x >= tsiz.x || y >= tsiz.y)) //Clamp texture
                {
                    wpptr->r = wpptr->g = wpptr->b = wpptr->a = 0;
                    continue;
                }

                dacol = *(char *)(waloff[dapic]+x2*tsiz.y+y2);

                if (!fullbrightloadingpass)
                {
                    // regular texture
                    if (dacol > 239 && dacol != 255 && dofullbright)
                        hasfullbright = 1;

                    wpptr->a = 255;
                }
                else
                {
                    // texture with only fullbright areas
                    if (dacol < 240)    // regular colors
                    {
                        wpptr->a = 0;
                        hasalpha = 1;
                    }
                    else   // fullbright
                    {
                        wpptr->a = 255;
                    }
                }

                if (dacol != 255)
                {
                    char *p = (char *)(palookup[dapal])+(int32_t)(dashade<<8);
                    dacol = (uint8_t)p[dacol];
                }
                else
                {
                    wpptr->a = 0;
                    hasalpha = 1;
                }

                bricolor((palette_t *)wpptr, dacol);
            }
        }
    }

    if (doalloc) bglGenTextures(1,(GLuint *)&pth->glpic); //# of textures (make OpenGL allocate structure)
    bglBindTexture(GL_TEXTURE_2D,pth->glpic);

    fixtransparency(pic,tsiz,siz,dameth);

    int32_t npoty = 0;

    if (polymost_want_npotytex(dameth, siz.y) &&
            tsiz.x==siz.x && tsiz.y==siz.y)  // XXX
    {
        const int32_t nextpoty = 1<<((picsiz[dapic]>>4)+1);
        const int32_t ydif = nextpoty - siz.y;
        coltype *paddedpic;

        Bassert(ydif > 0 && ydif < siz.y);

        paddedpic = (coltype*) Xrealloc(pic, siz.x*nextpoty*sizeof(coltype));

        pic = paddedpic;
        Bmemcpy(&pic[siz.x*siz.y], pic, siz.x*ydif*sizeof(coltype));
        siz.y = tsiz.y = nextpoty;

        npoty = PTH_NPOTWALL;
    }

    uploadtexture(doalloc, siz.x, siz.y, hasalpha ? GL_RGBA : GL_RGB, GL_RGBA, pic, tsiz.x, tsiz.y, dameth);

    Bfree(pic);

    texture_setup(dameth, -1);

    pth->picnum = dapic;
    pth->palnum = dapal;
    pth->shade = dashade;
    pth->effects = 0;
    pth->flags = TO_PTH_CLAMPED(dameth) | (hasalpha*PTH_HASALPHA) | npoty;
    pth->hicr = NULL;

    if (hasfullbright && !fullbrightloadingpass)
    {
        // Load the ONLY texture that'll be assembled with the regular one to
        // make the final texture with fullbright pixels.
        fullbrightloadingpass = 1;

        pth->ofb = (pthtyp *)Xcalloc(1,sizeof(pthtyp));
        pth->flags |= PTH_HASFULLBRIGHT;

        gloadtile_art(dapic, dapal, 0, dameth, pth->ofb, 1);

        fullbrightloadingpass = 0;
    }
}

int32_t gloadtile_hi(int32_t dapic,int32_t dapalnum, int32_t facen, hicreplctyp *hicr,
                            int32_t dameth, pthtyp *pth, int32_t doalloc, char effect)
{
    if (!hicr) return -1;

    coltype *pic = NULL;

    char *picfil = NULL, *fn;
    int32_t picfillen, intexfmt = GL_RGBA, filh;

    int32_t startticks=0, willprint=0;

    if (facen > 0)
    {
        if (!hicr->skybox) return -1;
        if (facen > 6) return -1;
        if (!hicr->skybox->face[facen-1]) return -1;
        fn = hicr->skybox->face[facen-1];
    }
    else
    {
        if (!hicr->filename) return -1;
        fn = hicr->filename;
    }

    if (EDUKE32_PREDICT_FALSE((filh = kopen4load(fn, 0)) < 0))
    {
        OSD_Printf("hightile: %s (pic %d) not found\n", fn, dapic);
        return -2;
    }

    picfillen = kfilelength(filh);

    kclose(filh);       // FIXME: shouldn't have to do this. bug in cache1d.c

    char hasalpha = 255;
    texcacheheader cachead;
    int32_t gotcache = texcache_readtexheader(fn, picfillen+(dapalnum<<8), dameth, effect, &cachead, 0);
    vec2_t siz ={ 0, 0 }, tsiz;

    if (gotcache && !texcache_loadtile(&cachead, &doalloc, pth))
    {
        tsiz.x = cachead.xdim;
        tsiz.y = cachead.ydim;
        hasalpha = (cachead.flags & CACHEAD_HASALPHA) ? 0 : 255;
    }
    else
    {
        int32_t r, g, b;
        int32_t j, y;

        gotcache = 0;   // the compressed version will be saved to disk

        if ((filh = kopen4load(fn, 0)) < 0) return -1;

        picfil = (char *)Xmalloc(picfillen+1);
        if (EDUKE32_PREDICT_FALSE(kread(filh, picfil, picfillen) != picfillen))
            initprintf("warning: didn't fully read %s\n", fn);
        // prevent
        // Conditional jump or move depends on uninitialised value(s)
        //  at kpegrend (kplib.c:1655)
        picfil[picfillen] = 0;
        kclose(filh);

        // tsizx/y = replacement texture's natural size
        // xsiz/y = 2^x size of replacement

        kpgetdim(picfil,picfillen,&tsiz.x,&tsiz.y);
        if (tsiz.x == 0 || tsiz.y == 0) { Bfree(picfil); return -1; }

        pth->siz.x = tsiz.x;
        pth->siz.y = tsiz.y;

        if (!glinfo.texnpot)
        {
            for (siz.x=1; siz.x<tsiz.x; siz.x+=siz.x);
            for (siz.y=1; siz.y<tsiz.y; siz.y+=siz.y);
        }
        else
        {
            siz.x = tsiz.x;
            siz.y = tsiz.y;
        }
        pic = (coltype *)Xcalloc(siz.x,siz.y*sizeof(coltype));

        startticks = getticks();

        static coltype *lastpic = NULL;
        static char *lastfn = NULL;
        static int32_t lastsize = 0;

        if (lastpic && lastfn && !Bstrcmp(lastfn,fn))
        {
            willprint=1;
            Bmemcpy(pic, lastpic, siz.x*siz.y*sizeof(coltype));
        }
        else
        {
            if (kprender(picfil,picfillen,(intptr_t)pic,siz.x*sizeof(coltype),siz.x,siz.y)) { Bfree(picfil); Bfree(pic); return -2; }
            willprint=2;

            if (hicprecaching)
            {
                lastfn = fn;  // careful...
                if (!lastpic)
                {
                    lastpic = (coltype *)Bmalloc(siz.x*siz.y*sizeof(coltype));
                    lastsize = siz.x*siz.y;
                }
                else if (lastsize < siz.x*siz.y)
                {
                    Bfree(lastpic);
                    lastpic = (coltype *)Bmalloc(siz.x*siz.y*sizeof(coltype));
                }
                if (lastpic)
                    Bmemcpy(lastpic, pic, siz.x*siz.y*sizeof(coltype));
            }
            else if (lastpic)
            {
                Bfree(lastpic); lastpic=NULL;
                lastfn = NULL;
                lastsize = 0;
            }
        }

        r=(glinfo.bgra)?hictinting[dapalnum].r:hictinting[dapalnum].b;
        g=hictinting[dapalnum].g;
        b=(glinfo.bgra)?hictinting[dapalnum].b:hictinting[dapalnum].r;
        for (y=0,j=0; y<tsiz.y; y++,j+=siz.x)
        {
            coltype tcol;
            char *cptr = britable[gammabrightness ? 0 : curbrightness];
            coltype *rpptr = &pic[j];

            int32_t x;

            for (x=0; x<tsiz.x; x++)
            {
                tcol.b = cptr[rpptr[x].b];
                tcol.g = cptr[rpptr[x].g];
                tcol.r = cptr[rpptr[x].r];
                tcol.a = rpptr[x].a;
                hasalpha &= rpptr[x].a;

                if (effect & HICTINT_GRAYSCALE)
                {
                    tcol.g = tcol.r = tcol.b = (uint8_t) ((tcol.r * GRAYSCALE_COEFF_RED) + (tcol.g * GRAYSCALE_COEFF_GREEN) +
                                                          (tcol.b * GRAYSCALE_COEFF_BLUE));
                }

                if (effect & HICTINT_INVERT)
                {
                    tcol.b = 255-tcol.b;
                    tcol.g = 255-tcol.g;
                    tcol.r = 255-tcol.r;
                }

                if (effect & HICTINT_COLORIZE)
                {
                    tcol.b = min((int32_t)((tcol.b)*r)/64,255);
                    tcol.g = min((int32_t)((tcol.g)*g)/64,255);
                    tcol.r = min((int32_t)((tcol.r)*b)/64,255);
                }

                rpptr[x] = tcol;
            }
        }

        if ((!(dameth & DAMETH_CLAMPED)) || facen) //Duplicate texture pixels (wrapping tricks for non power of 2 texture sizes)
        {
            if (siz.x > tsiz.x) //Copy left to right
            {
                int32_t *lptr = (int32_t *)pic;
                for (y=0; y<tsiz.y; y++,lptr+=siz.x)
                    Bmemcpy(&lptr[tsiz.x],lptr,(siz.x-tsiz.x)<<2);
            }
            if (siz.y > tsiz.y)  //Copy top to bottom
                Bmemcpy(&pic[siz.x*tsiz.y],pic,(siz.y-tsiz.y)*siz.x<<2);
        }

        int32_t texfmt;

        if (!glinfo.bgra)
        {
            texfmt = GL_RGBA;

            for (j=siz.x*siz.y-1; j>=0; j--)
                swapchar(&pic[j].r, &pic[j].b);
        }
        else texfmt = GL_BGRA;

        Bfree(picfil); picfil = 0;

        if (tsiz.x>>r_downsize <= tilesiz[dapic].x || tsiz.y>>r_downsize <= tilesiz[dapic].y)
            hicr->flags |= (HICR_NOCOMPRESS + HICR_NOSAVE);

        if (glinfo.texcompr && glusetexcompr && !(hicr->flags & HICR_NOSAVE))
            intexfmt = (hasalpha == 255) ? GL_COMPRESSED_RGB_ARB : GL_COMPRESSED_RGBA_ARB;
        else if (hasalpha == 255) intexfmt = GL_RGB;

        if ((doalloc&3)==1)
            bglGenTextures(1, &pth->glpic); //# of textures (make OpenGL allocate structure)
        bglBindTexture(GL_TEXTURE_2D,pth->glpic);

        fixtransparency(pic,tsiz,siz,dameth);
        uploadtexture(doalloc,siz.x,siz.y,intexfmt,texfmt,pic,-1,tsiz.y,
                      dameth | DAMETH_HI | (hicr->flags & HICR_NOCOMPRESS ? DAMETH_NOCOMPRESS : 0));
    }

    // precalculate scaling parameters for replacement
    if (facen > 0)
    {
        pth->scale.x = (float)tsiz.x * (1.0f/64.f);
        pth->scale.y = (float)tsiz.y * (1.0f/64.f);
    }
    else
    {
        pth->scale.x = (float)tsiz.x / (float)tilesiz[dapic].x;
        pth->scale.y = (float)tsiz.y / (float)tilesiz[dapic].y;
    }

#ifdef EDUKE32_TOUCH_DEVICES
    texture_setup(dameth, 5);
#else
    texture_setup(dameth, -1);
#endif

    DO_FREE_AND_NULL(pic);

    if (tsiz.x>>r_downsize <= tilesiz[dapic].x || tsiz.y>>r_downsize <= tilesiz[dapic].y)
        hicr->flags |= HICR_NOCOMPRESS | HICR_NOSAVE;

    pth->picnum = dapic;
    pth->effects = effect;
    pth->flags = TO_PTH_CLAMPED(dameth) | PTH_HIGHTILE | ((facen>0) * PTH_SKYBOX) | ((hasalpha != 255) ? PTH_HASALPHA : 0);
    pth->skyface = facen;
    pth->hicr = hicr;

    if (!gotcache && glinfo.texcompr && glusetexcompr && glusetexcache && !(hicr->flags & HICR_NOSAVE))
    {
        const int32_t nonpow2 = check_nonpow2(siz.x) || check_nonpow2(siz.y);

        // save off the compressed version
        cachead.quality = (hicr->flags & HICR_NOCOMPRESS) ? 0 : r_downsize;
        cachead.xdim = tsiz.x >> cachead.quality;
        cachead.ydim = tsiz.y >> cachead.quality;

        // handle nocompress:
        cachead.flags = nonpow2 * CACHEAD_NONPOW2 | (hasalpha != 255 ? CACHEAD_HASALPHA : 0) |
                        (hicr->flags & HICR_NOCOMPRESS ? CACHEAD_NOCOMPRESS : 0);

        ///            OSD_Printf("Caching \"%s\"\n", fn);
        texcache_writetex(fn, picfillen + (dapalnum << 8), dameth, effect, &cachead);

        if (willprint)
        {
            int32_t etime = getticks() - startticks;
            if (etime >= MIN_CACHETIME_PRINT)
                OSD_Printf("Load tile %4d: p%d-m%d-e%d %s... cached... %d ms\n", dapic, dapalnum, dameth, effect,
                           willprint == 2 ? fn : "", etime);
            willprint = 0;
        }
        else
            OSD_Printf("Cached \"%s\"\n", fn);
    }

    if (willprint)
    {
        int32_t etime = getticks()-startticks;
        if (etime>=MIN_CACHETIME_PRINT)
            OSD_Printf("Load tile %4d: p%d-m%d-e%d %s... %d ms\n", dapic, dapalnum, dameth, effect,
                       willprint==2 ? fn : "", etime);
    }

    return 0;
}

void polymost_setupdetailtexture(const int32_t texunits, const int32_t tex)
{
    bglActiveTextureARB(texunits);

    bglEnable(GL_TEXTURE_2D);
    bglBindTexture(GL_TEXTURE_2D, tex);

    bglTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);

    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);

    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);

    bglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);

    bglTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2.0f);

    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

void polymost_setupglowtexture(const int32_t texunits, const int32_t tex)
{
    bglActiveTextureARB(texunits);

    bglEnable(GL_TEXTURE_2D);
    bglBindTexture(GL_TEXTURE_2D, tex);

    bglTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE_ARB);

    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);

    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);

    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_ONE_MINUS_SRC_ALPHA);

    bglTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
    bglTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS_ARB);
    bglTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);

    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    bglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
#endif

//(dpx,dpy) specifies an n-sided polygon. The polygon must be a convex clockwise loop.
//    n must be <= 8 (assume clipping can double number of vertices)
//method: 0:solid, 1:masked(255 is transparent), 2:transluscent #1, 3:transluscent #2
//    +4 means it's a sprite, so wraparound isn't needed

// drawpoly's hack globals
static int32_t pow2xsplit = 0, skyclamphack = 0;
static float alpha = 0.f;

static inline pthtyp *our_texcache_fetch(int32_t dameth)
{
    // r_usetileshades 1 is TX's method.
    return texcache_fetch(globalpicnum, globalpal, getpalookup((r_usetileshades == 1) ? globvis>>3 : 0, globalshade), dameth);
}

static void drawpoly(vec2f_t *dpxy, int32_t n, int32_t method)
{
    const int32_t method_ = method;
    int32_t i, j, k;
    vec2_t tsiz;
    float f, r, ox, oy, oz, ox2, oy2, oz2, dd[16], uu[16], vv[16], px[16], py[16];

#ifdef YAX_ENABLE
    if (g_nodraw) return;
#endif

    if (method == -1 || (uint32_t)globalpicnum >= MAXTILES) return;

    if (n == 3)
    {
        if ((dpxy[0].x-dpxy[1].x) * (dpxy[2].y-dpxy[1].y) >=
            (dpxy[2].x-dpxy[1].x) * (dpxy[0].y-dpxy[1].y)) return; //for triangle
    }
    else
    {
        f = 0; //f is area of polygon / 2
        for (i=n-2,j=n-1,k=0; k<n; i=j,j=k,k++)
            f += (dpxy[i].x-dpxy[k].x)*dpxy[j].y;
        if (f <= 0) return;
    }

    //Load texture (globalpicnum)
    setgotpic(globalpicnum);
    tsiz = tilesiz[globalpicnum];

    if (palookup[globalpal] == NULL)
        globalpal = 0;
    if (!waloff[globalpicnum])
    {
        loadtile(globalpicnum);
        if (!waloff[globalpicnum])
        {
            if (getrendermode() < REND_POLYMOST) return;
            tsiz.x = tsiz.y = 1;
            method = 1; //Hack to update Z-buffer for invalid mirror textures
        }
    }

    j = 0;

    for (i=0; i<n; i++)
    {
        ox = dpxy[i].x-ghalfx;
        oy = dpxy[i].y-ghoriz;
        oz = ghalfx;

        //Up/down rotation
        ox2 = ox;
        oy2 = oy*gchang - oz*gshang;
        oz2 = oy*gshang + oz*gchang;

        //Tilt rotation
        ox = ox2*gctang - oy2*gstang;
        oy = ox2*gstang + oy2*gctang;
        oz = oz2;

        r = ghalfx / oz;

        dd[j] = (dpxy[i].x*gdx + dpxy[i].y*gdy + gdo)*r;
        uu[j] = (dpxy[i].x*gux + dpxy[i].y*guy + guo)*r;
        vv[j] = (dpxy[i].x*gvx + dpxy[i].y*gvy + gvo)*r;

        px[j] = ox*r + ghalfx;
        py[j] = oy*r + ghoriz;
        if ((!j) || (px[j] != px[j-1]) || (py[j] != py[j-1])) j++;
    }

    while ((j >= 3) && (px[j-1] == px[0]) && (py[j-1] == py[0])) j--;
    if (j < 3) return;

#ifdef USE_OPENGL
    if (getrendermode() >= REND_POLYMOST)
    {
        float hackscx = 1.f, hackscy = 1.f;
        int32_t texunits = GL_TEXTURE0_ARB;
        int32_t xx, yy;

        int32_t jj = j;

        if (skyclamphack) method |= DAMETH_CLAMPED;
        pthtyp *pth = our_texcache_fetch(method&(~3));

        if (!pth)
        {
            if (editstatus)
            {
                Bsprintf(ptempbuf, "pth==NULL! (bad pal?) pic=%d pal=%d", globalpicnum, globalpal);
                polymost_printext256(8,8, editorcolors[15],editorcolors[5], ptempbuf, 0);
            }
            return;
        }

        if (pth->flags & PTH_HASFULLBRIGHT && r_fullbrights && indrawroomsandmasks)
        {
            if (!fullbrightdrawingpass)
                fullbrightdrawingpass = 1;
            else if (fullbrightdrawingpass == 2)
                pth = pth->ofb;
        }

        // If we aren't rendmode 3, we're in Polymer, which means this code is
        // used for rotatesprite only. Polymer handles all the material stuff,
        // just submit the geometry and don't mess with textures.
        if (getrendermode() == REND_POLYMOST)
        {
            bglBindTexture(GL_TEXTURE_2D, pth ? pth->glpic : 0);

            if (srepeat)
                bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
            if (trepeat)
                bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
        }

        // texture scale by parkar request
        if (pth && pth->hicr && !drawingskybox && ((pth->hicr->scale.x != 1.0f) || (pth->hicr->scale.y != 1.0f)))
        {
            bglMatrixMode(GL_TEXTURE);
            bglLoadIdentity();
            bglScalef(pth->hicr->scale.x, pth->hicr->scale.y, 1.0f);
            bglMatrixMode(GL_MODELVIEW);
        }

#ifndef EDUKE32_GLES
        // detail texture
        pthtyp *detailpth = NULL;
        if (r_detailmapping && usehightile && !drawingskybox && hicfindsubst(globalpicnum, DETAILPAL))
            detailpth = texcache_fetch(globalpicnum, DETAILPAL, 0, method&(~3));

        if (detailpth && detailpth->hicr && (detailpth->hicr->palnum == DETAILPAL))
        {
            polymost_setupdetailtexture(++texunits, detailpth ? detailpth->glpic : 0);

            f = detailpth ? detailpth->hicr->scale.x : 1.f;

            bglMatrixMode(GL_TEXTURE);
            bglLoadIdentity();

            if (pth && pth->hicr && ((pth->hicr->scale.x != 1.0f) || (pth->hicr->scale.y != 1.0f)))
                bglScalef(pth->hicr->scale.x, pth->hicr->scale.y, 1.0f);

            if (detailpth && detailpth->hicr && ((detailpth->hicr->scale.x != 1.0f) || (detailpth->hicr->scale.y != 1.0f)))
                bglScalef(detailpth->hicr->scale.x, detailpth->hicr->scale.y, 1.0f);

            bglMatrixMode(GL_MODELVIEW);
        }

        // glow texture
        pthtyp *glowpth = NULL;
        if (r_glowmapping && usehightile && !drawingskybox && hicfindsubst(globalpicnum, GLOWPAL))
            glowpth = texcache_fetch(globalpicnum, GLOWPAL, 0, method&(~3));

        if (glowpth && glowpth->hicr && (glowpth->hicr->palnum == GLOWPAL))
            polymost_setupglowtexture(++texunits, glowpth ? glowpth->glpic : 0);
#endif

        if (pth && (pth->flags & PTH_HIGHTILE))
        {
            hackscx = pth->scale.x;
            hackscy = pth->scale.y;
            tsiz.x = pth->siz.x;
            tsiz.y = pth->siz.y;
        }

        xx = tsiz.x;
        yy = tsiz.y;

        if (!glinfo.texnpot)
        {
            for (xx=1; xx<tsiz.x; xx+=xx)
            {
                /* do nothing */
            }
            for (yy=1; yy<tsiz.y; yy+=yy)
            {
                /* do nothing */
            }
        }

        ox2 = 1.0f/xx;
        oy2 = 1.0f/yy;

        if ((!(method&3)) && (!fullbrightdrawingpass))
        {
            bglDisable(GL_BLEND);
            bglDisable(GL_ALPHA_TEST);
        }
        else
        {
            float al = 0.f; // PLAG : default alphacut was 0.32 before goodalpha
            if (pth && pth->hicr && pth->hicr->alphacut >= 0.f) al = pth->hicr->alphacut;
            if (alphahackarray[globalpicnum])
                al=alphahackarray[globalpicnum];
            if (!waloff[globalpicnum]) al = 0.f;        // invalid textures ignore the alpha cutoff settings
            bglEnable(GL_BLEND);
            bglEnable(GL_ALPHA_TEST);
            bglAlphaFunc(GL_GREATER,al);
        }

        {
            float pc[4];

#ifdef POLYMER
            if (getrendermode() == REND_POLYMER && pr_artmapping && polymer_eligible_for_artmap(globalpicnum, pth))
                pc[0] = pc[1] = pc[2] = 1.0f;
            else
#endif
                pc[0] = pc[1] = pc[2] = getshadefactor(globalshade);

            // spriteext full alpha control
            pc[3] = float_trans[method&3] * (1.f - alpha);

            // tinting happens only to hightile textures, and only if the texture we're
            // rendering isn't for the same palette as what we asked for
            if (!(hictinting[globalpal].f & HICTINT_COLORIZE))
            {
                if (pth && (pth->flags & PTH_HIGHTILE))
                {
                    if (pth->palnum != globalpal || (hictinting[globalpal].f & HICTINT_APPLYOVERALTPAL))
                        hictinting_apply(pc, globalpal);

                    if (have_basepal_tint())
                        hictinting_apply(pc, MAXPALOOKUPS-1);
                }
                // hack: this is for drawing the 8-bit crosshair recolored in polymost
                else if (hictinting[globalpal].f & HICTINT_USEONART)
                    hictinting_apply(pc, globalpal);
            }

            bglColor4f(pc[0],pc[1],pc[2],pc[3]);
        }

        //Hack for walls&masked walls which use textures that are not a power of 2
        if ((pow2xsplit) && (tsiz.x != xx))
        {
            int32_t nn, ix0, ix1;

            float ngdx = 0.f, ngdy = 0.f, ngdo = 0.f, ngux = 0.f, nguy = 0.f, nguo = 0.f, uoffs;
            float ngvx = 0.f, ngvy = 0.f, ngvo = 0.f, dp, up, vp, du0 = 0.f, du1 = 0.f, dui, duj;

            ox = py[1]-py[2]; oy = py[2]-py[0]; oz = py[0]-py[1];
            r = 1.f / (ox*px[0] + oy*px[1] + oz*px[2]);
            ngdx = (ox*dd[0] + oy*dd[1] + oz*dd[2])*r;
            ngux = (ox*uu[0] + oy*uu[1] + oz*uu[2])*r;
            ngvx = (ox*vv[0] + oy*vv[1] + oz*vv[2])*r;
            ox = px[2]-px[1]; oy = px[0]-px[2]; oz = px[1]-px[0];
            ngdy = (ox*dd[0] + oy*dd[1] + oz*dd[2])*r;
            nguy = (ox*uu[0] + oy*uu[1] + oz*uu[2])*r;
            ngvy = (ox*vv[0] + oy*vv[1] + oz*vv[2])*r;
            ox = px[0]-0.5f; oy = py[0]-0.5f; //.5 centers texture nicely
            ngdo = dd[0] - ox*ngdx - oy*ngdy;
            nguo = uu[0] - ox*ngux - oy*nguy;
            ngvo = vv[0] - ox*ngvx - oy*ngvy;

            ngux *= hackscx; nguy *= hackscx; nguo *= hackscx;
            ngvx *= hackscy; ngvy *= hackscy; ngvo *= hackscy;
            uoffs = ((float)(xx-tsiz.x)*0.5f);
            ngux -= ngdx*uoffs;
            nguy -= ngdy*uoffs;
            nguo -= ngdo*uoffs;

            //Find min&max u coordinates (du0...du1)
            for (i=0; i<jj; i++)
            {
                ox = px[i]; oy = py[i];
                f = (ox*ngux + oy*nguy + nguo) / (ox*ngdx + oy*ngdy + ngdo);
                if (!i) { du0 = du1 = f; continue; }
                if (f < du0) du0 = f;
                else if (f > du1) du1 = f;
            }

            f = 1.0f/tsiz.x;
            ix0 = Blrintf(floorf(du0*f));
            ix1 = Blrintf(floorf(du1*f));
            for (; ix0<=ix1; ix0++)
            {
                du0 = (float)(ix0*tsiz.x);   // + uoffs;
                du1 = (float)((ix0+1)*tsiz.x); // + uoffs;

                i = 0; nn = 0;
                duj = (px[i]*ngux + py[i]*nguy + nguo) / (px[i]*ngdx + py[i]*ngdy + ngdo);
                do
                {
                    j = i+1; if (j == jj) j = 0;

                    dui = duj;
                    duj = (px[j]*ngux + py[j]*nguy + nguo) / (px[j]*ngdx + py[j]*ngdy + ngdo);

                    if ((du0 <= dui) && (dui <= du1))
                    {
                        uu[nn] = px[i];
                        vv[nn] = py[i];
                        nn++;
                    }

                    //ox*(ngux-ngdx*du1) + oy*(nguy-ngdy*du1) + (nguo-ngdo*du1) = 0
                    //(px[j]-px[i])*f + px[i] = ox
                    //(py[j]-py[i])*f + py[i] = oy

                    ///Solve for f
                    //((px[j]-px[i])*f + px[i])*(ngux-ngdx*du1) +
                    //((py[j]-py[i])*f + py[i])*(nguy-ngdy*du1) + (nguo-ngdo*du1) = 0

#define DRAWPOLY_MATH_BULLSHIT(X) do { f = -(px[i] *(ngux-ngdx*X) + py[i] *(nguy-ngdy*X) + (nguo-ngdo*X)) / ((px[j]-px[i])*(ngux-ngdx*X) + (py[j]-py[i])*(nguy-ngdy*X)); \
                                        uu[nn] = (px[j]-px[i])*f + px[i]; vv[nn] = (py[j]-py[i])*f + py[i]; nn++; } while (0)


                    if (duj <= dui)
                    {
                        if ((du1 < duj) != (du1 < dui))
                            DRAWPOLY_MATH_BULLSHIT(du1);
                        if ((du0 < duj) != (du0 < dui))
                            DRAWPOLY_MATH_BULLSHIT(du0);
                    }
                    else
                    {
                        if ((du0 < duj) != (du0 < dui))
                            DRAWPOLY_MATH_BULLSHIT(du0);
                        if ((du1 < duj) != (du1 < dui))
                            DRAWPOLY_MATH_BULLSHIT(du1);
                    }
                    i = j;

#undef DRAWPOLY_MATH_BULLSHIT

                }
                while (i);

                if (nn < 3) continue;

                bglBegin(GL_TRIANGLE_FAN);
                for (i=0; i<nn; i++)
                {
                    ox = uu[i]; oy = vv[i];
                    dp = ox*ngdx + oy*ngdy + ngdo;
                    up = ox*ngux + oy*nguy + nguo;
                    vp = ox*ngvx + oy*ngvy + ngvo;
                    r = 1.f/dp;
                    if (texunits > GL_TEXTURE0_ARB)
                    {
                        j = GL_TEXTURE0_ARB;
                        while (j <= texunits)
                            bglMultiTexCoord2fARB(j++, (up*r-du0+uoffs)*ox2,vp*r*oy2);
                    }
                    else
                        bglTexCoord2f((up*r-du0+uoffs)*ox2,vp*r*oy2);
                    bglVertex3f((ox-ghalfx)*r*grhalfxdown10x,(ghoriz-oy)*r*grhalfxdown10,r*(1.f/1024.f));
                }
                bglEnd();
            }
        }
        else
        {
            ox2 *= hackscx; oy2 *= hackscy;
            bglBegin(GL_TRIANGLE_FAN);
            for (i=0; i<jj; i++)
            {
                r = 1.f/dd[i];
                if (texunits > GL_TEXTURE0_ARB)
                {
                    j = GL_TEXTURE0_ARB;
                    while (j <= texunits)
                        bglMultiTexCoord2fARB(j++, uu[i]*r*ox2,vv[i]*r*oy2);
                }
                else
                    bglTexCoord2f(uu[i]*r*ox2,vv[i]*r*oy2);
                bglVertex3f((px[i]-ghalfx)*r*grhalfxdown10x,(ghoriz-py[i])*r*grhalfxdown10,r*(1.f/1024.f));
            }
            bglEnd();
        }

        while (texunits >= GL_TEXTURE0_ARB)
        {
            bglActiveTextureARB(texunits);
            bglMatrixMode(GL_TEXTURE);
            bglLoadIdentity();
            bglMatrixMode(GL_MODELVIEW);
            if (texunits > GL_TEXTURE0_ARB)
            {
                bglTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 1.0f);
                bglDisable(GL_TEXTURE_2D);
            }
            texunits--;
        }

        if (getrendermode() == REND_POLYMOST)
        {
            if (srepeat)
                bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,glinfo.clamptoedge?GL_CLAMP_TO_EDGE:GL_CLAMP);
            if (trepeat)
                bglTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,glinfo.clamptoedge?GL_CLAMP_TO_EDGE:GL_CLAMP);
        }

        if (fullbrightdrawingpass == 1) // tile has fullbright colors ?
        {
            int32_t shadeforfullbrightpass = globalshade; // save the current shade
            fullbrightdrawingpass = 2;
            globalshade = -128; // fullbright
            bglDisable(GL_FOG);
            drawpoly(dpxy, n, method_); // draw them afterwards, then. :)
            if (!nofog) bglEnable(GL_FOG);
            globalshade = shadeforfullbrightpass;
            fullbrightdrawingpass = 0;
        }
        return;
    }
#endif
}


static inline void vsp_finalize_init(vsptyp *vsp, int32_t vcnt)
{
    int32_t i;

    for (i=0; i<vcnt; i++)
    {
        vsp[i].cy[1] = vsp[i+1].cy[0]; vsp[i].ctag = i;
        vsp[i].fy[1] = vsp[i+1].fy[0]; vsp[i].ftag = i;
        vsp[i].n = i+1; vsp[i].p = i-1;
//        vsp[i].tag = -1;
    }
    vsp[vcnt-1].n = 0; vsp[0].p = vcnt-1;

    //VSPMAX-1 is dummy empty node
    for (i=vcnt; i<VSPMAX; i++) { vsp[i].n = i+1; vsp[i].p = i-1; }
    vsp[VSPMAX-1].n = vcnt; vsp[vcnt].p = VSPMAX-1;
}

static inline void vsdel(vsptyp *vsp, int32_t i)
{
    //Delete i
    vsp[vsp[i].n].p = vsp[i].p;
    vsp[vsp[i].p].n = vsp[i].n;

    //Add i to empty list
    vsp[i].n = vsp[VSPMAX-1].n;
    vsp[i].p = VSPMAX-1;
    vsp[vsp[VSPMAX-1].n].p = i;
    vsp[VSPMAX-1].n = i;
}

static inline int32_t vsinsaft(vsptyp *vsp, int32_t i)
{
    //i = next element from empty list
    int32_t r = vsp[VSPMAX-1].n;
    vsp[vsp[r].n].p = VSPMAX-1;
    vsp[VSPMAX-1].n = vsp[r].n;

    vsp[r] = vsp[i]; //copy i to r

    //insert r after i
    vsp[r].p = i; vsp[r].n = vsp[i].n;
    vsp[vsp[i].n].p = r; vsp[i].n = r;

    return(r);
}

static int32_t domostpolymethod = 0;

void domost(float x0, float y0, float x1, float y1)
{
    vec2f_t dpxy[4], n0, n1;
    float slop, dx;
    float spx[4], /*spy[4],*/ cy[2], cv[2];
    int32_t i, k, z, vcnt = 0, scnt, newi, dir = 0, spt[4];

    alpha = 0.f;

    if (x0 < x1)
    {
        dir = 1; //clip dmost (floor)
        y0 -= .01f;
        y1 -= .01f;
    }
    else
    {
        if (x0 == x1) return;
        swapfloat(&x0, &x1);
        swapfloat(&y0, &y1);
        //dir = 0; //clip umost (ceiling)
        //y0 += .01; y1 += .01; //necessary?
    }

    slop = (y1-y0)/(x1-x0);
    for (i=vsp[0].n; i; i=newi)
    {
        newi = vsp[i].n; n0.x = vsp[i].x; n1.x = vsp[newi].x;
        if ((x0 >= n1.x) || (n0.x >= x1) || (vsp[i].ctag <= 0)) continue;
        dx = n1.x-n0.x;
        cy[0] = vsp[i].cy[0]; cv[0] = vsp[i].cy[1]-cy[0];
        cy[1] = vsp[i].fy[0]; cv[1] = vsp[i].fy[1]-cy[1];

        scnt = 0;

        //Test if left edge requires split (x0,y0) (nx0,cy(0)),<dx,cv(0)>
        if ((x0 > n0.x) && (x0 < n1.x))
        {
            const float t = (x0-n0.x)*cv[dir] - (y0-cy[dir])*dx;
            if (((!dir) && (t < 0.f)) || ((dir) && (t > 0.f)))
                { spx[scnt] = x0; /*spy[scnt] = y0;*/ spt[scnt] = -1; scnt++; }
        }

        //Test for intersection on umost (0) and dmost (1)

        {
            const float d[2] ={
                ((y0-y1) * dx) - ((x0-x1) * cv[0]),
                ((y0-y1) * dx) - ((x0-x1) * cv[1])
            };

            const float n[2] ={
                ((y0-cy[0]) * dx) - ((x0-n0.x) * cv[0]),
                ((y0-cy[1]) * dx) - ((x0-n0.x) * cv[1])
            };

            const float fnx[2] ={
                x0 + ((n[0]/d[0]) * (x1-x0)),
                x0 + ((n[1]/d[1]) * (x1-x0))
            };

            if ((Bfabsf(d[0]) > Bfabsf(n[0])) && (d[0] * n[0] >= 0.f) && (fnx[0] > n0.x) && (fnx[0] < n1.x))
                spx[scnt] = fnx[0], spt[scnt++] = 0;

            if ((Bfabsf(d[1]) > Bfabsf(n[1])) && (d[1] * n[1] >= 0.f) && (fnx[1] > n0.x) && (fnx[1] < n1.x))
                spx[scnt] = fnx[1], spt[scnt++] = 1;
        }

        //Nice hack to avoid full sort later :)
        if ((scnt >= 2) && (spx[scnt-1] < spx[scnt-2]))
        {
            swapfloat(&spx[scnt-1], &spx[scnt-2]);
            swaplong(&spt[scnt-1], &spt[scnt-2]);
            /* f = spy[scnt-1]; spy[scnt-1] = spy[scnt-2]; spy[scnt-2] = f; */
        }

        //Test if right edge requires split
        if ((x1 > n0.x) && (x1 < n1.x))
        {
            const float t = (x1-n0.x)*cv[dir] - (y1-cy[dir])*dx;
            if (((!dir) && (t < 0)) || ((dir) && (t > 0)))
                { spx[scnt] = x1; /* spy[scnt] = y1; */ spt[scnt] = -1; scnt++; }
        }

        vsp[i].tag = vsp[newi].tag = -1;

        dx = 1.f/dx;

        for (z=0; z<=scnt; z++,i=vcnt)
        {
            float t;

            if (z == scnt)
                goto skip;

            t = (spx[z]-n0.x)*dx;
            vcnt = vsinsaft(vsp, i);
            vsp[i].cy[1] = t*cv[0] + cy[0];
            vsp[i].fy[1] = t*cv[1] + cy[1];
            vsp[vcnt].x = spx[z];
            vsp[vcnt].cy[0] = vsp[i].cy[1];
            vsp[vcnt].fy[0] = vsp[i].fy[1];
            vsp[vcnt].tag = spt[z];

skip: ;
            int32_t ni = vsp[i].n; if (!ni) continue; //this 'if' fixes many bugs!
            float dx0 = vsp[i].x; if (x0 > dx0) continue;
            float dx1 = vsp[ni].x; if (x1 < dx1) continue;
            n0.y = (dx0-x0)*slop + y0;
            n1.y = (dx1-x0)*slop + y0;

            //      dx0           dx1
            //       ~             ~
            //----------------------------
            //     t0+=0         t1+=0
            //   vsp[i].cy[0]  vsp[i].cy[1]
            //============================
            //     t0+=1         t1+=3
            //============================
            //   vsp[i].fy[0]    vsp[i].fy[1]
            //     t0+=2         t1+=6
            //
            //     ny0 ?         ny1 ?

            k = 1+3;
            if ((vsp[i].tag == 0) || (n0.y <= vsp[i].cy[0]+.01f)) k--;
            if ((vsp[i].tag == 1) || (n0.y >= vsp[i].fy[0]-.01f)) k++;
            if ((vsp[ni].tag == 0) || (n1.y <= vsp[i].cy[1]+.01f)) k -= 3;
            if ((vsp[ni].tag == 1) || (n1.y >= vsp[i].fy[1]-.01f)) k += 3;

            dpxy[0].x = dx0;
            dpxy[1].x = dx1;

            if (!dir)
            {
                dpxy[0].y = vsp[i].cy[0];
                dpxy[1].y = vsp[i].cy[1];

                switch (k)
                {
                case 4:
                case 5:
                case 7:
                    dpxy[2].x = dx1; dpxy[3].x = dx0;
                    dpxy[2].y = n1.y; dpxy[3].y = n0.y;
                    vsp[i].cy[0] = n0.y; vsp[i].cy[1] = n1.y; vsp[i].ctag = gtag;
                    drawpoly(dpxy, 4, domostpolymethod);
                    break;
                case 1:
                case 2:
                    dpxy[2].x = dx0;
                    dpxy[2].y = n0.y;
                    vsp[i].cy[0] = n0.y; vsp[i].ctag = gtag;
                    drawpoly(dpxy, 3, domostpolymethod);
                    break;
                case 3:
                case 6:
                    dpxy[2].x = dx1;
                    dpxy[2].y = n1.y;
                    vsp[i].cy[1] = n1.y; vsp[i].ctag = gtag;
                    drawpoly(dpxy, 3, domostpolymethod);
                    break;
                case 8:
                    dpxy[2].x = dx1; dpxy[2].y = vsp[i].fy[1];
                    dpxy[3].x = dx0; dpxy[3].y = vsp[i].fy[0];
                    vsp[i].ctag = vsp[i].ftag = -1;
                    drawpoly(dpxy, 4, domostpolymethod);
                default:
                    break;
                }
            }
            else
            {
                switch (k)
                {
                case 4:
                case 3:
                case 1:
                    dpxy[2].x = dx1; dpxy[3].x = dx0;
                    dpxy[0].y = n0.y; dpxy[1].y = n1.y; dpxy[2].y = vsp[i].fy[1]; dpxy[3].y = vsp[i].fy[0];
                    vsp[i].fy[0] = n0.y; vsp[i].fy[1] = n1.y; vsp[i].ftag = gtag;
                    drawpoly(dpxy, 4, domostpolymethod);
                    break;
                case 7:
                case 6:
                    dpxy[2].x = dx0;
                    dpxy[0].y = n0.y; dpxy[1].y = vsp[i].fy[1]; dpxy[2].y = vsp[i].fy[0];
                    vsp[i].fy[0] = n0.y; vsp[i].ftag = gtag;
                    drawpoly(dpxy, 3, domostpolymethod);
                    break;
                case 5:
                case 2:
                    dpxy[2].x = dx1;
                    dpxy[0].y = vsp[i].fy[0]; dpxy[1].y = n1.y; dpxy[2].y = vsp[i].fy[1];
                    vsp[i].fy[1] = n1.y; vsp[i].ftag = gtag;
                    drawpoly(dpxy, 3, domostpolymethod);
                    break;
                case 0:
                    dpxy[2].x = dx1; dpxy[3].x = dx0;
                    dpxy[0].y = vsp[i].cy[0]; dpxy[1].y = vsp[i].cy[1]; dpxy[2].y = vsp[i].fy[1]; dpxy[3].y = vsp[i].fy[0];
                    vsp[i].ctag = vsp[i].ftag = -1;
                    drawpoly(dpxy, 4, domostpolymethod);
                default:
                    break;
                }
            }
        }
    }

    gtag++;

    //Combine neighboring vertical strips with matching collinear top&bottom edges
    //This prevents x-splits from propagating through the entire scan
    i = vsp[0].n;

    while (i)
    {
        int32_t ni = vsp[i].n;

        if ((vsp[i].cy[0] >= vsp[i].fy[0]) && (vsp[i].cy[1] >= vsp[i].fy[1]))
            vsp[i].ctag = vsp[i].ftag = -1;

        if ((vsp[i].ctag == vsp[ni].ctag) && (vsp[i].ftag == vsp[ni].ftag))
        {
            vsp[i].cy[1] = vsp[ni].cy[1];
            vsp[i].fy[1] = vsp[ni].fy[1];
            vsdel(vsp, ni);
        }
        else i = ni;
    }
}

void polymost_editorfunc(void)
{
    vec3_t v;
    vec3f_t o, o2;
    int32_t cz, fz;
    hitdata_t hit;
    vec3_t vect;
    const float ratio = get_projhack_ratio();

    o2.x = (searchx-ghalfx)/ratio;
    o2.y = (searchy-ghoriz)/ratio;  // ghoriz is (ydimen>>1) here
    o2.z = ghalfx;

    //Tilt rotation
    o.x = o2.x*gctang + o2.y*gstang;
    o.y = o2.y*gctang - o2.x*gstang;
    o.z = o2.z;

    //Up/down rotation
    o2.x = o.z*gchang - o.y*gshang;
    o2.y = o.x;
    o2.z = o.y*gchang + o.z*gshang;

    //Standard Left/right rotation
    v.x = Blrintf (o2.x*fcosglobalang - o2.y*fsinglobalang);
    v.y = Blrintf (o2.x*fsinglobalang + o2.y*fcosglobalang);
    v.z = Blrintf (o2.z*16384.f);

    vect.x = globalposx;
    vect.y = globalposy;
    vect.z = globalposz;

    hitallsprites = 1;
    hitscan((const vec3_t *) &vect, globalcursectnum, //Start position
        v.x>>10, v.y>>10, v.z>>6, &hit, 0xffff0030);

    if (hit.sect != -1) // if hitsect is -1, hitscan overflowed somewhere
    {
        getzsofslope(hit.sect, hit.pos.x, hit.pos.y, &cz, &fz);
        hitallsprites = 0;

        searchsector = hit.sect;
        if (hit.pos.z<cz) searchstat = 1;
        else if (hit.pos.z>fz) searchstat = 2;
        else if (hit.wall >= 0)
        {
            searchbottomwall = searchwall = hit.wall; searchstat = 0;
            if (wall[hit.wall].nextwall >= 0)
            {
                int32_t cz, fz;
                getzsofslope(wall[hit.wall].nextsector, hit.pos.x, hit.pos.y, &cz, &fz);
                if (hit.pos.z > fz)
                {
                    searchisbottom = 1;
                    if (wall[hit.wall].cstat&2) //'2' bottoms of walls
                        searchbottomwall = wall[hit.wall].nextwall;
                }
                else
                {
                    searchisbottom = 0;
                    if ((hit.pos.z > cz) && (wall[hit.wall].cstat&(16+32))) //masking or 1-way
                        searchstat = 4;
                }
            }
        }
        else if (hit.sprite >= 0) { searchwall = hit.sprite; searchstat = 3; }
        else
        {
            int32_t cz, fz;
            getzsofslope(hit.sect, hit.pos.x, hit.pos.y, &cz, &fz);
            if ((hit.pos.z<<1) < cz+fz) searchstat = 1; else searchstat = 2;
            //if (vz < 0) searchstat = 1; else searchstat = 2; //Won't work for slopes :/
        }

        if (preview_mouseaim && spritesortcnt < MAXSPRITESONSCREEN)
        {
            tspritetype *tsp = &tsprite[spritesortcnt];
            double dadist, x, y, z;
            Bmemcpy(tsp, &hit.pos, sizeof(vec3_t));
            x = tsp->x-globalposx; y=tsp->y-globalposy; z=(tsp->z-globalposz)/16.0;
            dadist = Bsqrt(x*x + y*y + z*z);
            tsp->sectnum = hit.sect;
            tsp->picnum = 2523;  // CROSSHAIR
            tsp->cstat = 128;
            tsp->owner = MAXSPRITES-1;
            tsp->xrepeat = tsp->yrepeat = min(max(1, (int32_t) (dadist*48.0/3200.0)), 255);
            sprite[tsp->owner].xoffset = sprite[tsp->owner].yoffset = 0;
            tspriteptr[spritesortcnt++] = tsp;
        }

        if ((searchstat==1 || searchstat==2) && searchsector>=0)
        {
            int32_t scrv[2] ={ (v.x>>12), (v.y>>12) };
            int32_t scrv_r[2] ={ scrv[1], -scrv[0] };
            walltype *wal = &wall[sector[searchsector].wallptr];
            uint64_t wdistsq, bestwdistsq=0x7fffffff;
            int32_t k, bestk=-1;

            for (k=0; k<sector[searchsector].wallnum; k++)
            {
                int32_t w1[2] ={ wal[k].x, wal[k].y };
                int32_t w2[2] ={ wall[wal[k].point2].x, wall[wal[k].point2].y };
                int32_t w21[2] ={ w1[0]-w2[0], w1[1]-w2[1] };
                int32_t pw1[2] ={ w1[0]-hit.pos.x, w1[1]-hit.pos.y };
                int32_t pw2[2] ={ w2[0]-hit.pos.x, w2[1]-hit.pos.y };
                float w1d = (float) (scrv_r[0]*pw1[0] + scrv_r[1]*pw1[1]);
                float w2d = (float) (scrv_r[0]*pw2[0] + scrv_r[1]*pw2[1]);
                int32_t ptonline[2], scrp[2];
                int64_t t1, t2;

                w2d = -w2d;
                if ((w1d==0 && w2d==0) || (w1d<0 || w2d<0))
                    continue;
                ptonline[0] = (int32_t) (w2[0]+(w2d/(w1d+w2d))*w21[0]);
                ptonline[1] = (int32_t) (w2[1]+(w2d/(w1d+w2d))*w21[1]);
                scrp[0] = ptonline[0]-vect.x;
                scrp[1] = ptonline[1]-vect.y;
                if (scrv[0]*scrp[0] + scrv[1]*scrp[1] <= 0)
                    continue;
                t1=scrp[0]; t2=scrp[1];
                wdistsq = t1*t1 + t2*t2;
                if (wdistsq < bestwdistsq)
                {
                    bestk = k;
                    bestwdistsq = wdistsq;
                }
            }

            if (bestk >= 0)
                searchwall = sector[searchsector].wallptr + bestk;
        }
    }
    searchit = 0;
}

void polymost_scansector(int32_t sectnum);

// variables that are set to ceiling- or floor-members, depending
// on which one is processed right now
static int32_t global_cf_z;
static float global_cf_xpanning, global_cf_ypanning, global_cf_heinum;
static int32_t global_cf_shade, global_cf_pal, global_cf_fogpal;
static int32_t (*global_getzofslope_func)(int16_t, int32_t, int32_t);

static void polymost_internal_nonparallaxed(float nx0, float ny0, float nx1, float ny1, float ryp0, float ryp1,
                                            float x0, float x1, float cf_y0, float cf_y1, int32_t have_floor,
                                            int32_t sectnum)
{
    float ft[4], fx, fy, ox, oy, oz, ox2, oy2;
    int32_t i;

    const sectortype *sec = &sector[sectnum];

    // comments from floor code:
            //(singlobalang/-16384*(sx-ghalfx) + 0*(sy-ghoriz) + (cosviewingrangeglobalang/16384)*ghalfx)*d + globalposx    = u*16
            //(cosglobalang/ 16384*(sx-ghalfx) + 0*(sy-ghoriz) + (sinviewingrangeglobalang/16384)*ghalfx)*d + globalposy    = v*16
            //(                  0*(sx-ghalfx) + 1*(sy-ghoriz) + (                             0)*ghalfx)*d + globalposz/16 = (sec->floorz/16)
    if (!(globalorientation&64))
        { ft[0] = fglobalposx; ft[1] = fglobalposy; ft[2] = fcosglobalang; ft[3] = fsinglobalang; }
    else
    {
        float r;
        //relative alignment
        fx = (float)(wall[wall[sec->wallptr].point2].x-wall[sec->wallptr].x);
        fy = (float)(wall[wall[sec->wallptr].point2].y-wall[sec->wallptr].y);
        r = polymost_invsqrt(fx*fx+fy*fy); fx *= r; fy *= r;
        ft[2] = fcosglobalang*fx + fsinglobalang*fy;
        ft[3] = fsinglobalang*fx - fcosglobalang*fy;
        ft[0] = ((float)(globalposx-wall[sec->wallptr].x))*fx + ((float)(globalposy-wall[sec->wallptr].y))*fy;
        ft[1] = ((float)(globalposy-wall[sec->wallptr].y))*fx - ((float)(globalposx-wall[sec->wallptr].x))*fy;
        if (!(globalorientation&4)) globalorientation ^= 32; else globalorientation ^= 16;
    }
    gdx = 0;
    gdy = gxyaspect;
    if (!(globalorientation&2))
        if (global_cf_z-globalposz)  // PK 2012: don't allow div by zero
            gdy /= (double)(global_cf_z-globalposz);
    gdo = -ghoriz*gdy;
    if (globalorientation&8) { ft[0] *= (1.f/8.f); ft[1] *= (-1.f/8.f); ft[2] *= (1.f/2097152.f); ft[3] *= (1.0f/2097152.f); }
    else { ft[0] *= (1.0f/16.f); ft[1] *= (-1.0f/16.f); ft[2] *= (1.0f/4194304.f); ft[3] *= (1.0f/4194304.f); }
    gux = ft[3]*((double)viewingrange)*(-1.0/65536.0);
    gvx = ft[2]*((double)viewingrange)*(-1.0/65536.0);
    guy = ft[0]*gdy; gvy = ft[1]*gdy;
    guo = ft[0]*gdo; gvo = ft[1]*gdo;
    guo += (ft[2]-gux)*ghalfx;
    gvo -= (ft[3]+gvx)*ghalfx;

    //Texture flipping
    if (globalorientation&4)
    {
        double r;

        r = gux; gux = gvx; gvx = r;
        r = guy; guy = gvy; gvy = r;
        r = guo; guo = gvo; gvo = r;
    }
    if (globalorientation&16) { gux = -gux; guy = -guy; guo = -guo; }
    if (globalorientation&32) { gvx = -gvx; gvy = -gvy; gvo = -gvo; }

    //Texture panning
    fx = global_cf_xpanning*((float)(1<<(picsiz[globalpicnum]&15)))*(1.0f/256.f);
    fy = global_cf_ypanning*((float)(1<<(picsiz[globalpicnum]>>4)))*(1.0f/256.f);
    if ((globalorientation&(2+64)) == (2+64)) //Hack for panning for slopes w/ relative alignment
    {
        float r = global_cf_heinum * (1.0f/4096.f); r = polymost_invsqrt(r*r+1);
        if (!(globalorientation&4)) fy *= r; else fx *= r;
    }
    guy += gdy*fx; guo += gdo*fx;
    gvy += gdy*fy; gvo += gdo*fy;

    if (globalorientation&2) //slopes
    {
        double px[3], py[3];
        float dd[3], uu[3], vv[3];
        float r;

        px[0] = x0; py[0] = ryp0 + (double)ghoriz;
        px[1] = x1; py[1] = ryp1 + (double)ghoriz;

        //Pick some point guaranteed to be not collinear to the 1st two points
        ox = nx0 + (ny1-ny0);
        oy = ny0 + (nx0-nx1);
        ox2 = (oy-fglobalposy)*gcosang  - (ox-fglobalposx)*gsinang ;
        oy2 = (ox-fglobalposx)*gcosang2 + (oy-fglobalposy)*gsinang2;
        oy2 = 1.f/oy2;
        px[2] = ghalfx*ox2*oy2 + ghalfx; oy2 *= gyxscale;
        py[2] = oy2 + (double)ghoriz;

        for (i=0; i<3; i++)
        {
            dd[i] = px[i]*gdx + py[i]*gdy + gdo;
            uu[i] = px[i]*gux + py[i]*guy + guo;
            vv[i] = px[i]*gvx + py[i]*gvy + gvo;
        }

        py[0] = cf_y0;
        py[1] = cf_y1;
        py[2] = (double)((float)(global_getzofslope_func(sectnum,Blrintf(ox),Blrintf(oy))-globalposz)*oy2 + ghoriz);

        ox = py[1]-py[2]; oy = py[2]-py[0]; oz = py[0]-py[1];
        r = 1.0 / (ox*px[0] + oy*px[1] + oz*px[2]);
        gdx = (ox*dd[0] + oy*dd[1] + oz*dd[2])*r;
        gux = (ox*uu[0] + oy*uu[1] + oz*uu[2])*r;
        gvx = (ox*vv[0] + oy*vv[1] + oz*vv[2])*r;
        ox = px[2]-px[1]; oy = px[0]-px[2]; oz = px[1]-px[0];
        gdy = (ox*dd[0] + oy*dd[1] + oz*dd[2])*r;
        guy = (ox*uu[0] + oy*uu[1] + oz*uu[2])*r;
        gvy = (ox*vv[0] + oy*vv[1] + oz*vv[2])*r;
        gdo = dd[0] - px[0]*gdx - py[0]*gdy;
        guo = uu[0] - px[0]*gux - py[0]*guy;
        gvo = vv[0] - px[0]*gvx - py[0]*gvy;

        if (globalorientation&64) //Hack for relative alignment on slopes
        {
            r = global_cf_heinum * (1.0f / 4096.f);
            r = Bsqrtf(r*r+1);
            if (!(globalorientation&4)) { gvx *= r; gvy *= r; gvo *= r; }
            else { gux *= r; guy *= r; guo *= r; }
        }
    }

    domostpolymethod = (globalorientation>>7)&3;

    pow2xsplit = 0;
    alpha = 0.f;

    if (!nofog) calc_and_apply_fog(globalpicnum, fogpal_shade(sec, global_cf_shade), sec->visibility,
        POLYMOST_CHOOSE_FOG_PAL(global_cf_fogpal, global_cf_pal));

    if (have_floor)
    {
        if (globalposz >= getflorzofslope(sectnum, globalposx, globalposy))
            domostpolymethod = -1; //Back-face culling

        domost(x0, cf_y0, x1, cf_y1); //flor
    }
    else
    {
        if (globalposz <= getceilzofslope(sectnum, globalposx, globalposy))
            domostpolymethod = -1; //Back-face culling

        domost(x1, cf_y1, x0, cf_y0); //ceil
    }

    domostpolymethod = 0;
}

static void calc_ypanning(int32_t refposz, float ryp0, float ryp1,
                          float x0, float x1, uint8_t ypan, uint8_t yrepeat,
                          int32_t dopancor)
{
    int32_t i;
    float t0, t1, t, fy;

    t0 = ((float)(refposz-globalposz))*ryp0 + ghoriz;
    t1 = ((float)(refposz-globalposz))*ryp1 + ghoriz;
    t = ((gdx*x0 + gdo) * (float)yrepeat) / ((x1-x0) * ryp0 * 2048.f);
    i = (1<<(picsiz[globalpicnum]>>4)); if (i < tilesiz[globalpicnum].y) i <<= 1;

#ifdef NEW_MAP_FORMAT
    if (g_loadedMapVersion >= 10)
        i = tilesiz[globalpicnum].y;
    else
#endif
    if (polymost_is_npotmode())
    {
        t *= (float)tilesiz[globalpicnum].y / i;
        i = tilesiz[globalpicnum].y;
    }
    else if (dopancor)
    {
        // Carry out panning "correction" to make it look like classic in some
        // cases, but failing in the general case.
        int32_t yoffs = Blrintf((i-tilesiz[globalpicnum].y)*(255.f/i));

        if (ypan > 256-yoffs)
            ypan -= yoffs;
    }

    fy = (float) (ypan * i) * (1.f/256.f);
    gvx = (t0-t1)*t;
    gvy = (x1-x0)*t;
    gvo = -gvx*x0 - gvy*t0 + fy*gdo; gvx += fy*gdx; gvy += fy*gdy;
}

static inline int32_t testvisiblemost(float x0, float x1)
{
    int32_t i, newi;

    for (i=vsp[0].n; i; i=newi)
    {
        newi = vsp[i].n;
        if ((x0 < vsp[newi].x) && (vsp[i].x < x1) && (vsp[i].ctag >= 0)) return(1);
    }
    return(0);
}

static void polymost_drawalls(int32_t bunch)
{
    sectortype *sec, *nextsec;
    walltype *wal, *wal2, *nwal;
    float ox, oy, oz, dd[3], vv[3];
    float fx, x0, x1, cy0, cy1, fy0, fy1, xp0, yp0, xp1, yp1, ryp0, ryp1, nx0, ny0, nx1, ny1;
    float t, r, t0, t1, ocy0, ocy1, ofy0, ofy1, oxp0, oyp0, ft[4];
    float oguo, ogux, oguy;
    int32_t i, x, y, z, cz, fz, wallnum, sectnum, nextsectnum;

    int32_t dapskybits;
    const int8_t *dapskyoff;

    alpha = 0.f;

    sectnum = thesector[bunchfirst[bunch]]; sec = &sector[sectnum];

    //DRAW WALLS SECTION!
    for (z=bunchfirst[bunch]; z>=0; z=bunchp2[z])
    {
        wallnum = thewall[z]; wal = &wall[wallnum]; wal2 = &wall[wal->point2];
        nextsectnum = wal->nextsector;
        nextsec = nextsectnum>=0 ? &sector[nextsectnum] : NULL;

#ifdef YAX_ENABLE
        if (yax_nomaskpass==1 && yax_isislandwall(wallnum, !yax_globalcf) && (yax_nomaskdidit=1))
            continue;
#endif

        //Offset&Rotate 3D coordinates to screen 3D space
        x = wal->x-globalposx; y = wal->y-globalposy;
        xp0 = (float)y*gcosang  - (float)x*gsinang;
        yp0 = (float)x*gcosang2 + (float)y*gsinang2;
        x = wal2->x-globalposx; y = wal2->y-globalposy;
        xp1 = (float)y*gcosang  - (float)x*gsinang;
        yp1 = (float)x*gcosang2 + (float)y*gsinang2;

        oxp0 = xp0; oyp0 = yp0;

        //Clip to close parallel-screen plane
        if (yp0 < SCISDIST)
        {
            if (yp1 < SCISDIST) continue;
            t0 = (SCISDIST-yp0)/(yp1-yp0); xp0 = (xp1-xp0)*t0+xp0; yp0 = SCISDIST;
            nx0 = (wal2->x-wal->x)*t0+wal->x;
            ny0 = (wal2->y-wal->y)*t0+wal->y;
        }
        else { t0 = 0.f; nx0 = (float)wal->x; ny0 = (float)wal->y; }
        if (yp1 < SCISDIST)
        {
            t1 = (SCISDIST-oyp0)/(yp1-oyp0); xp1 = (xp1-oxp0)*t1+oxp0; yp1 = SCISDIST;
            nx1 = (wal2->x-wal->x)*t1+wal->x;
            ny1 = (wal2->y-wal->y)*t1+wal->y;
        }
        else { t1 = 1.f; nx1 = (float)wal2->x; ny1 = (float)wal2->y; }

        ryp0 = 1.f/yp0; ryp1 = 1.f/yp1;

        //Generate screen coordinates for front side of wall
        x0 = ghalfx*xp0*ryp0 + ghalfx;
        x1 = ghalfx*xp1*ryp1 + ghalfx;
        if (x1 <= x0) continue;

        ryp0 *= gyxscale; ryp1 *= gyxscale;

        getzsofslope(sectnum,Blrintf(nx0),Blrintf(ny0),&cz,&fz);
        cy0 = ((float)(cz-globalposz))*ryp0 + ghoriz;
        fy0 = ((float)(fz-globalposz))*ryp0 + ghoriz;
        getzsofslope(sectnum,Blrintf(nx1),Blrintf(ny1),&cz,&fz);
        cy1 = ((float)(cz-globalposz))*ryp1 + ghoriz;
        fy1 = ((float)(fz-globalposz))*ryp1 + ghoriz;

        globalpicnum = sec->floorpicnum; globalshade = sec->floorshade; globalpal = (int32_t)((uint8_t)sec->floorpal);
        globalorientation = sec->floorstat;
        globvis = globalcisibility;
        if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

        DO_TILE_ANIM(globalpicnum, sectnum);

        dapskyoff = getpsky(globalpicnum, NULL, &dapskybits);

        global_cf_fogpal = sec->fogpal;
        global_cf_shade = sec->floorshade, global_cf_pal = sec->floorpal; global_cf_z = sec->floorz;  // REFACT
        global_cf_xpanning = sec->floorxpanning; global_cf_ypanning = sec->floorypanning, global_cf_heinum = sec->floorheinum;
        global_getzofslope_func = &getflorzofslope;

        if (!(globalorientation&1))
        {
#ifdef YAX_ENABLE
            if (globalposz <= sec->floorz || yax_getbunch(sectnum, YAX_FLOOR) < 0 || yax_getnextwall(wallnum, YAX_FLOOR) >= 0)
#endif
                polymost_internal_nonparallaxed(nx0, ny0, nx1, ny1, ryp0, ryp1, x0, x1, fy0, fy1, 1, sectnum);
        }
        else if ((nextsectnum < 0) || (!(sector[nextsectnum].floorstat&1)))
        {
            //Parallaxing sky... hacked for Ken's mountain texture; paper-sky only :/
#ifdef USE_OPENGL
            if (getrendermode() >= REND_POLYMOST)
            {
                if (!nofog) calc_and_apply_fog_factor(sec->floorpicnum, sec->floorshade, sec->visibility, sec->floorpal, 0.005f);

                //Use clamping for tiled sky textures
                for (i=(1<<dapskybits)-1; i>0; i--)
                    if (dapskyoff[i] != dapskyoff[i-1])
                        { skyclamphack = r_parallaxskyclamping; break; }
            }
#endif
            if (bpp == 8 || !usehightile || !hicfindskybox(globalpicnum, globalpal))
            {
//                g_nodraw = 1;

                dd[0] = fxdimen*.0000001f; //Adjust sky depth based on screen size!
                t = (float)((1<<(picsiz[globalpicnum]&15))<<dapskybits);
                vv[1] = dd[0]*((float)xdimscale*fviewingrange) * (1.f/(65536.f*65536.f));
                vv[0] = dd[0]*((float) ((tilesiz[globalpicnum].y>>1)/*+g_psky.yoffs*/)) - vv[1]*ghoriz;
                i = (1<<(picsiz[globalpicnum]>>4)); if (i != tilesiz[globalpicnum].y) i += i;

                //Hack to draw black rectangle below sky when looking down...
                gdx = 0; gdy = gxyaspect * (1.f/262144.f); gdo = -ghoriz*gdy;
                gux = 0; guy = 0; guo = 0;
                gvx = 0; gvy = (float)(tilesiz[globalpicnum].y-1)*gdy; gvo = (float)(tilesiz[globalpicnum].y-1)*gdo;
                oy = ((float)tilesiz[globalpicnum].y*dd[0]-vv[0])/vv[1];
                if ((oy > fy0) && (oy > fy1)) domost(x0,oy,x1,oy);
                else if ((oy > fy0) != (oy > fy1))
                {
                    //  fy0                      fy1
                    //     \                    /
                    //oy----------      oy----------
                    //        \              /
                    //         fy1        fy0
                    ox = (oy-fy0)*(x1-x0)/(fy1-fy0) + x0;
                    if (oy > fy0) { domost(x0,oy,ox,oy); domost(ox,oy,x1,fy1); }
                    else { domost(x0,fy0,ox,oy); domost(ox,oy,x1,oy); }
                }
                else domost(x0,fy0,x1,fy1);

                if (r_parallaxskypanning)
                    vv[0] += dd[0]*((float)sec->floorypanning)*((float)i)*(1.f/256.f);

                gdx = 0; gdy = 0; gdo = dd[0];
                gux = gdo *
                    (t * (float) ((uint64_t) (xdimscale * yxaspect) * viewingrange)) * (1.f/(16384.0*65536.0*65536.0*5.0*1024.0));
                guy = 0; //guo calculated later
                gvx = 0; gvy = vv[1]; gvo = vv[0];

                i = globalpicnum; r = (fy1-fy0)/(x1-x0); //slope of line
                oy = fviewingrange/(ghalfx*256.f); oz = 1.f/oy;

                y = ((((int32_t)((x0-ghalfx)*oy))+globalang)>>(11-dapskybits));
                fx = x0;
                do
                {
                    globalpicnum = dapskyoff[y&((1<<dapskybits)-1)]+i;
                    guo = gdo*(t*((float)(globalang-(y<<(11-dapskybits)))) * (1.f/2048.f) + (float)((r_parallaxskypanning)?sec->floorxpanning:0)) - gux*ghalfx;
                    y++;
                    ox = fx; fx = ((float)((y<<(11-dapskybits))-globalang))*oz+ghalfx;
                    if (fx > x1) { fx = x1; i = -1; }

                    pow2xsplit = 0; domost(ox,(ox-x0)*r+fy0,fx,(fx-x0)*r+fy0); //flor
                }
                while (i >= 0);

//                g_nodraw = 0;
            }
            else  //NOTE: code copied from ceiling code... lots of duplicated stuff :/
            {
                //Skybox code for parallax ceiling!
                float _xp0, _yp0, _xp1, _yp1, _oxp0, _oyp0, _t0, _t1; // _nx0, _ny0, _nx1, _ny1;
                float _ryp0, _ryp1, _x0, _x1, _cy0, _fy0, _cy1, _fy1, _ox0, _ox1;
                float nfy0, nfy1;
                int32_t skywalx[4] = {-512,512,512,-512}, skywaly[4] = {-512,-512,512,512};

                pow2xsplit = 0;
                skyclamphack = 1;

                for (i=0; i<4; i++)
                {
                    x = skywalx[i&3]; y = skywaly[i&3];
                    _xp0 = (float)y*gcosang  - (float)x*gsinang;
                    _yp0 = (float)x*gcosang2 + (float)y*gsinang2;
                    x = skywalx[(i+1)&3]; y = skywaly[(i+1)&3];
                    _xp1 = (float)y*gcosang  - (float)x*gsinang;
                    _yp1 = (float)x*gcosang2 + (float)y*gsinang2;

                    _oxp0 = _xp0; _oyp0 = _yp0;

                    //Clip to close parallel-screen plane
                    if (_yp0 < SCISDIST)
                    {
                        if (_yp1 < SCISDIST) continue;
                        _t0 = (SCISDIST-_yp0)/(_yp1-_yp0); _xp0 = (_xp1-_xp0)*_t0+_xp0; _yp0 = SCISDIST;
//                        _nx0 = (skywalx[(i+1)&3]-skywalx[i&3])*_t0+skywalx[i&3];
//                        _ny0 = (skywaly[(i+1)&3]-skywaly[i&3])*_t0+skywaly[i&3];
                    }
                    else { _t0 = 0.f; /*_nx0 = skywalx[i&3]; _ny0 = skywaly[i&3];*/ }
                    if (_yp1 < SCISDIST)
                    {
                        _t1 = (SCISDIST-_oyp0)/(_yp1-_oyp0); _xp1 = (_xp1-_oxp0)*_t1+_oxp0; _yp1 = SCISDIST;
//                        _nx1 = (skywalx[(i+1)&3]-skywalx[i&3])*_t1+skywalx[i&3];
//                        _ny1 = (skywaly[(i+1)&3]-skywaly[i&3])*_t1+skywaly[i&3];
                    }
                    else { _t1 = 1.f; /*_nx1 = skywalx[(i+1)&3]; _ny1 = skywaly[(i+1)&3];*/ }

                    _ryp0 = 1.f/_yp0; _ryp1 = 1.f/_yp1;

                    //Generate screen coordinates for front side of wall
                    _x0 = ghalfx*_xp0*_ryp0 + ghalfx;
                    _x1 = ghalfx*_xp1*_ryp1 + ghalfx;
                    if (_x1 <= _x0) continue;
                    if ((_x0 >= x1) || (x0 >= _x1)) continue;

                    _ryp0 *= gyxscale; _ryp1 *= gyxscale;

                    _cy0 = -8192.f*_ryp0 + ghoriz;
                    _fy0 =  8192.f*_ryp0 + ghoriz;
                    _cy1 = -8192.f*_ryp1 + ghoriz;
                    _fy1 =  8192.f*_ryp1 + ghoriz;

                    _ox0 = _x0; _ox1 = _x1;

                    //Make sure: x0<=_x0<_x1<=_x1
                    nfy0 = fy0; nfy1 = fy1;
                    if (_x0 < x0)
                    {
                        t = (x0-_x0)/(_x1-_x0);
                        _cy0 += (_cy1-_cy0)*t;
                        _fy0 += (_fy1-_fy0)*t;
                        _x0 = x0;
                    }
                    else if (_x0 > x0) nfy0 += (_x0-x0)*(fy1-fy0)/(x1-x0);
                    if (_x1 > x1)
                    {
                        t = (x1-_x1)/(_x1-_x0);
                        _cy1 += (_cy1-_cy0)*t;
                        _fy1 += (_fy1-_fy0)*t;
                        _x1 = x1;
                    }
                    else if (_x1 < x1) nfy1 += (_x1-x1)*(fy1-fy0)/(x1-x0);

                    //   (skybox floor)
                    //(_x0,_fy0)-(_x1,_fy1)
                    //   (skybox wall)
                    //(_x0,_cy0)-(_x1,_cy1)
                    //   (skybox ceiling)
                    //(_x0,nfy0)-(_x1,nfy1)

                    //ceiling of skybox
                    ft[0] = 512/16; ft[1] = 512/-16;
                    ft[2] = fcosglobalang*(1.f/2147483648.f);
                    ft[3] = fsinglobalang*(1.f/2147483648.f);
                    gdx = 0;
                    gdy = gxyaspect*(1.f/4194304.f);
                    gdo = -ghoriz*gdy;
                    gux = (double)ft[3]*fviewingrange*(-1.0/65536.0);
                    gvx = (double)ft[2]*fviewingrange*(-1.0/65536.0);
                    guy = ft[0]*gdy; gvy = ft[1]*gdy;
                    guo = ft[0]*gdo; gvo = ft[1]*gdo;
                    guo += (ft[2]-gux)*ghalfx;
                    gvo -= (ft[3]+gvx)*ghalfx;
                    gvx = -gvx; gvy = -gvy; gvo = -gvo; //y-flip skybox floor
#ifdef USE_OPENGL
                    drawingskybox = 6; //ceiling/5th texture/index 4 of skybox
#endif
                    if ((_fy0 > nfy0) && (_fy1 > nfy1)) domost(_x0,_fy0,_x1,_fy1);
                    else if ((_fy0 > nfy0) != (_fy1 > nfy1))
                    {
                        //(ox,oy) is intersection of: (_x0,_cy0)-(_x1,_cy1)
                        //                            (_x0,nfy0)-(_x1,nfy1)
                        //ox = _x0 + (_x1-_x0)*t
                        //oy = _cy0 + (_cy1-_cy0)*t
                        //oy = nfy0 + (nfy1-nfy0)*t
                        t = (_fy0-nfy0)/(nfy1-nfy0-_fy1+_fy0);
                        ox = _x0 + (_x1-_x0)*t;
                        oy = _fy0 + (_fy1-_fy0)*t;
                        if (nfy0 > _fy0) { domost(_x0,nfy0,ox,oy); domost(ox,oy,_x1,_fy1); }
                        else { domost(_x0,_fy0,ox,oy); domost(ox,oy,_x1,nfy1); }
                    }
                    else domost(_x0,nfy0,_x1,nfy1);

                    //wall of skybox
#ifdef USE_OPENGL
                    drawingskybox = i+1; //i+1th texture/index i of skybox
#endif
                    gdx = (_ryp0-_ryp1)*gxyaspect*(1.f/512.f) / (_ox0-_ox1);
                    gdy = 0;
                    gdo = _ryp0*gxyaspect*(1.f/512.f) - gdx*_ox0;
                    gux = (_t0*_ryp0 - _t1*_ryp1)*gxyaspect*(64.f/512.f) / (_ox0-_ox1);
                    guo = _t0*_ryp0*gxyaspect*(64.f/512.f) - gux*_ox0;
                    guy = 0;
                    _t0 = -8192.f*_ryp0 + ghoriz;
                    _t1 = -8192.f*_ryp1 + ghoriz;
                    t = ((gdx*_ox0 + gdo)*8.f) / ((_ox1-_ox0) * _ryp0 * 2048.f);
                    gvx = (_t0-_t1)*t;
                    gvy = (_ox1-_ox0)*t;
                    gvo = -gvx*_ox0 - gvy*_t0;
                    if ((_cy0 > nfy0) && (_cy1 > nfy1)) domost(_x0,_cy0,_x1,_cy1);
                    else if ((_cy0 > nfy0) != (_cy1 > nfy1))
                    {
                        //(ox,oy) is intersection of: (_x0,_fy0)-(_x1,_fy1)
                        //                            (_x0,nfy0)-(_x1,nfy1)
                        //ox = _x0 + (_x1-_x0)*t
                        //oy = _fy0 + (_fy1-_fy0)*t
                        //oy = nfy0 + (nfy1-nfy0)*t
                        t = (_cy0-nfy0)/(nfy1-nfy0-_cy1+_cy0);
                        ox = _x0 + (_x1-_x0)*t;
                        oy = _cy0 + (_cy1-_cy0)*t;
                        if (nfy0 > _cy0) { domost(_x0,nfy0,ox,oy); domost(ox,oy,_x1,_cy1); }
                        else { domost(_x0,_cy0,ox,oy); domost(ox,oy,_x1,nfy1); }
                    }
                    else domost(_x0,nfy0,_x1,nfy1);
                }

                //Floor of skybox
#ifdef USE_OPENGL
                drawingskybox = 5; //floor/6th texture/index 5 of skybox
#endif
                ft[0] = 512/16; ft[1] = -512/-16;
                ft[2] = fcosglobalang*(1.f/2147483648.f);
                ft[3] = fsinglobalang*(1.f/2147483648.f);
                gdx = 0;
                gdy = gxyaspect*(-1.f/4194304.f);
                gdo = -ghoriz*gdy;
                gux = ft[3]*((double)viewingrange)*(-1.0/65536.0);
                gvx = ft[2]*((double)viewingrange)*(-1.0/65536.0);
                guy = ft[0]*gdy; gvy = ft[1]*gdy;
                guo = ft[0]*gdo; gvo = ft[1]*gdo;
                guo += (ft[2]-gux)*ghalfx;
                gvo -= (ft[3]+gvx)*ghalfx;
                domost(x0,fy0,x1,fy1);

                skyclamphack = 0;
#ifdef USE_OPENGL
                drawingskybox = 0;
#endif
            }
#ifdef USE_OPENGL
            if (getrendermode() >= REND_POLYMOST)
            {
                skyclamphack = 0;
                if (!nofog)
                    bglEnable(GL_FOG);
            }
#endif
        }

        globalpicnum = sec->ceilingpicnum; globalshade = sec->ceilingshade; globalpal = (int32_t)((uint8_t)sec->ceilingpal);
        globalorientation = sec->ceilingstat;
        globvis = globalcisibility;
        if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

        DO_TILE_ANIM(globalpicnum, sectnum);

        dapskyoff = getpsky(globalpicnum, NULL, &dapskybits);

        global_cf_fogpal = sec->fogpal;
        global_cf_shade = sec->ceilingshade, global_cf_pal = sec->ceilingpal; global_cf_z = sec->ceilingz;  // REFACT
        global_cf_xpanning = sec->ceilingxpanning; global_cf_ypanning = sec->ceilingypanning, global_cf_heinum = sec->ceilingheinum;
        global_getzofslope_func = &getceilzofslope;

        if (!(globalorientation&1))
        {
#ifdef YAX_ENABLE
            if (globalposz >= sec->ceilingz || yax_getbunch(sectnum, YAX_CEILING) < 0 || yax_getnextwall(wallnum, YAX_CEILING) >= 0)
#endif
                polymost_internal_nonparallaxed(nx0, ny0, nx1, ny1, ryp0, ryp1, x0, x1, cy0, cy1, 0, sectnum);
        }
        else if ((nextsectnum < 0) || (!(sector[nextsectnum].ceilingstat&1)))
        {
#ifdef USE_OPENGL
            if (getrendermode() >= REND_POLYMOST)
            {
                if (!nofog) calc_and_apply_fog_factor(sec->ceilingpicnum, sec->ceilingshade, sec->visibility, sec->ceilingpal, 0.005f);

                //Use clamping for tiled sky textures
                for (i=(1<<dapskybits)-1; i>0; i--)
                    if (dapskyoff[i] != dapskyoff[i-1])
                        { skyclamphack = r_parallaxskyclamping; break; }
            }
#endif
            //Parallaxing sky...
            if (bpp == 8 || !usehightile || !hicfindskybox(globalpicnum, globalpal))
            {
//                g_nodraw = 1;

                //Render for parallaxtype == 0 / paper-sky
                dd[0] = fxdimen*.0000001f; //Adjust sky depth based on screen size!
                t = (float)((1<<(picsiz[globalpicnum]&15))<<dapskybits);
                vv[1] = dd[0]*((float)xdimscale*viewingrange) * (1.f/(65536.f*65536.f));
                vv[0] = dd[0]*((float)((tilesiz[globalpicnum].y>>1)/*+g_psky.yoffs*/)) - vv[1]*ghoriz;
                i = (1<<(picsiz[globalpicnum]>>4)); if (i != tilesiz[globalpicnum].y) i += i;

                //Hack to draw black rectangle below sky when looking down...
                gdx = 0; gdy = gxyaspect * (1.f/-262144.f); gdo = -ghoriz*gdy;
                gux = 0; guy = 0; guo = 0;
                gvx = 0; gvy = 0; gvo = 0;
                oy = -vv[0]/vv[1];

                if ((oy < cy0) && (oy < cy1)) domost(x1,oy,x0,oy);
                else if ((oy < cy0) != (oy < cy1))
                {
                    /*         cy1        cy0
                    //        /              \
                    //oy----------      oy---------
                    //    /                   \
                    //  cy0                     cy1
                    */

                    ox = (oy-cy0)*(x1-x0)/(cy1-cy0) + x0;
                    if (oy < cy0) { domost(ox,oy,x0,oy); domost(x1,cy1,ox,oy); }
                    else { domost(ox,oy,x0,cy0); domost(x1,oy,ox,oy); }
                }
                else domost(x1,cy1,x0,cy0);

                if (r_parallaxskypanning)
                    vv[0] += dd[0]*(float)sec->ceilingypanning*(float)i*(1.f/256.f);
               
                gdx = 0; gdy = 0; gdo = dd[0];
                gux = gdo *
                    (t * (float) ((uint64_t)(xdimscale * yxaspect) * viewingrange)) * (1.f/(16384.0*65536.0*65536.0*5.0*1024.0));
                guy = 0; //guo calculated later
                gvx = 0; gvy = vv[1]; gvo = vv[0];

                i = globalpicnum; r = (cy1-cy0)/(x1-x0); //slope of line
                oy = fviewingrange/(ghalfx*256.f); oz = 1.f/oy;

                y = ((((int32_t)((x0-ghalfx)*oy))+globalang)>>(11-dapskybits));
                fx = x0;
                do
                {
                    globalpicnum = dapskyoff[y&((1<<dapskybits)-1)]+i;
                    guo = gdo*(t*((float)(globalang-(y<<(11-dapskybits)))) * 1.f/2048.f + (float)((r_parallaxskypanning)?sec->ceilingxpanning:0)) - gux*ghalfx;
                    y++;
                    ox = fx; fx = ((float)((y<<(11-dapskybits))-globalang))*oz+ghalfx;
                    if (fx > x1) { fx = x1; i = -1; }
                    pow2xsplit = 0; domost(fx,(fx-x0)*r+cy0,ox,(ox-x0)*r+cy0); //ceil
                }
                while (i >= 0);

//                g_nodraw = 0;
            }
            else
            {
                //Skybox code for parallax ceiling!
                float _xp0, _yp0, _xp1, _yp1, _oxp0, _oyp0, _t0, _t1; // _nx0, _ny0, _nx1, _ny1;
                float _ryp0, _ryp1, _x0, _x1, _cy0, _fy0, _cy1, _fy1, _ox0, _ox1;
                float ncy0, ncy1;
                int32_t skywalx[4] = {-512,512,512,-512}, skywaly[4] = {-512,-512,512,512};

                pow2xsplit = 0;
                skyclamphack = 1;

                for (i=0; i<4; i++)
                {
                    x = skywalx[i&3]; y = skywaly[i&3];
                    _xp0 = (float)y*gcosang  - (float)x*gsinang;
                    _yp0 = (float)x*gcosang2 + (float)y*gsinang2;
                    x = skywalx[(i+1)&3]; y = skywaly[(i+1)&3];
                    _xp1 = (float)y*gcosang  - (float)x*gsinang;
                    _yp1 = (float)x*gcosang2 + (float)y*gsinang2;

                    _oxp0 = _xp0; _oyp0 = _yp0;

                    //Clip to close parallel-screen plane
                    if (_yp0 < SCISDIST)
                    {
                        if (_yp1 < SCISDIST) continue;
                        _t0 = (SCISDIST-_yp0)/(_yp1-_yp0); _xp0 = (_xp1-_xp0)*_t0+_xp0; _yp0 = SCISDIST;
//                        _nx0 = (skywalx[(i+1)&3]-skywalx[i&3])*_t0+skywalx[i&3];
//                        _ny0 = (skywaly[(i+1)&3]-skywaly[i&3])*_t0+skywaly[i&3];
                    }
                    else { _t0 = 0.f; /*_nx0 = skywalx[i&3]; _ny0 = skywaly[i&3];*/ }
                    if (_yp1 < SCISDIST)
                    {
                        _t1 = (SCISDIST-_oyp0)/(_yp1-_oyp0); _xp1 = (_xp1-_oxp0)*_t1+_oxp0; _yp1 = SCISDIST;
//                        _nx1 = (skywalx[(i+1)&3]-skywalx[i&3])*_t1+skywalx[i&3];
//                        _ny1 = (skywaly[(i+1)&3]-skywaly[i&3])*_t1+skywaly[i&3];
                    }
                    else { _t1 = 1.f; /*_nx1 = skywalx[(i+1)&3]; _ny1 = skywaly[(i+1)&3];*/ }

                    _ryp0 = 1.f/_yp0; _ryp1 = 1.f/_yp1;

                    //Generate screen coordinates for front side of wall
                    _x0 = ghalfx*_xp0*_ryp0 + ghalfx;
                    _x1 = ghalfx*_xp1*_ryp1 + ghalfx;
                    if (_x1 <= _x0) continue;
                    if ((_x0 >= x1) || (x0 >= _x1)) continue;

                    _ryp0 *= gyxscale; _ryp1 *= gyxscale;

                    _cy0 = -8192.f*_ryp0 + ghoriz;
                    _fy0 =  8192.f*_ryp0 + ghoriz;
                    _cy1 = -8192.f*_ryp1 + ghoriz;
                    _fy1 =  8192.f*_ryp1 + ghoriz;

                    _ox0 = _x0; _ox1 = _x1;

                    //Make sure: x0<=_x0<_x1<=_x1
                    ncy0 = cy0; ncy1 = cy1;
                    if (_x0 < x0)
                    {
                        t = (x0-_x0)/(_x1-_x0);
                        _cy0 += (_cy1-_cy0)*t;
                        _fy0 += (_fy1-_fy0)*t;
                        _x0 = x0;
                    }
                    else if (_x0 > x0) ncy0 += (_x0-x0)*(cy1-cy0)/(x1-x0);
                    if (_x1 > x1)
                    {
                        t = (x1-_x1)/(_x1-_x0);
                        _cy1 += (_cy1-_cy0)*t;
                        _fy1 += (_fy1-_fy0)*t;
                        _x1 = x1;
                    }
                    else if (_x1 < x1) ncy1 += (_x1-x1)*(cy1-cy0)/(x1-x0);

                    //   (skybox ceiling)
                    //(_x0,_cy0)-(_x1,_cy1)
                    //   (skybox wall)
                    //(_x0,_fy0)-(_x1,_fy1)
                    //   (skybox floor)
                    //(_x0,ncy0)-(_x1,ncy1)

                    //ceiling of skybox
#ifdef USE_OPENGL
                    drawingskybox = 5; //ceiling/5th texture/index 4 of skybox
#endif
                    ft[0] = 512/16; ft[1] = -512/-16;
                    ft[2] = fcosglobalang*(1.f/2147483648.f);
                    ft[3] = fsinglobalang*(1.f/2147483648.f);
                    gdx = 0;
                    gdy = gxyaspect*(-1.f/4194304.f);
                    gdo = -ghoriz*gdy;
                    gux = ft[3]*fviewingrange*(-1.0/65536.0);
                    gvx = ft[2]*fviewingrange*(-1.0/65536.0);
                    guy = ft[0]*gdy; gvy = ft[1]*gdy;
                    guo = ft[0]*gdo; gvo = ft[1]*gdo;
                    guo += (ft[2]-gux)*ghalfx;
                    gvo -= (ft[3]+gvx)*ghalfx;
                    if ((_cy0 < ncy0) && (_cy1 < ncy1)) domost(_x1,_cy1,_x0,_cy0);
                    else if ((_cy0 < ncy0) != (_cy1 < ncy1))
                    {
                        //(ox,oy) is intersection of: (_x0,_cy0)-(_x1,_cy1)
                        //                            (_x0,ncy0)-(_x1,ncy1)
                        //ox = _x0 + (_x1-_x0)*t
                        //oy = _cy0 + (_cy1-_cy0)*t
                        //oy = ncy0 + (ncy1-ncy0)*t
                        t = (_cy0-ncy0)/(ncy1-ncy0-_cy1+_cy0);
                        ox = _x0 + (_x1-_x0)*t;
                        oy = _cy0 + (_cy1-_cy0)*t;
                        if (ncy0 < _cy0) { domost(ox,oy,_x0,ncy0); domost(_x1,_cy1,ox,oy); }
                        else { domost(ox,oy,_x0,_cy0); domost(_x1,ncy1,ox,oy); }
                    }
                    else domost(_x1,ncy1,_x0,ncy0);

                    //wall of skybox
#ifdef USE_OPENGL
                    drawingskybox = i+1; //i+1th texture/index i of skybox
#endif
                    gdx = (_ryp0-_ryp1)*gxyaspect*(1.f/512.f) / (_ox0-_ox1);
                    gdy = 0;
                    gdo = _ryp0*gxyaspect*(1.f/512.f) - gdx*_ox0;
                    gux = (_t0*_ryp0 - _t1*_ryp1)*gxyaspect*(64.f/512.f) / (_ox0-_ox1);
                    guo = _t0*_ryp0*gxyaspect*(64.f/512.f) - gux*_ox0;
                    guy = 0;
                    _t0 = -8192.f*_ryp0 + ghoriz;
                    _t1 = -8192.f*_ryp1 + ghoriz;
                    t = ((gdx*_ox0 + gdo)*8.f) / ((_ox1-_ox0) * _ryp0 * 2048.f);
                    gvx = (_t0-_t1)*t;
                    gvy = (_ox1-_ox0)*t;
                    gvo = -gvx*_ox0 - gvy*_t0;
                    if ((_fy0 < ncy0) && (_fy1 < ncy1)) domost(_x1,_fy1,_x0,_fy0);
                    else if ((_fy0 < ncy0) != (_fy1 < ncy1))
                    {
                        //(ox,oy) is intersection of: (_x0,_fy0)-(_x1,_fy1)
                        //                            (_x0,ncy0)-(_x1,ncy1)
                        //ox = _x0 + (_x1-_x0)*t
                        //oy = _fy0 + (_fy1-_fy0)*t
                        //oy = ncy0 + (ncy1-ncy0)*t
                        t = (_fy0-ncy0)/(ncy1-ncy0-_fy1+_fy0);
                        ox = _x0 + (_x1-_x0)*t;
                        oy = _fy0 + (_fy1-_fy0)*t;
                        if (ncy0 < _fy0) { domost(ox,oy,_x0,ncy0); domost(_x1,_fy1,ox,oy); }
                        else { domost(ox,oy,_x0,_fy0); domost(_x1,ncy1,ox,oy); }
                    }
                    else domost(_x1,ncy1,_x0,ncy0);
                }

                //Floor of skybox
#ifdef USE_OPENGL
                drawingskybox = 6; //floor/6th texture/index 5 of skybox
#endif
                ft[0] = 512/16; ft[1] = 512/-16;
                ft[2] = fcosglobalang*(1.f/2147483648.f);
                ft[3] = fsinglobalang*(1.f/2147483648.f);
                gdx = 0;
                gdy = gxyaspect*(1.f/4194304.f);
                gdo = -ghoriz*gdy;
                gux = ft[3]*fviewingrange*(-1.0/65536.0);
                gvx = ft[2]*fviewingrange*(-1.0/65536.0);
                guy = ft[0]*gdy; gvy = ft[1]*gdy;
                guo = ft[0]*gdo; gvo = ft[1]*gdo;
                guo += (ft[2]-gux)*ghalfx;
                gvo -= (ft[3]+gvx)*ghalfx;
                gvx = -gvx; gvy = -gvy; gvo = -gvo; //y-flip skybox floor
                domost(x1,cy1,x0,cy0);

                skyclamphack = 0;
#ifdef USE_OPENGL
                drawingskybox = 0;
#endif
            }
#ifdef USE_OPENGL
            if (getrendermode() >= REND_POLYMOST)
            {
                skyclamphack = 0;
                if (!nofog)
                    bglEnable(GL_FOG);
            }
#endif
        }

        //(x0,cy0) == (u=             0,v=0,d=)
        //(x1,cy0) == (u=wal->xrepeat*8,v=0)
        //(x0,fy0) == (u=             0,v=v)
        //             u = (gux*sx + guy*sy + guo) / (gdx*sx + gdy*sy + gdo)
        //             v = (gvx*sx + gvy*sy + gvo) / (gdx*sx + gdy*sy + gdo)
        //             0 = (gux*x0 + guy*cy0 + guo) / (gdx*x0 + gdy*cy0 + gdo)
        //wal->xrepeat*8 = (gux*x1 + guy*cy0 + guo) / (gdx*x1 + gdy*cy0 + gdo)
        //             0 = (gvx*x0 + gvy*cy0 + gvo) / (gdx*x0 + gdy*cy0 + gdo)
        //             v = (gvx*x0 + gvy*fy0 + gvo) / (gdx*x0 + gdy*fy0 + gdo)
        //sx = x0, u = t0*wal->xrepeat*8, d = yp0;
        //sx = x1, u = t1*wal->xrepeat*8, d = yp1;
        //d = gdx*sx + gdo
        //u = (gux*sx + guo) / (gdx*sx + gdo)
        //yp0 = gdx*x0 + gdo
        //yp1 = gdx*x1 + gdo
        //t0*wal->xrepeat*8 = (gux*x0 + guo) / (gdx*x0 + gdo)
        //t1*wal->xrepeat*8 = (gux*x1 + guo) / (gdx*x1 + gdo)
        //gdx*x0 + gdo = yp0
        //gdx*x1 + gdo = yp1
        gdx = (ryp0-ryp1)*gxyaspect / (x0-x1);
        gdy = 0;
        gdo = ryp0*gxyaspect - gdx*x0;

        //gux*x0 + guo = t0*wal->xrepeat*8*yp0
        //gux*x1 + guo = t1*wal->xrepeat*8*yp1
        gux = (t0*ryp0 - t1*ryp1)*gxyaspect*(float)wal->xrepeat*8.f / (x0-x1);
        guo = t0*ryp0*gxyaspect*(float)wal->xrepeat*8.f - gux*x0;
        guo += (float)wal->xpanning*gdo;
        gux += (float)wal->xpanning*gdx;
        guy = 0;
        //Derivation for u:
        //   (gvx*x0 + gvy*cy0 + gvo) / (gdx*x0 + gdy*cy0 + gdo) = 0
        //   (gvx*x1 + gvy*cy1 + gvo) / (gdx*x1 + gdy*cy1 + gdo) = 0
        //   (gvx*x0 + gvy*fy0 + gvo) / (gdx*x0 + gdy*fy0 + gdo) = v
        //   (gvx*x1 + gvy*fy1 + gvo) / (gdx*x1 + gdy*fy1 + gdo) = v
        //   (gvx*x0 + gvy*cy0 + gvo*1) = 0
        //   (gvx*x1 + gvy*cy1 + gvo*1) = 0
        //   (gvx*x0 + gvy*fy0 + gvo*1) = t
        ogux = gux; oguy = guy; oguo = guo;

        Bassert(domostpolymethod == 0);
        domostpolymethod = DAMETH_WALL;

        if (nextsectnum >= 0)
        {
            getzsofslope(nextsectnum,Blrintf(nx0),Blrintf(ny0),&cz,&fz);
            ocy0 = ((float)(cz-globalposz))*ryp0 + ghoriz;
            ofy0 = ((float)(fz-globalposz))*ryp0 + ghoriz;
            getzsofslope(nextsectnum,Blrintf(nx1),Blrintf(ny1),&cz,&fz);
            ocy1 = ((float)(cz-globalposz))*ryp1 + ghoriz;
            ofy1 = ((float)(fz-globalposz))*ryp1 + ghoriz;

            if ((wal->cstat&48) == 16) maskwall[maskwallcnt++] = z;

            if (((cy0 < ocy0) || (cy1 < ocy1)) && (!((sec->ceilingstat&sector[nextsectnum].ceilingstat)&1)))
            {
                globalpicnum = wal->picnum; globalshade = wal->shade; globalpal = (int32_t)((uint8_t)wal->pal);
                globvis = globalvisibility;
                if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

                DO_TILE_ANIM(globalpicnum, wallnum+16384);

                if (!(wal->cstat&4)) i = sector[nextsectnum].ceilingz; else i = sec->ceilingz;

                // over
                calc_ypanning(i, ryp0, ryp1, x0, x1, wal->ypanning, wal->yrepeat, wal->cstat&4);

                if (wal->cstat&8) //xflip
                {
                    t = (float)(wal->xrepeat*8 + wal->xpanning*2);
                    gux = gdx*t - gux;
                    guy = gdy*t - guy;
                    guo = gdo*t - guo;
                }
                if (wal->cstat&256) { gvx = -gvx; gvy = -gvy; gvo = -gvo; } //yflip

                if (!nofog) calc_and_apply_fog(wal->picnum, fogpal_shade(sec, wal->shade), sec->visibility, get_floor_fogpal(sec));

                pow2xsplit = 1; domost(x1,ocy1,x0,ocy0);
                if (wal->cstat&8) { gux = ogux; guy = oguy; guo = oguo; }
            }
            if (((ofy0 < fy0) || (ofy1 < fy1)) && (!((sec->floorstat&sector[nextsectnum].floorstat)&1)))
            {
                if (!(wal->cstat&2)) nwal = wal;
                else
                {
                    nwal = &wall[wal->nextwall];
                    guo += (float)(nwal->xpanning-wal->xpanning)*gdo;
                    gux += (float)(nwal->xpanning-wal->xpanning)*gdx;
                    guy += (float)(nwal->xpanning-wal->xpanning)*gdy;
                }
                globalpicnum = nwal->picnum; globalshade = nwal->shade; globalpal = (int32_t)((uint8_t)nwal->pal);
                globvis = globalvisibility;
                if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

                DO_TILE_ANIM(globalpicnum, wallnum+16384);

                if (!(nwal->cstat&4)) i = sector[nextsectnum].floorz; else i = sec->ceilingz;

                // under
                calc_ypanning(i, ryp0, ryp1, x0, x1, nwal->ypanning, wal->yrepeat, !(nwal->cstat&4));

                if (wal->cstat&8) //xflip
                {
                    t = (float)(wal->xrepeat*8 + nwal->xpanning*2);
                    gux = gdx*t - gux;
                    guy = gdy*t - guy;
                    guo = gdo*t - guo;
                }
                if (nwal->cstat&256) { gvx = -gvx; gvy = -gvy; gvo = -gvo; } //yflip

                if (!nofog) calc_and_apply_fog(nwal->picnum, fogpal_shade(sec, nwal->shade), sec->visibility, get_floor_fogpal(sec));

                pow2xsplit = 1; domost(x0,ofy0,x1,ofy1);
                if (wal->cstat&(2+8)) { guo = oguo; gux = ogux; guy = oguy; }
            }
        }

        if ((nextsectnum < 0) || (wal->cstat&32))   //White/1-way wall
        {
            const int32_t maskingOneWay = (nextsectnum >= 0 && (wal->cstat&32));

            if (nextsectnum < 0) globalpicnum = wal->picnum; else globalpicnum = wal->overpicnum;
            globalshade = wal->shade; globalpal = (int32_t)((uint8_t)wal->pal);
            globvis = globalvisibility;
            if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

            DO_TILE_ANIM(globalpicnum, wallnum+16384);

            if (nextsectnum >= 0) { if (!(wal->cstat&4)) i = nextsec->ceilingz; else i = sec->ceilingz; }
            else { if (!(wal->cstat&4)) i = sec->ceilingz;     else i = sec->floorz; }

            // white / 1-way
            calc_ypanning(i, ryp0, ryp1, x0, x1, wal->ypanning, wal->yrepeat, !maskingOneWay && !(wal->cstat&4));

            if (wal->cstat&8) //xflip
            {
                t = (float)(wal->xrepeat*8 + wal->xpanning*2);
                gux = gdx*t - gux;
                guy = gdy*t - guy;
                guo = gdo*t - guo;
            }
            if (wal->cstat&256) { gvx = -gvx; gvy = -gvy; gvo = -gvo; } //yflip

            if (!nofog) calc_and_apply_fog(wal->picnum, fogpal_shade(sec, wal->shade), sec->visibility, get_floor_fogpal(sec));

            pow2xsplit = 1; domost(x0,-10000,x1,-10000);
        }

        domostpolymethod = 0;

        if (nextsectnum >= 0)
            if ((!(gotsector[nextsectnum>>3]&pow2char[nextsectnum&7])) && (testvisiblemost(x0,x1)))
                polymost_scansector(nextsectnum);
    }
}

static int32_t polymost_bunchfront(const int32_t b1, const int32_t b2)
{
    const int32_t b1f = bunchfirst[b1];
    const float x2b2 = dxb2[bunchlast[b2]];
    const float x1b1 = dxb1[b1f];

    if (x1b1 >= x2b2)
        return -1;

    {
        const int32_t b2f = bunchfirst[b2];
        int32_t i;
        const float x1b2 = dxb1[b2f];

        if (x1b2 >= dxb2[bunchlast[b1]])
            return -1;

        if (x1b1 >= x1b2)
        {
            for (i=b2f; dxb2[i]<=x1b1; i=bunchp2[i]);
            return wallfront(b1f, i);
        }

        for (i=b1f; dxb2[i]<=x1b2; i=bunchp2[i]);
        return wallfront(i, b2f);
    }
}

void polymost_scansector(int32_t sectnum)
{
    walltype *wal, *wal2;
    spritetype *spr;
    int32_t z, zz, startwall, endwall, numscansbefore, scanfirst, bunchfrst, nextsectnum, sectorbordercnt;
    vec2f_t p1, p2, fp1, fp2;
    float d;

    if (sectnum < 0) return;

    sectorborder[0] = sectnum, sectorbordercnt = 1;
    do
    {
        sectnum = sectorborder[--sectorbordercnt];

        for (z=headspritesect[sectnum]; z>=0; z=nextspritesect[z])
        {
            spr = &sprite[z];
            if ((((spr->cstat&0x8000) == 0) || (showinvisibility)) &&
                    (spr->xrepeat > 0) && (spr->yrepeat > 0))
            {
                vec2_t s = { spr->x-globalposx, spr->y-globalposy };

                if ((spr->cstat&48) || (usemodels && tile2model[spr->picnum].modelid>=0) || ((s.x * gcosang) + (s.y * gsinang) > 0))
                {
                    if ((spr->cstat&(64+48))!=(64+16) || dmulscale6(sintable[(spr->ang+512)&2047],-s.x, sintable[spr->ang&2047],-s.y) > 0)
                        if (engine_addtsprite(z, sectnum))
                            break;
                }
            }
        }

        gotsector[sectnum>>3] |= pow2char[sectnum&7];

        bunchfrst = numbunches;
        numscansbefore = numscans;

        startwall = sector[sectnum].wallptr; endwall = sector[sectnum].wallnum+startwall;
        scanfirst = numscans;
        p2.x = p2.y = 0;

        for (z=startwall,wal=&wall[z]; z<endwall; z++,wal++)
        {
            wal2 = &wall[wal->point2];
            fp1.x = (float)(wal->x-globalposx); fp1.y = (float)(wal->y-globalposy);
            fp2.x = (float)(wal2->x-globalposx); fp2.y = (float)(wal2->y-globalposy);

            nextsectnum = wal->nextsector; //Scan close sectors

            if (nextsectnum >= 0 && !(wal->cstat&32) && sectorbordercnt < ARRAY_SSIZE(sectorborder))
#ifdef YAX_ENABLE
            if (yax_nomaskpass==0 || !yax_isislandwall(z, !yax_globalcf) || (yax_nomaskdidit=1, 0))
#endif
            if ((gotsector[nextsectnum>>3]&pow2char[nextsectnum&7]) == 0)
            {
                d = fp1.x*fp2.y - fp2.x*fp1.y;
                p1.x = fp2.x-fp1.x;
                p1.y = fp2.y-fp1.y;

                if (d*d <= (p1.x*p1.x + p1.y*p1.y) * (SCISDIST*SCISDIST*260.f))
                {
                    sectorborder[sectorbordercnt++] = nextsectnum;
                    gotsector[nextsectnum>>3] |= pow2char[nextsectnum&7];
                }
            }

            if ((z == startwall) || (wall[z-1].point2 != z))
            {
                p1.x = ((fp1.y * fcosglobalang) - (fp1.x * fsinglobalang)) * (1.0f/64.f);
                p1.y = ((fp1.x * (float)cosviewingrangeglobalang) + (fp1.y * (float)sinviewingrangeglobalang)) * (1.0f/64.f);
            }
            else { p1 = p2; }

            p2.x = ((fp2.y * fcosglobalang) - (fp2.x * fsinglobalang)) * (1.0f/64.f);
            p2.y = ((fp2.x * (float) cosviewingrangeglobalang) + (fp2.y * (float) sinviewingrangeglobalang)) * (1.0f/64.f);

            if ((p1.y >= SCISDIST) || (p2.y >= SCISDIST))
                if (p1.x*p2.y < p2.x*p1.y) //if wall is facing you...
                {
                    if (p1.y >= SCISDIST)
                        dxb1[numscans] = p1.x*ghalfx/p1.y + ghalfx;
                    else dxb1[numscans] = -1e32f;

                    if (p2.y >= SCISDIST)
                        dxb2[numscans] = p2.x*ghalfx/p2.y + ghalfx;
                    else dxb2[numscans] = 1e32f;

                    if (dxb1[numscans] < dxb2[numscans])
                        { thesector[numscans] = sectnum; thewall[numscans] = z; bunchp2[numscans] = numscans+1; numscans++; }
                }

            if ((wall[z].point2 < z) && (scanfirst < numscans))
                { bunchp2[numscans-1] = scanfirst; scanfirst = numscans; }
        }

        for (z=numscansbefore; z<numscans; z++)
            if ((wall[thewall[z]].point2 != thewall[bunchp2[z]]) || (dxb2[z] > dxb1[bunchp2[z]]))
            {
                bunchfirst[numbunches++] = bunchp2[z]; bunchp2[z] = -1;
#ifdef YAX_ENABLE
                if (scansector_retfast)
                    return;
#endif
            }

        for (z=bunchfrst; z<numbunches; z++)
        {
            for (zz=bunchfirst[z]; bunchp2[zz]>=0; zz=bunchp2[zz]);
            bunchlast[z] = zz;
        }
    }
    while (sectorbordercnt > 0);
}

/*Init viewport boundary (must be 4 point convex loop):
//      (px[0],py[0]).----.(px[1],py[1])
//                  /      \
//                /          \
// (px[3],py[3]).--------------.(px[2],py[2])
*/


void initmosts(const float * px, const float * py, int32_t n)
{
    int32_t i, j, k, imin, vcnt;

    vcnt = 1; //0 is dummy solid node

    if (n < 3) return;
    imin = (px[1] < px[0]);
    for (i=n-1; i>=2; i--) if (px[i] < px[imin]) imin = i;


    vsp[vcnt].x = px[imin];
    vsp[vcnt].cy[0] = vsp[vcnt].fy[0] = py[imin];
    vcnt++;
    i = imin+1; if (i >= n) i = 0;
    j = imin-1; if (j < 0) j = n-1;
    do
    {
        if (px[i] < px[j])
        {
            if ((vcnt > 1) && (px[i] <= vsp[vcnt-1].x)) vcnt--;
            vsp[vcnt].x = px[i];
            vsp[vcnt].cy[0] = py[i];
            k = j+1; if (k >= n) k = 0;
            //(px[k],py[k])
            //(px[i],?)
            //(px[j],py[j])
            vsp[vcnt].fy[0] = (px[i]-px[k])*(py[j]-py[k])/(px[j]-px[k]) + py[k];
            vcnt++;
            i++; if (i >= n) i = 0;
        }
        else if (px[j] < px[i])
        {
            if ((vcnt > 1) && (px[j] <= vsp[vcnt-1].x)) vcnt--;
            vsp[vcnt].x = px[j];
            vsp[vcnt].fy[0] = py[j];
            k = i-1; if (k < 0) k = n-1;
            //(px[k],py[k])
            //(px[j],?)
            //(px[i],py[i])
            vsp[vcnt].cy[0] = (px[j]-px[k])*(py[i]-py[k])/(px[i]-px[k]) + py[k];
            vcnt++;
            j--; if (j < 0) j = n-1;
        }
        else
        {
            if ((vcnt > 1) && (px[i] <= vsp[vcnt-1].x)) vcnt--;
            vsp[vcnt].x = px[i];
            vsp[vcnt].cy[0] = py[i];
            vsp[vcnt].fy[0] = py[j];
            vcnt++;
            i++; if (i >= n) i = 0; if (i == j) break;
            j--; if (j < 0) j = n-1;
        }
    } while (i != j);

    if (px[i] > vsp[vcnt-1].x)
    {
        vsp[vcnt].x = px[i];
        vsp[vcnt].cy[0] = vsp[vcnt].fy[0] = py[i];
        vcnt++;
    }

    vsp_finalize_init(vsp, vcnt);
    gtag = vcnt;
}

void polymost_drawrooms()
{
    int32_t i, j, n, n2, closest;
    float ox, oy, oz, ox2, oy2, oz2, r, px[6], py[6], pz[6], px2[6], py2[6], pz2[6], sx[6], sy[6];

    if (getrendermode() == REND_CLASSIC) return;

    begindrawing();
    frameoffset = frameplace + windowy1*bytesperline + windowx1;

#ifdef USE_OPENGL
    if (getrendermode() >= REND_POLYMOST)
    {
        resizeglcheck();
#ifdef YAX_ENABLE
        if (numyaxbunches==0)
#endif
        if (editstatus)
            bglClear(GL_COLOR_BUFFER_BIT);

        bglClear(GL_DEPTH_BUFFER_BIT);

        bglDisable(GL_BLEND);
        bglEnable(GL_TEXTURE_2D);
        bglEnable(GL_DEPTH_TEST);
        bglDepthFunc(GL_LESS); //NEVER,LESS,(,L)EQUAL,GREATER,(NOT,G)EQUAL,ALWAYS
//        bglDepthRange(0.0, 1.0); //<- this is more widely supported than glPolygonOffset

        //Enable this for OpenGL red-blue glasses mode :)
#ifdef REDBLUEMODE
        if (glredbluemode)
        {
            static int32_t grbfcnt = 0; grbfcnt++;
            if (redblueclearcnt < numpages) { redblueclearcnt++; bglColorMask(1,1,1,1); bglClear(GL_COLOR_BUFFER_BIT); }
            if (grbfcnt&1)
            {
                bglViewport(windowx1-16,yres-(windowy2+1),windowx2-(windowx1-16)+1,windowy2-windowy1+1);
                bglColorMask(1,0,0,1);
                globalposx += singlobalang>>10;
                globalposy -= cosglobalang>>10;
            }
            else
            {
                bglViewport(windowx1,yres-(windowy2+1),windowx2+16-windowx1+1,windowy2-windowy1+1);
                bglColorMask(0,1,1,1);
                globalposx -= singlobalang>>10;
                globalposy += cosglobalang>>10;
            }
        }
#endif
    }
#endif
   
    //Polymost supports true look up/down :) Here, we convert horizon to angle.
    //gchang&gshang are cos&sin of this angle (respectively)
    fviewingrange = (float) viewingrange;
    gyxscale = ((float)xdimenscale)*(1.0f/131072.f);
    gxyaspect = ((float)xyaspect*fviewingrange)*(5.f/(65536.f*262144.f));
    gviewxrange = fviewingrange * fxdimen * (1.f/(32768.f*1024.f));
    fcosglobalang = (float) cosglobalang;
    gcosang = fcosglobalang*(1.0f/262144.f);
    fsinglobalang = (float) singlobalang;
    gsinang = fsinglobalang*(1.0f/262144.f);
    gcosang2 = gcosang * (fviewingrange * (1.0f/65536.f));
    gsinang2 = gsinang * (fviewingrange * (1.0f/65536.f));
    ghalfx = fxdimen * .5f;
    grhalfxdown10 = 1.f/(ghalfx*1024.f);
    ghoriz = (float)globalhoriz;

    gvisibility = ((float)globalvisibility)*FOGSCALE;

    //global cos/sin height angle
    r = fydimen * .5f - ghoriz;
    gshang = r/Bsqrtf(r*r+ghalfx*ghalfx);
    gchang = Bsqrtf(1.f-gshang*gshang);
    ghoriz = fydimen * .5f;

    //global cos/sin tilt angle
    gctang = cos(gtang);
    gstang = sin(gtang);
    if (Bfabsf(gstang) < .001f) //This hack avoids nasty precision bugs in domost()
        { gstang = 0.f; if (gctang > 0.f) gctang = 1.f; else gctang = -1.f; }

    if (inpreparemirror)
        gstang = -gstang;

    //Generate viewport trapezoid (for handling screen up/down)
    px[0] = px[3] = 0-1; px[1] = px[2] = (float)(windowx2+1-windowx1+2);
    py[0] = py[1] = 0-1; py[2] = py[3] = (float)(windowy2+1-windowy1+2);
   
    n = 4;

    for (i=0; i<n; i++)
    {
        ox = px[i]-ghalfx; oy = py[i]-ghoriz; oz = ghalfx;

        //Tilt rotation (backwards)
        ox2 = ox*gctang + oy*gstang;
        oy2 = oy*gctang - ox*gstang;
        oz2 = oz;

        //Up/down rotation (backwards)
        px[i] = ox2;
        py[i] = oy2*gchang + oz2*gshang;
        pz[i] = oz2*gchang - oy2*gshang;
    }

    //Clip to SCISDIST plane
    n2 = 0;
    for (i=0; i<n; i++)
    {
        j = i+1; if (j >= n) j = 0;
        if (pz[i] >= SCISDIST) { px2[n2] = px[i]; py2[n2] = py[i]; pz2[n2] = pz[i]; n2++; }
        if ((pz[i] >= SCISDIST) != (pz[j] >= SCISDIST))
        {
            r = (SCISDIST-pz[i])/(pz[j]-pz[i]);
            px2[n2] = (px[j]-px[i])*r + px[i];
            py2[n2] = (py[j]-py[i])*r + py[i];
            pz2[n2] = SCISDIST; n2++;
        }
    }
    if (n2 < 3) { enddrawing(); return; }
    for (i=0; i<n2; i++)
    {
        r = ghalfx / pz2[i];
        sx[i] = px2[i]*r + ghalfx;
        sy[i] = py2[i]*r + ghoriz;
    }
    initmosts(sx,sy,n2);

    if (searchit == 2)
        polymost_editorfunc();

    numscans = numbunches = 0;

    // MASKWALL_BAD_ACCESS
    // Fixes access of stale maskwall[maskwallcnt] (a "scan" index, in BUILD lingo):
    maskwallcnt = 0;

    polymost_scansector(globalcursectnum);

    if (inpreparemirror)
    {
        grhalfxdown10x = -grhalfxdown10;
        inpreparemirror = 0;

        // see engine.c: INPREPAREMIRROR_NO_BUNCHES
        if (numbunches > 0)
        {
            polymost_drawalls(0);
            numbunches--;
            bunchfirst[0] = bunchfirst[numbunches];
            bunchlast[0] = bunchlast[numbunches];
        }
    }
    else
        grhalfxdown10x = grhalfxdown10;

    while (numbunches > 0)
    {
        Bmemset(ptempbuf,0,numbunches+3); ptempbuf[0] = 1;

        closest = 0;              //Almost works, but not quite :(
        for (i=1; i<numbunches; i++)
        {
            j = polymost_bunchfront(i,closest); if (j < 0) continue;
            ptempbuf[i] = 1;
            if (!j) { ptempbuf[closest] = 1; closest = i; }
        }
        for (i=0; i<numbunches; i++) //Double-check
        {
            if (ptempbuf[i]) continue;
            j = polymost_bunchfront(i,closest); if (j < 0) continue;
            ptempbuf[i] = 1;
            if (!j) { ptempbuf[closest] = 1; closest = i; i = 0; }
        }

        polymost_drawalls(closest);

        numbunches--;
        bunchfirst[closest] = bunchfirst[numbunches];
        bunchlast[closest] = bunchlast[numbunches];
    }
#ifdef USE_OPENGL
    if (getrendermode() >= REND_POLYMOST)
    {
        bglDepthFunc(GL_LESS); //NEVER,LESS,(,L)EQUAL,GREATER,(NOT,G)EQUAL,ALWAYS
//        bglDepthRange(0.0, 1.0); //<- this is more widely supported than glPolygonOffset
    }
#endif

    enddrawing();
}

void polymost_drawmaskwall(int32_t damaskwallcnt)
{
    vec2f_t dpxy[8];
    float dpx2[8], dpy2[8];
    float x0, x1, sx0, sy0, sx1, sy1, xp0, yp0, xp1, yp1, oxp0, oyp0, ryp0, ryp1;
    float r, t, t0, t1, csy[4], fsy[4];
    int32_t i, j, n, n2, z, sectnum, z1, z2, cz[4], fz[4], method;
    int32_t m0, m1;
    sectortype *sec, *nsec;
    walltype *wal, *wal2;

    z = maskwall[damaskwallcnt];
    wal = &wall[thewall[z]]; wal2 = &wall[wal->point2];
    sectnum = thesector[z]; sec = &sector[sectnum];

//    if (wal->nextsector < 0) return;
    // Without MASKWALL_BAD_ACCESS fix:
    // wal->nextsector is -1, WGR2 SVN Lochwood Hollow (Til' Death L1)  (or trueror1.map)

    nsec = &sector[wal->nextsector];
    z1 = max(nsec->ceilingz,sec->ceilingz);
    z2 = min(nsec->floorz,sec->floorz);

    globalpicnum = wal->overpicnum; if ((uint32_t)globalpicnum >= MAXTILES) globalpicnum = 0;
    globvis = globalvisibility;
    if (sector[sectnum].visibility != 0) globvis = mulscale4(globvis, (uint8_t)(sector[sectnum].visibility+16));

    DO_TILE_ANIM(globalpicnum, (int16_t)thewall[z]+16384);
    globalshade = (int32_t)wal->shade;
    globalpal = (int32_t)((uint8_t)wal->pal);
    globalorientation = (int32_t)wal->cstat;

    sx0 = (float)(wal->x-globalposx); sx1 = (float)(wal2->x-globalposx);
    sy0 = (float)(wal->y-globalposy); sy1 = (float)(wal2->y-globalposy);
    yp0 = sx0*gcosang2 + sy0*gsinang2;
    yp1 = sx1*gcosang2 + sy1*gsinang2;
    if ((yp0 < SCISDIST) && (yp1 < SCISDIST)) return;
    xp0 = sy0*gcosang - sx0*gsinang;
    xp1 = sy1*gcosang - sx1*gsinang;

    //Clip to close parallel-screen plane
    oxp0 = xp0; oyp0 = yp0;
    if (yp0 < SCISDIST) { t0 = (SCISDIST-yp0)/(yp1-yp0); xp0 = (xp1-xp0)*t0+xp0; yp0 = SCISDIST; }
    else t0 = 0.f;
    if (yp1 < SCISDIST) { t1 = (SCISDIST-oyp0)/(yp1-oyp0); xp1 = (xp1-oxp0)*t1+oxp0; yp1 = SCISDIST; }
    else { t1 = 1.f; }

    m0 = (int32_t)((wal2->x-wal->x)*t0+wal->x);
    m1 = (int32_t)((wal2->y-wal->y)*t0+wal->y);
    getzsofslope(sectnum,m0,m1,&cz[0],&fz[0]);
    getzsofslope(wal->nextsector,m0,m1,&cz[1],&fz[1]);
    m0 = (int32_t)((wal2->x-wal->x)*t1+wal->x);
    m1 = (int32_t)((wal2->y-wal->y)*t1+wal->y);
    getzsofslope(sectnum,m0,m1,&cz[2],&fz[2]);
    getzsofslope(wal->nextsector,m0,m1,&cz[3],&fz[3]);

    ryp0 = 1.f/yp0; ryp1 = 1.f/yp1;

    //Generate screen coordinates for front side of wall
    x0 = ghalfx*xp0*ryp0 + ghalfx;
    x1 = ghalfx*xp1*ryp1 + ghalfx;
    if (x1 <= x0) return;

    ryp0 *= gyxscale; ryp1 *= gyxscale;

    gdx = (ryp0-ryp1)*gxyaspect / (x0-x1);
    gdy = 0;
    gdo = ryp0*gxyaspect - gdx*x0;

    //gux*x0 + guo = t0*wal->xrepeat*8*yp0
    //gux*x1 + guo = t1*wal->xrepeat*8*yp1
    gux = (t0*ryp0 - t1*ryp1)*gxyaspect*(float)wal->xrepeat*8.f / (x0-x1);
    guo = t0*ryp0*gxyaspect*(float)wal->xrepeat*8.f - gux*x0;
    guo += (float)wal->xpanning*gdo;
    gux += (float)wal->xpanning*gdx;
    guy = 0;

    if (!(wal->cstat&4)) i = z1; else i = z2;

    // mask
    calc_ypanning(i, ryp0, ryp1, x0, x1, wal->ypanning, wal->yrepeat, 0);

    if (wal->cstat&8) //xflip
    {
        t = (float)(wal->xrepeat*8 + wal->xpanning*2);
        gux = gdx*t - gux;
        guy = gdy*t - guy;
        guo = gdo*t - guo;
    }
    if (wal->cstat&256) { gvx = -gvx; gvy = -gvy; gvo = -gvo; } //yflip

    method = 1; pow2xsplit = 1;
    if (wal->cstat&128) { if (!(wal->cstat&512)) method = 2; else method = 3; }
    method |= DAMETH_WALL;

    if (!nofog) calc_and_apply_fog(wal->picnum, fogpal_shade(sec, wal->shade), sec->visibility, get_floor_fogpal(sec));

    for (i=0; i<2; i++)
    {
        csy[i] = ((float)(cz[i]-globalposz))*ryp0 + ghoriz;
        fsy[i] = ((float)(fz[i]-globalposz))*ryp0 + ghoriz;
        csy[i+2] = ((float)(cz[i+2]-globalposz))*ryp1 + ghoriz;
        fsy[i+2] = ((float)(fz[i+2]-globalposz))*ryp1 + ghoriz;
    }

    //Clip 2 quadrilaterals
    //               /csy3
    //             /   |
    // csy0------/----csy2
    //   |     /xxxxxxx|
    //   |   /xxxxxxxxx|
    // csy1/xxxxxxxxxxx|
    //   |xxxxxxxxxxx/fsy3
    //   |xxxxxxxxx/   |
    //   |xxxxxxx/     |
    // fsy0----/------fsy2
    //   |   /
    // fsy1/

    dpxy[0].x = x0; dpxy[0].y = csy[1];
    dpxy[1].x = x1; dpxy[1].y = csy[3];
    dpxy[2].x = x1; dpxy[2].y = fsy[3];
    dpxy[3].x = x0; dpxy[3].y = fsy[1];
    n = 4;

    //Clip to (x0,csy[0])-(x1,csy[2])
    n2 = 0; t1 = -((dpxy[0].x-x0)*(csy[2]-csy[0]) - (dpxy[0].y-csy[0])*(x1-x0));
    for (i=0; i<n; i++)
    {
        j = i+1; if (j >= n) j = 0;

        t0 = t1; t1 = -((dpxy[j].x-x0)*(csy[2]-csy[0]) - (dpxy[j].y-csy[0])*(x1-x0));
        if (t0 >= 0) { dpx2[n2] = dpxy[i].x; dpy2[n2] = dpxy[i].y; n2++; }
        if ((t0 >= 0) != (t1 >= 0))
        {
            r = t0/(t0-t1);
            dpx2[n2] = (dpxy[j].x-dpxy[i].x)*r + dpxy[i].x;
            dpy2[n2] = (dpxy[j].y-dpxy[i].y)*r + dpxy[i].y;
            n2++;
        }
    }
    if (n2 < 3) return;

    //Clip to (x1,fsy[2])-(x0,fsy[0])
    n = 0; t1 = -((dpx2[0]-x1)*(fsy[0]-fsy[2]) - (dpy2[0]-fsy[2])*(x0-x1));
    for (i=0; i<n2; i++)
    {
        j = i+1; if (j >= n2) j = 0;

        t0 = t1; t1 = -((dpx2[j]-x1)*(fsy[0]-fsy[2]) - (dpy2[j]-fsy[2])*(x0-x1));
        if (t0 >= 0) { dpxy[n].x = dpx2[i]; dpxy[n].y = dpy2[i]; n++; }
        if ((t0 >= 0) != (t1 >= 0))
        {
            r = t0/(t0-t1);
            dpxy[n].x = (dpx2[j]-dpx2[i])*r + dpx2[i];
            dpxy[n].y = (dpy2[j]-dpy2[i])*r + dpy2[i];
            n++;
        }
    }
    if (n < 3) return;

    pow2xsplit = 0;
    skyclamphack = 0;
    alpha = 0.f;
    drawpoly(dpxy, n, method);
}

#define POINT2(i) (wall[wall[i].point2])


static inline int polymost_getclosestpointonwall(vec2_t const * const pos, int32_t dawall, vec2_t * const n)
{
    vec2_t w = { wall[dawall].x, wall[dawall].y };
    vec2_t p2 = { POINT2(dawall).x, POINT2(dawall).y };
    vec2_t d = { p2.x - w.x, p2.y - w.y };
    int64_t i = d.x * (pos->x - w.x) + d.y * (pos->y - w.y);

    if (i < 0)
        return 1;

    int64_t j = d.x * d.x + d.y * d.y;

    if (i > j)
        return 1;

    i = tabledivide64((i << 15), j) << 15;

    n->x = w.x + ((d.x * i) >> 30);
    n->y = w.y + ((d.y * i) >> 30);

    return 0;
}

typedef struct
{
    uint32_t wrev;
    uint32_t srev;
    int16_t wall;
    int8_t wdist;
    int8_t filler;
} wallspriteinfo_t;

wallspriteinfo_t wsprinfo[MAXSPRITES];

static inline int32_t polymost_findwall(tspritetype const * const tspr, int32_t * rd)
{
    int32_t dist = 8, closest = -1, dst;
    sectortype const * const sect = &sector[tspr->sectnum];
    vec2_t n;

    for (int i=sect->wallptr; i<sect->wallptr + sect->wallnum; i++)
    {
        if (!polymost_getclosestpointonwall((const vec2_t *)tspr, i, &n))
        {
            dst = klabs(tspr->x - n.x) + klabs(tspr->y - n.y);

            if (dst <= dist)
            {
                dist = dst;
                closest = i;
            }
        }
    }

    *rd = dist;
    return closest;
}

int32_t polymost_lintersect(int32_t x1, int32_t y1, int32_t x2, int32_t y2,
                            int32_t x3, int32_t y3, int32_t x4, int32_t y4)
{
    // p1 to p2 is a line segment
    int32_t const x21 = x2 - x1, x34 = x3 - x4;
    int32_t const y21 = y2 - y1, y34 = y3 - y4;
    int32_t const bot = x21 * y34 - y21 * x34;
    int32_t const x31 = x3 - x1, y31 = y3 - y1;
    int32_t const topt = x31 * y34 - y31 * x34;

    if (bot