// an entire fps in a single small .cpp file...

#include <stdio.h>
#include <math.h>
#include <time.h>
#include <SDL.h>
#include <SDL_opengl.h>

#define RAD (3.1415927f/180.0f)

template <class T> struct vec
{
    T x, y, z;
    vec() {}
    vec(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {}
    vec(T d[3]) : x(d[0]), y(d[1]), z(d[2]) {}
    template<class U> vec(vec<U> d) : x((T)d.x), y((T)d.y), z((T)d.z) {}
    operator T *() { return &x; }
    vec operator+(const vec &o) { return vec(x+o.x, y+o.y, z+o.z); }
    vec operator-(const vec &o) { return vec(x-o.x, y-o.y, z-o.z); }
    vec operator*(T d) { return vec(x*d, y*d, z*d); }
    vec operator/(T d) { return vec(x/d, y/d, z/d); }
    T dot(const vec &o) { return x*o.x+y*o.y+z*o.z; }
    float len() { return sqrtf((float)dot(*this)); }
    vec normalize() { return *this/(T)len(); }
    //vec cross(vec &b) { return vec(y*b.z-z*b.y, z*b.x-x*b.z, x*b.y-y*b.x); }
};

typedef vec<float> vecf;
typedef vec<int> veci;

#define loop(n, m) for(int n = 0; n<(m); n++)
#define loopworld(b) loop(x, WIDTH) loop(y, WIDTH) loop(z, HEIGHT) { veci v(x, y, z); cube &c = getcube(v); b; }

int scrw = 800, scrh = 600;
int frametime = 0, lastmillis = 0, frames = 0, starttime = 0;

void leave(const char *why)
{
    printf("EXIT: %s, average frametime: %d\n", why, (SDL_GetTicks()-starttime)/frames);
    SDL_ShowCursor(1);
    SDL_Quit();
    exit(1);
}

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

// GL_ARB_vertex_program, GL_ARB_fragment_program
PFNGLGENPROGRAMSARBPROC            glGenPrograms_            = NULL;
PFNGLBINDPROGRAMARBPROC            glBindProgram_            = NULL;
PFNGLPROGRAMSTRINGARBPROC          glProgramString_          = NULL;
PFNGLPROGRAMENVPARAMETER4FARBPROC  glProgramEnvParameter4f_  = NULL;
PFNGLPROGRAMENVPARAMETER4FVARBPROC glProgramEnvParameter4fv_ = NULL;

// GL_EXT_framebuffer_object
PFNGLBINDRENDERBUFFEREXTPROC        glBindRenderbuffer_        = NULL;
PFNGLDELETERENDERBUFFERSEXTPROC     glDeleteRenderbuffers_     = NULL;
PFNGLGENFRAMEBUFFERSEXTPROC         glGenRenderbuffers_        = NULL;
PFNGLRENDERBUFFERSTORAGEEXTPROC     glRenderbufferStorage_     = NULL;
PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC  glCheckFramebufferStatus_  = NULL;
PFNGLBINDFRAMEBUFFEREXTPROC         glBindFramebuffer_         = NULL;
PFNGLDELETEFRAMEBUFFERSEXTPROC      glDeleteFramebuffers_      = NULL;
PFNGLGENFRAMEBUFFERSEXTPROC         glGenFramebuffers_         = NULL;
PFNGLFRAMEBUFFERTEXTURE2DEXTPROC    glFramebufferTexture2D_    = NULL;
PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbuffer_ = NULL;

void initgl()
{
    const char *vendor = (const char *)glGetString(GL_VENDOR);
    const char *exts = (const char *)glGetString(GL_EXTENSIONS);
    const char *renderer = (const char *)glGetString(GL_RENDERER);

    if(!strstr(exts, "GL_ARB_texture_float")) leave("no float textures!");

    if(!strstr(exts, "GL_ARB_multitexture")) leave("no multitexture!");
    glActiveTexture_       = (PFNGLACTIVETEXTUREARBPROC)      SDL_GL_GetProcAddress("glActiveTextureARB");
    glClientActiveTexture_ = (PFNGLCLIENTACTIVETEXTUREARBPROC)SDL_GL_GetProcAddress("glClientActiveTextureARB");
    glMultiTexCoord2f_     = (PFNGLMULTITEXCOORD2FARBPROC)    SDL_GL_GetProcAddress("glMultiTexCoord2fARB");
    glMultiTexCoord3f_     = (PFNGLMULTITEXCOORD3FARBPROC)    SDL_GL_GetProcAddress("glMultiTexCoord3fARB");

    if(!strstr(exts, "GL_ARB_vertex_program") || !strstr(exts, "GL_ARB_fragment_program")) leave("no shaders!");

    glGenPrograms_ =            (PFNGLGENPROGRAMSARBPROC)           SDL_GL_GetProcAddress("glGenProgramsARB");
    glBindProgram_ =            (PFNGLBINDPROGRAMARBPROC)           SDL_GL_GetProcAddress("glBindProgramARB");
    glProgramString_ =          (PFNGLPROGRAMSTRINGARBPROC)         SDL_GL_GetProcAddress("glProgramStringARB");
    glProgramEnvParameter4f_ =  (PFNGLPROGRAMENVPARAMETER4FARBPROC) SDL_GL_GetProcAddress("glProgramEnvParameter4fARB");
    glProgramEnvParameter4fv_ = (PFNGLPROGRAMENVPARAMETER4FVARBPROC)SDL_GL_GetProcAddress("glProgramEnvParameter4fvARB");

    if(!strstr(exts, "GL_EXT_framebuffer_object")) leave("no FBOs!");

    glBindRenderbuffer_        = (PFNGLBINDRENDERBUFFEREXTPROC)       SDL_GL_GetProcAddress("glBindRenderbufferEXT");
    glDeleteRenderbuffers_     = (PFNGLDELETERENDERBUFFERSEXTPROC)    SDL_GL_GetProcAddress("glDeleteRenderbuffersEXT");
    glGenRenderbuffers_        = (PFNGLGENFRAMEBUFFERSEXTPROC)        SDL_GL_GetProcAddress("glGenRenderbuffersEXT");
    glRenderbufferStorage_     = (PFNGLRENDERBUFFERSTORAGEEXTPROC)    SDL_GL_GetProcAddress("glRenderbufferStorageEXT");
    glCheckFramebufferStatus_  = (PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) SDL_GL_GetProcAddress("glCheckFramebufferStatusEXT");
    glBindFramebuffer_         = (PFNGLBINDFRAMEBUFFEREXTPROC)        SDL_GL_GetProcAddress("glBindFramebufferEXT");
    glDeleteFramebuffers_      = (PFNGLDELETEFRAMEBUFFERSEXTPROC)     SDL_GL_GetProcAddress("glDeleteFramebuffersEXT");
    glGenFramebuffers_         = (PFNGLGENFRAMEBUFFERSEXTPROC)        SDL_GL_GetProcAddress("glGenFramebuffersEXT");
    glFramebufferTexture2D_    = (PFNGLFRAMEBUFFERTEXTURE2DEXTPROC)   SDL_GL_GetProcAddress("glFramebufferTexture2DEXT");
    glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)SDL_GL_GetProcAddress("glFramebufferRenderbufferEXT");

    glEnable(GL_VERTEX_PROGRAM_ARB);
    glEnable(GL_FRAGMENT_PROGRAM_ARB);
}

void compileasmshader(GLenum type, GLuint &idx, const char *name, const char *part, const char *def)
{
    glGenPrograms_(1, &idx);
    glBindProgram_(type, idx);
    def += strspn(def, " \t\r\n");
    glProgramString_(type, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)strlen(def), def);
    GLint err;
    glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &err);
    if(err!=-1)
    {
        printf("COMPILE ERROR (%s:%s) - %s", name, part, glGetString(GL_PROGRAM_ERROR_STRING_ARB));
        loop(i, err) putchar(*def++);
        puts(" <<HERE>> ");
        while(*def) putchar(*def++);
    }
}

struct asmshader
{
    enum { FRAGMENT, VERTEX };

    const char *name;
    GLuint vs, ps;

    asmshader(const char *name) : name(name), vs(0), ps(0) {}

    void compile(const char *vsdef, const char *psdef)
    {
        compileasmshader(GL_VERTEX_PROGRAM_ARB,   vs, name, "VS", vsdef);
        compileasmshader(GL_FRAGMENT_PROGRAM_ARB, ps, name, "PS", psdef);
    }

    void set()
    {
        glBindProgram_(GL_VERTEX_PROGRAM_ARB,   vs);
        glBindProgram_(GL_FRAGMENT_PROGRAM_ARB, ps);
    }

    void param(int type, int n, float x = 0, float y = 0, float z = 0, float w = 0)
    {
        glProgramEnvParameter4f_(type==VERTEX ? GL_VERTEX_PROGRAM_ARB : GL_FRAGMENT_PROGRAM_ARB, n, x, y, z, w);
    }

    void paramv(int type, int n, float *v)
    {
        glProgramEnvParameter4fv_(type==VERTEX ? GL_VERTEX_PROGRAM_ARB : GL_FRAGMENT_PROGRAM_ARB, n, v);
    }
};

asmshader bbshader("bb"), 
          colorshadowshader[2] = { asmshader("colorshadow1"), asmshader("colorshadow2") },
          colorshader("color"), shadowshader("shadow");

void makeshaders()
{
    bbshader.compile(
        "!!ARBvp1.0\n"
        "MOV result.position, vertex.position;"
        "MOV result.texcoord[0], vertex.texcoord[0];"
        "\nEND",
        
        "!!ARBfp1.0\n"
        "TEX result.color, fragment.texcoord[0], texture[0], 2D;"
        "\nEND");

    colorshader.compile(
        "!!ARBvp1.0\n"
        "OPTION ARB_position_invariant;"
        "MOV result.color, vertex.color;"
        "\nEND",

        "!!ARBfp1.0\n"
        "MOV result.color, fragment.color;"
        "\nEND"
    );

    colorshadowshader[0].compile(
        "!!ARBvp1.0\n"
        "OPTION ARB_position_invariant;"
        "SUB result.texcoord[1], program.env[2], vertex.position;"
        "MOV result.texcoord[1].w, 20;"
        "MOV result.color, vertex.color;"
        "MOV result.texcoord[2], vertex.normal;"

        "TEMP origz, lvec, foo, tc1, tc2;"
        "SUB lvec, vertex.position, program.env[2];"
        "MUL lvec, lvec, 0.05;" // 1/20
        "MOV origz, lvec.z;"
        "DP3 lvec.w, lvec, lvec;"
        "RSQ lvec.w, lvec.w;"
        "MUL lvec.xyz, lvec, lvec.w;"
        "SUB foo.x, lvec.z, 1.02;"
        "ADD foo.y, lvec.z, 1.02;"
        "RCP foo.x, foo.x;"
        "RCP foo.y, foo.y;"
        "MUL foo.xy, foo, 0.5;"
        "MAD result.texcoord[0].xy, lvec, foo.x, 0.5;"
        "MAD result.texcoord[3].xy, lvec, foo.y, 0.5;"
        "MOV result.texcoord[0].z, origz;"
        "RCP result.texcoord[0].w, lvec.w;"
        "\nEND",

        "!!ARBfp1.0\n"
        "TEMP tc, s1, s2, s3, s4, s5, s6, s7, s8, s9, moments, shadow, variance, diff, pmax;"

        "CMP tc.xy, fragment.texcoord[0].z, fragment.texcoord[0], fragment.texcoord[3];"
        "ADD s1, tc, { -0.001953125, -0.001953125, 0, 0 };"
        "ADD s2, tc, {            0, -0.001953125, 0, 0 };"
        "ADD s3, tc, {  0.001953125, -0.001953125, 0, 0 };"
        "ADD s4, tc, { -0.001953125,           0, 0, 0 };"
        "ADD s5, tc, {            0,           0, 0, 0 };"
        "ADD s6, tc, {  0.001953125,           0, 0, 0 };"
        "ADD s7, tc, { -0.001953125,  0.001953125, 0, 0 };"
        "ADD s8, tc, {            0,  0.001953125, 0, 0 };"
        "ADD s9, tc, {  0.001953125,  0.001953125, 0, 0 };"
        "TEX s1, s1, texture[0], 2D;"
        "TEX s2, s2, texture[0], 2D;"
        "TEX s3, s3, texture[0], 2D;"
        "TEX s4, s4, texture[0], 2D;"
        "TEX s5, s5, texture[0], 2D;"
        "TEX s6, s6, texture[0], 2D;"
        "TEX s7, s7, texture[0], 2D;"
        "TEX s8, s8, texture[0], 2D;"
        "TEX s9, s9, texture[0], 2D;"

        "ADD moments, s1, s2;"
        "ADD moments, moments, s3;"
        "ADD moments, moments, s4;"
        "ADD moments, moments, s5;"
        "ADD moments, moments, s6;"
        "ADD moments, moments, s7;"
        "ADD moments, moments, s8;"
        "ADD moments, moments, s9;"
        "MUL moments, moments, 0.111111111111111;"
        "CMP moments.xy, fragment.texcoord[0].z, moments, moments.zwzw;"
        "SGE shadow, moments.x, fragment.texcoord[0].w;"
        "MAD variance, -moments.x, moments.x, moments.y;"
        "ADD_SAT variance, variance, 0.005;"
        "SUB diff, moments.x, fragment.texcoord[0].w;"
        "MAD pmax, diff, diff, variance;"
        "RCP pmax.x, pmax.x;"
        "MUL pmax.x, pmax.x, variance;"
        "MAX shadow, shadow, pmax.x;"
        "TEMP lvec, light;"
        "DP3 lvec, fragment.texcoord[1], fragment.texcoord[1];"
        "RSQ lvec.w, lvec.w;"
        "MUL lvec, fragment.texcoord[1], lvec.w;"
        "RCP lvec.w, lvec.w;"
        "SUB_SAT lvec.w, 1, lvec.w;"
        "DP3_SAT light, fragment.texcoord[2], lvec;"
        "MUL light, light, lvec.w;"
        "MUL result.color, light, shadow;"
        "\nEND"
    );

    colorshadowshader[1].compile(
        "!!ARBvp1.0\n"
        "OPTION ARB_position_invariant;"
        "SUB result.texcoord[1], program.env[2], vertex.position;"
        "MOV result.texcoord[1].w, 20;"
        "MOV result.color, vertex.color;"
        "MOV result.texcoord[2], vertex.normal;"

        "TEMP origz, lvec, foo, tc1, tc2;"
        "SUB lvec, vertex.position, program.env[2];"
        "MUL lvec, lvec, 0.05;" // 1/20
        "MOV origz, lvec.z;"
        "DP3 lvec.w, lvec, lvec;"
        "RSQ lvec.w, lvec.w;"
        "MUL lvec.xyz, lvec, lvec.w;"
        "SUB foo.x, lvec.z, 1.02;"
        "ADD foo.y, lvec.z, 1.02;"
        "RCP foo.x, foo.x;"
        "RCP foo.y, foo.y;"
        "MUL foo.xy, foo, 0.5;"
        "MAD result.texcoord[0].xy, lvec, foo.x, 0.5;"
        "MAD result.texcoord[3].xy, lvec, foo.y, 0.5;"
        "MOV result.texcoord[0].z, origz;"
        "RCP result.texcoord[0].w, lvec.w;"
        "\nEND",

        "!!ARBfp1.0\n"
        "TEMP tc, s1, s2, s3, s4, s5, s6, s7, s8, s9, moments, shadow, variance, diff, pmax;"
       
        "CMP tc.xy, fragment.texcoord[0].z, fragment.texcoord[0], fragment.texcoord[3];" 
        "ADD s1, tc, { -0.001953125, -0.001953125, 0, 0 };"
        "ADD s2, tc, {            0, -0.001953125, 0, 0 };"
        "ADD s3, tc, {  0.001953125, -0.001953125, 0, 0 };"
        "ADD s4, tc, { -0.001953125,           0, 0, 0 };"
        "ADD s5, tc, {            0,           0, 0, 0 };"
        "ADD s6, tc, {  0.001953125,           0, 0, 0 };"
        "ADD s7, tc, { -0.001953125,  0.001953125, 0, 0 };"
        "ADD s8, tc, {            0,  0.001953125, 0, 0 };"
        "ADD s9, tc, {  0.001953125,  0.001953125, 0, 0 };"
        "TEX s1, s1, texture[0], 2D;"
        "TEX s2, s2, texture[0], 2D;"
        "TEX s3, s3, texture[0], 2D;"
        "TEX s4, s4, texture[0], 2D;"
        "TEX s5, s5, texture[0], 2D;"
        "TEX s6, s6, texture[0], 2D;"
        "TEX s7, s7, texture[0], 2D;"
        "TEX s8, s8, texture[0], 2D;"
        "TEX s9, s9, texture[0], 2D;"

        "ADD moments, s1, s2;"
        "ADD moments, moments, s3;"
        "ADD moments, moments, s4;"
        "ADD moments, moments, s5;"
        "ADD moments, moments, s6;"
        "ADD moments, moments, s7;"
        "ADD moments, moments, s8;"
        "ADD moments, moments, s9;"
        "MUL moments, moments, 0.111111111111111;"
        "CMP moments.xy, fragment.texcoord[0].z, moments, moments.zwzw;"
        "SGE shadow, moments.x, fragment.texcoord[0].w;"
        "MAD_SAT variance, -moments.x, moments.x, moments.y;"
        "ADD_SAT variance, variance, 0.001;"
        "SUB diff, moments.x, fragment.texcoord[0].w;"
        "MAD pmax, diff, diff, variance;"
        "RCP pmax.x, pmax.x;"
        "MUL pmax.x, pmax.x, variance;"
        "MAX shadow, shadow, pmax.x;"
        "TEMP lvec, light;"
        "DP3 lvec, fragment.texcoord[1], fragment.texcoord[1];"
        "RSQ lvec.w, lvec.w;"
        "MUL lvec, fragment.texcoord[1], lvec.w;"
        "RCP lvec.w, lvec.w;"
        "SUB_SAT lvec.w, 1, lvec.w;"
        "DP3_SAT light, fragment.texcoord[2], lvec;"
        "MUL light, light, lvec.w;"
        "MUL light, light, shadow;"
        "MUL result.color, light, fragment.color;"
        "\nEND"
    );

    shadowshader.compile(
        "!!ARBvp1.0\n"
        "TEMP lvec, foo, origz;"
        "SUB lvec, vertex.position, program.env[3];"
        "MUL lvec, lvec, 0.05;" // 1/20
        "MOV origz, lvec.z;"
        "DP3 lvec.w, lvec, lvec;"
        "RSQ lvec.w, lvec.w;"
        "MUL lvec.xyz, lvec, lvec.w;"
        "ADD foo.x, lvec.z, program.env[4].x;"
        "RCP foo.x, foo.x;"
        "MUL lvec.xy, lvec, foo.x;"
        "RCP lvec.w, lvec.w;"
        "MAD lvec.z, 2, lvec.w, -1;"
        "MOV result.position.xyz, lvec;"
        "MOV result.position.w, 1;"
        "MOV result.texcoord[0], lvec.w;"
        "MOV result.texcoord[1].xy, lvec;"
        "MOV result.texcoord[1].z, 0;"
        "MAD result.texcoord[1].w, origz, program.env[4].x, 0.02;"
        "\nEND",

        "!!ARBfp1.0\n"
        "TEMP inside;"
        "DP3 inside.w, fragment.texcoord[1], fragment.texcoord[1];"
        "SLT inside.w, inside.w, 1;"
        "MUL inside.w, inside.w, fragment.texcoord[1].w;"
        "KIL inside.w;"
        "MOV result.color.xz, fragment.texcoord[0];"
        "MUL result.color.yw, fragment.texcoord[0], fragment.texcoord[0];"
        "\nEND"
    );

    colorshader.set();
};

enum { EMPTY, SOLID, ENTITY };
enum { WIDTH = 64, HEIGHT = 32 };

struct cube
{
    int type, color;
    vecf lighting[6];
    bool visible[6];
    vecf vel, pos;
    int update;
}
world[WIDTH][WIDTH][HEIGHT];

cube &getcube(const veci &v) { return world[v.x&(WIDTH-1)][v.y&(WIDTH-1)][v.z&(HEIGHT-1)]; }

veci sun(0, -32, 96);

float yaw = 0, pitch = 0;
vecf cam, light;
int move = 0, strafe = 0;
bool kup = false, kdown = false, kleft = false, kright = false, fire = false; 
int lastfire = 0;

int cv[8][3] = { {0,0,0}, {1,0,0}, {0,1,0}, {1,1,0}, {0,0,1}, {1,0,1}, {0,1,1}, {1,1,1} };
int dv[6][3] = { {1,0,0}, {-1,0,0}, {0,1,0}, {0,-1,0}, {0,0,1}, {0,0,-1} };
int iv[6][4] = { {1,5,7,3}, {0,2,6,4}, {2,3,7,6}, {0,4,5,1}, {4,6,7,5}, {0,1,3,2} };
float colors[8][3] = { {1,0,0}, {0,1,0}, {0,0,1}, {1,1,0}, {1,0,1}, {1,1,1}, {0,0,0} };

void changed(cube &c, veci &v)
{
    if(c.type==EMPTY) return;
    loop(i, 6) 
    {
        if(c.visible[i] = getcube(v+*(veci *)dv[i]).type!=SOLID)
            c.lighting[i] = (*(vecf *)colors[c.color]) * (vecf(sun-v).normalize().dot((vecf)(veci)dv[i])*0.3f+0.7f);
    }
}

void clearworld()
{
    cam = light = vecf(WIDTH/2, WIDTH/2, HEIGHT/2);
    loopworld
    ({
        c.type = v.z<HEIGHT-2 ? SOLID : EMPTY;
        c.color = rand()%6;
        c.update = 0;
    });
    loop(i, 3000)
    {
        int x = rand()%WIDTH-1, y = rand()%WIDTH, z = rand()%HEIGHT, w = rand()%(WIDTH/8), h = rand()%(HEIGHT/4);
        loop(xx, w) loop(yy, w) loop(zz, h) getcube(veci(xx+x, yy+y, zz+z)).type = EMPTY;    
    }
    loop(i, 8) getcube(veci(cam.x+(i&1?-1:0), cam.y+(i&2?-1:0), cam.z+(i&4?-1:0))).type = EMPTY;

    loopworld(changed(c, v));
}

bool movelight = false;
void gameupdate()
{
    vecf forward(sinf(RAD*(yaw))*cosf(RAD*pitch), -cosf(RAD*(yaw))*cosf(RAD*pitch), sinf(RAD*-pitch));
    vecf side(-cosf(RAD*(yaw)), -sinf(RAD*(yaw)), 0);
    if(movelight) light = light+forward*(move*WIDTH*frametime*1)/1000.0f+side*(strafe*WIDTH*frametime*1)/1000.0f;
    else cam = cam+forward*(move*WIDTH*frametime*10)/1000.0f+side*(strafe*WIDTH*frametime*10)/1000.0f;
    if(fire && lastfire+100<lastmillis)
    {
        lastfire = lastmillis;
        cube &c = getcube((veci)cam);
        c.type = ENTITY;
        c.vel = forward;
        c.pos = cam;
        c.color = 7;
    }
    loopworld
    ({
        if(c.type!=ENTITY || c.update==frames) continue;
        c.pos = c.pos+c.vel;
        veci nv(c.pos);
        cube &o = getcube(nv);
        if(&c==&o) continue;
        //if(o.type!=EMPTY) continue;
        o = c;
        o.update = frames;
        c.type = EMPTY;
        changed(o, nv);
        loop(i, 6) { veci n = v+*(veci *)dv[i]; changed(getcube(n), n); }
    })
}

#define DPSM_SIZE 512

GLuint dpsmfb[2] = { 0, 0 }, dpsmtex[2] = { 0, 0 }, dpsmdb = 0;

void createdpsm(int which)
{
    glGenTextures(1, &dpsmtex[which]);
    glBindTexture(GL_TEXTURE_2D, dpsmtex[which]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, DPSM_SIZE, DPSM_SIZE, 0, GL_RGBA, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glGenFramebuffers_(1, &dpsmfb[which]);
    glBindFramebuffer_(GL_FRAMEBUFFER_EXT, dpsmfb[which]);
    glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, dpsmtex[which], 0);
    if(glCheckFramebufferStatus_(GL_FRAMEBUFFER_EXT)!=GL_FRAMEBUFFER_COMPLETE_EXT) leave("failed creating color buffer!");
    if(!dpsmdb)
    {
        glGenRenderbuffers_(1, &dpsmdb);
        glBindRenderbuffer_(GL_RENDERBUFFER_EXT, dpsmdb);
        glRenderbufferStorage_(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, DPSM_SIZE, DPSM_SIZE);
    }
    glFramebufferRenderbuffer_(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, dpsmdb);
    if(glCheckFramebufferStatus_(GL_FRAMEBUFFER_EXT)!=GL_FRAMEBUFFER_COMPLETE_EXT) leave("failed creating depth buffer!");
    glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
}

int smcull = 0;

void drawdpsm()
{
    loop(i, 2) if(!dpsmfb[i]) createdpsm(i); 
    glBindFramebuffer_(GL_FRAMEBUFFER_EXT, dpsmfb[0]);
    shadowshader.set();
    shadowshader.param(asmshader::VERTEX, 0, 2.0f/WIDTH, 2.0f/WIDTH, -1.0f/HEIGHT, 1);
    shadowshader.param(asmshader::VERTEX, 1, -1, -1, 0, 0);
    shadowshader.param(asmshader::VERTEX, 2, 1.0f/HEIGHT, 0);

    glViewport(0, 0, DPSM_SIZE, DPSM_SIZE);
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);

  loop(hemisphere, 2)
  { 
    glColorMask(!hemisphere ? GL_TRUE : GL_FALSE, !hemisphere ? GL_TRUE : GL_FALSE, hemisphere ? GL_TRUE : GL_FALSE, hemisphere ? GL_TRUE : GL_FALSE); 
    glClear(GL_DEPTH_BUFFER_BIT);
    shadowshader.param(asmshader::VERTEX, 3, light.x, light.y, light.z);
    shadowshader.param(asmshader::VERTEX, 4, 1.02*(-1 + 2*hemisphere));

    if(smcull) glCullFace(GL_FRONT);
    glFrontFace(hemisphere ? GL_CCW : GL_CW);
    glBegin(GL_QUADS);
    loopworld
    ({
        if(c.type!=EMPTY) loop(i, 6) if(c.visible[i])
        {
            loop(j, 4) glVertex3iv(v+(veci)cv[iv[i][j]]);
        }
    });
    glEnd();
    if(smcull) glCullFace(GL_BACK);
}
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
}

int smcolor = 0;

void drawframe()
{
    glClearDepth(2.0);
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    drawdpsm();

    glViewport(0, 0, scrw, scrh);
    colorshadowshader[smcolor].set();
    colorshadowshader[smcolor].param(asmshader::VERTEX, 0, 1.0f/WIDTH, 1.0f/WIDTH, 1.0f/HEIGHT, 1);
    colorshadowshader[smcolor].param(asmshader::VERTEX, 1, 0, 0, 0.01f, 0);
    colorshadowshader[smcolor].param(asmshader::VERTEX, 2, light.x, light.y, light.z);

    glBindTexture(GL_TEXTURE_2D, dpsmtex[0]);

    glClearColor(0.6f, 0.7f, 1, 1);
    glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(80, 1, 0.1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(-pitch, -1, 0, 0);
    glRotatef(yaw, 0, 1, 0);
    glRotatef(-90, 1, 0, 0);
    glScalef(1, -1, 1);
    glTranslatef(-cam.x, -cam.y, -cam.z);  
    glBegin(GL_QUADS);
    loopworld
    ({
        if(c.type!=EMPTY) loop(i, 6) if(c.visible[i])
        {
            glColor3fv(colors[c.color]);//lighting[i]);
            glNormal3f(dv[i][0], dv[i][1], dv[i][2]);
            loop(j, 4) glVertex3iv(v+(veci)cv[iv[i][j]]);
        }
    }); 
    glEnd();

    if(movelight)
    {
        colorshader.set();
        glBegin(GL_QUADS);
        glColor3f(0, 1, 0);
        loop(i, 6) loop(j, 4) glVertex3fv(vecf(light)+vecf(-0.05f, -0.05f, -0.05f)+vecf(veci(cv[iv[i][j]]))*0.1f);
        glEnd();
    }

    bbshader.set();
    glDisable(GL_DEPTH_TEST);
    glBegin(GL_QUADS);
    glTexCoord2i(0, 0); glVertex3f(0.5, 0.5, 0);
    glTexCoord2i(1, 0); glVertex3f(1, 0.5, 0);
    glTexCoord2i(1, 1); glVertex3f(1, 1, 0);
    glTexCoord2i(0, 1); glVertex3f(0.5, 1, 0);
    glEnd();
    glEnable(GL_DEPTH_TEST);

}

void key(int k, bool down)
{
    switch(k)
    {
        case SDLK_ESCAPE: leave("esc");   
        case SDLK_F1: clearworld(); break;
        case SDLK_w: move   = (kup    = down) ?  1 : (kdown  ? -1 : 0); break;
        case SDLK_s: move   = (kdown  = down) ? -1 : (kup    ?  1 : 0); break;
        case SDLK_a: strafe = (kleft  = down) ?  1 : (kright ? -1 : 0); break;
        case SDLK_d: strafe = (kright = down) ? -1 : (kleft  ?  1 : 0); break;
        case SDLK_c: if(down) { smcolor = (smcolor+1)%2; printf("smcolor: %d\n", smcolor); } break;
        case SDLK_b: if(down) { smcull = (smcull+1)%2; printf("smcull: %d\n", smcull); } break;
        case SDLK_l: movelight = down; if(down) light = cam; break; 
        case -1: fire = down; break;
    }
}

int main(int argc, char **argv)
{
    bool fullscreen = false;
    if(SDL_Init(SDL_INIT_VIDEO)<0) leave("no SDL");
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    if(!SDL_SetVideoMode(scrw, scrh, 32, SDL_OPENGL|(fullscreen ? SDL_FULLSCREEN : 0)|SDL_RESIZABLE)) leave("no OpenGL window");
    SDL_WM_SetCaption("nanocube engine (DP remix)", NULL);
    if(fullscreen) SDL_WM_GrabInput(SDL_GRAB_ON);
    SDL_ShowCursor(0);
    initgl();
    makeshaders();
    srand(time(NULL));
    clearworld();
    starttime = SDL_GetTicks();
    int grabmouse = 0;
    for(;;)
    {
        int millis = SDL_GetTicks();
        frametime = lastmillis-millis;
        if(frametime<1)   frametime = 1;
        lastmillis = millis;
        frames++;
        gameupdate();
        SDL_GL_SwapBuffers();
        drawframe();
        SDL_Event event;
        while(SDL_PollEvent(&event)) switch(event.type)
        {
            case SDL_QUIT:
                leave("close");
                break;
        
            case SDL_KEYDOWN:
            case SDL_KEYUP:
                key(event.key.keysym.sym, event.key.state==SDL_PRESSED);
                break;

            case SDL_ACTIVEEVENT:
                if(event.active.state & SDL_APPINPUTFOCUS)
                    grabmouse = event.active.gain;
                else
                if(event.active.gain)
                    grabmouse = 1;
                break;

            case SDL_MOUSEBUTTONDOWN:
            case SDL_MOUSEBUTTONUP:
                key(-event.button.button, event.button.state!=0);
                break;

            case SDL_MOUSEMOTION:
                if(!fullscreen && grabmouse)
                {
                    if(event.motion.x == scrw / 2 && event.motion.y == scrh / 2) break;
                    SDL_WarpMouse(scrw / 2, scrh / 2);
                }
                if(fullscreen || grabmouse)
                {
                    yaw += event.motion.xrel/10.0f;
                    pitch += event.motion.yrel/10.0f;
                    if(pitch>90) pitch = 90;
                    else if(pitch<-90) pitch = -90;
                }
                break;
        }
    }
}
