#include "ve2.h"
#include "glext.h"
//#include "imageloader.h"
#include "texgen.h"
//#include "glow.h"
//#include "spline.h"
//#include "mesh.h"
//#include "shader.h"
//#include "vertexbuf.h"
//#include "videodec.h"
//#include "voxel.h"
#include "sync.h"

#define FADE_OUT_TIME (POINT08 - POINT07)
#define FADE_OUT_SPEED 0.5f
#define MAX_LIGHT  1.25f
#define MIN_LIGHT  0.50f

#define GRID_SIZE 48
#define CUBE_SIZE 0.475f
#define MOVE_SPEED 5.0f
#define SWITCH_INTERVAL 5.0f

static float ft, last_ft;

extern GLuint phong_shader;  // from scene_clock.c

////////////////////////////////////////////////////////////////////////////////

const static unsigned char logo_drg[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xFE, 0x82, 0x82, 0x7C, 0x00, 0xFE, 0x90, 0x98, 0x94, 0x62, 0x00, 0x7C, 0x82, 0x82, 0x92, 0x5E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const static unsigned char logo_fm[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x90, 0x90, 0x80, 0x00, 0xFA, 0x00, 0xFE, 0x40, 0x20, 0x40, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const static unsigned char logo_still[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x92, 0x92, 0x92, 0x4C, 0x00, 0x80, 0x80, 0xFE, 0x80, 0x80, 0x00, 0xFE, 0x00, 0xFE, 0x02, 0x02, 0x02, 0x00, 0xFE, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00 };
const static unsigned char logo_mds[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x40, 0x20, 0x40, 0xFE, 0x00, 0x82, 0xFE, 0x82, 0x82, 0x7C, 0x00, 0x64, 0x92, 0x92, 0x92, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

typedef float (*grid_func)(float, float, float);
static float mat_follow[16], mat_random[16];
#define gf_test gf_ball

static __inline void xform(float *_x, float *_y, float *_z, const float *m) {
    float x = *_x, y = *_y, z = *_z;
    *_x = m[ 0] * x + m[ 4] * y + m[ 8] * z + m[12];
    *_y = m[ 1] * x + m[ 5] * y + m[ 9] * z + m[13];
    *_z = m[ 2] * x + m[ 6] * y + m[10] * z + m[14];
}

static __forceinline float xsample(const unsigned char sample, int y, float f) {
    float s = ((sample >> y) & 1) * (1.0f - f) + ((sample >> (y + 1)) & 1) * f;
    return s*s;
}

static float getlogo(float x, float y, float z, const unsigned char *logo) {
    int ix, iy;
    float fx, fy;
    x += 16.0f;
    y += 4.0f;
    fx = (float) libc_floor(x);  ix = (int) fx; 
    fy = (float) libc_floor(y);  iy = (int) fy;  
    if ((ix < 0) || (iy < 0) || (x >= 32) || (y > 8)) return -1.0f;
    fx = x - fx;
    fy = y - fy;
    fx = xsample(logo[ix], iy, fy) * (1.0f - fx) + xsample(logo[ix + 1], iy, fy) * fx;
    return fx*fx - z*z;
}

static float gf_torus(float x, float y, float z) {
    float r;
    xform(&x, &y, &z, mat_random);
    r = sqrtf(x*x + z*z) - 0.6f;
    return 0.0625f - r*r - y*y;
}

static float gf_plane(float x, float y, float z) {
    xform(&x, &y, &z, mat_random);
    return 0.1f - fabsf(z);
}

static float gf_ball(float x, float y, float z) {
    x += 0.5f * sinf(0.923f * ft + 5.233f);
    y += 0.5f * sinf(0.427f * ft + 3.622f);
    z += 0.5f * sinf(0.241f * ft + 1.562f);
    return 0.375f - x*x - y*y - z*z;
}

#define LOGO_FUNC(group, xyscale, zscale) \
    static float gf_logo_##group(float x, float y, float z) { \
        xform(&x, &y, &z, mat_follow); \
        return getlogo(y * (xyscale), z * (xyscale), x * (zscale), logo_##group); \
    }

LOGO_FUNC(still, 10.0f, 10.0f)
LOGO_FUNC(drg,    8.0f,  8.0f)
LOGO_FUNC(fm,     8.0f,  8.0f)
LOGO_FUNC(mds,    9.0f,  8.0f)

const static grid_func grid_func_list[] = {
    gf_logo_drg,
    gf_ball,
    gf_logo_mds,
    gf_plane,
    gf_logo_still,
    gf_torus,
    gf_logo_fm,
NULL};
static const grid_func *current_grid_func = grid_func_list;
static float next_switch = SWITCH_INTERVAL;

////////////////////////////////////////////////////////////////////////////////

#define GP_ENABLED  1
#define GP_VISIBLE  2
#define GP_FADING   4

typedef struct _grid_point {
    unsigned char ix, iy, iz, flags;
    float         cx, cy, cz, light;
} grid_point_t;

static grid_point_t grid[GRID_SIZE * GRID_SIZE * GRID_SIZE];
static float gc[GRID_SIZE];

static void InitGrid(void) {
    int x, y, z;
    grid_point_t *p = grid;
    for (z = 0;  z < GRID_SIZE;  ++z) {
        gc[z] = z * (2.0f / (GRID_SIZE - 1)) - 1.0f;
    for (y = 0;  y < GRID_SIZE;  ++y) {
    for (x = 0;  x < GRID_SIZE;  ++x) {
        p->flags = 0;
        p->ix = x;
        p->iy = y;
        p->iz = z;
    ++p; } } }
}

static void ClearGrid(void) {
    int x, y, z;
    grid_point_t *p = grid;
    for (z = 0;  z < GRID_SIZE;  ++z) {
    for (y = 0;  y < GRID_SIZE;  ++y) {
    for (x = 0;  x < GRID_SIZE;  ++x) {
        p->flags = 0;
        p->cx = (float) x;
        p->cy = (float) y;
        p->cz = (float) z;
        p->light = 0.0f;
    ++p; } } }
}

static void shuffle_list(int *list, int size) {
    int pos, temp;
    while (size > 1) {
        pos = randNext() % size;
        if (pos) {
            temp = list[pos];
            list[pos] = list[0];
            list[0] = temp;
        }
        --size;
        ++list;
    }
}

static void UpdateGrid(grid_func func, float dt) {
    grid_point_t *p, *q;
    int x, y, z, i;
    float dt2;
    static int list[GRID_SIZE * GRID_SIZE * GRID_SIZE];
    int *ss_list = list;
    int *sd_list = &list[GRID_SIZE * GRID_SIZE * GRID_SIZE - 1];
    int ss_count = 0, sd_count = 0;
    
    /***** apply grid function and detect changes *****/
    p = grid;  i = 0;
    for (z = 0;  z < GRID_SIZE;  ++z) {
    for (y = 0;  y < GRID_SIZE;  ++y) {
    for (x = 0;  x < GRID_SIZE;  ++x) {
        int fl = p->flags;
        int de = (func(gc[x], gc[y], gc[z]) > 0.0f) - (fl & GP_ENABLED);
        if (de > 0) {
            // point becomes enabled -> add to "searching source" list
            p->flags = fl | GP_ENABLED;
            ss_list[ss_count++] = i;
        }
        else if (de < 0) {
            // point becomes disabled -> add to "searching destination" list
            p->flags = fl & (~GP_ENABLED);
            *sd_list-- = i;
            ++sd_count;
        }
    ++p; ++i; } } }
//OUT2("ss_count=%d, sd_count=%d -> ", ss_count, sd_count);

    /***** shuffle lists *****/
    shuffle_list(ss_list, ss_count);
    shuffle_list(sd_list, sd_count);

    /***** partner search *****/
    while (ss_count && sd_count) {
        int best_partner = 0;
        p = &grid[*ss_list++];
        // TODO: search
        q = &grid[sd_list[best_partner]];
        // copy source to destination, activate destination, kill source
        p->cx = q->cx;
        p->cy = q->cy;
        p->cz = q->cz;
        p->light = q->light;
        p->flags = GP_ENABLED | GP_VISIBLE | GP_FADING;
        q->flags = 0;
        // reduce "searching destination" list
        sd_list[best_partner] = sd_list[0];
        ++sd_list;
        --sd_count;
    }
//OUT2("ss_count=%d, sd_count=%d\n", ss_count, sd_count);

    /***** enable orphaned invisible "searching source" points *****/
    while (ss_count--) {
        p = &grid[*ss_list++];
        if (!(p->flags & GP_VISIBLE)) {
            p->flags = GP_ENABLED | GP_VISIBLE | GP_FADING;
            p->cx = p->ix;
            p->cy = p->iy;
            p->cz = p->iz;
            p->light = 0.0f;
        }
    }

    /***** update positions and intensities *****/
    if (dt > 1.0f) dt = 1.0f;
    dt2 = 1.0f - dt;
    p = grid;
    for (i = GRID_SIZE * GRID_SIZE * GRID_SIZE;  i;  --i, ++p) {
        float l;
        if (!(p->flags & GP_VISIBLE)) continue;
        l = p->light * dt2;
        if (p->flags & GP_ENABLED) {
            l += dt;
            if (l >= (1.0f - 1.0f / 256))
                p->flags &= ~GP_FADING;
        } else {
            p->flags |= GP_FADING;
            if (l < (1.0f / 256)) {
                p->flags = 0;  // just became invisible
                continue;
            }
        }
        p->light = l;
        p->cx = p->cx * dt2 + p->ix * dt;
        p->cy = p->cy * dt2 + p->iy * dt;
        p->cz = p->cz * dt2 + p->iz * dt;
    }
}

////////////////////////////////////////////////////////////////////////////////

typedef struct _sc_vertex {
    signed char px, py, pz;
    signed char s, t;
    signed char nx, ny, nz;
} sc_vertex_t;

static const sc_vertex_t CubeData[24] = {
    // front
    { -1,-1,+1,  0,0,  0,0,-1 },
    { -1,-1,-1,  0,1,  0,0,-1 },
    { +1,-1,-1,  1,1,  0,0,-1 },
    { +1,-1,+1,  1,0,  0,0,-1 },
    // right
    { +1,-1,+1,  0,0,  +1,0,0 },
    { +1,-1,-1,  0,1,  +1,0,0 },
    { +1,+1,-1,  1,1,  +1,0,0 },
    { +1,+1,+1,  1,0,  +1,0,0 },
    // back
    { +1,+1,+1,  0,0,  0,0,+1 },
    { +1,+1,-1,  0,1,  0,0,+1 },
    { -1,+1,-1,  1,1,  0,0,+1 },
    { -1,+1,+1,  1,0,  0,0,+1 },
    // left
    { -1,+1,+1,  0,0,  -1,0,0 },
    { -1,+1,-1,  0,1,  -1,0,0 },
    { -1,-1,-1,  1,1,  -1,0,0 },
    { -1,-1,+1,  1,0,  -1,0,0 },
    // bottom
    { -1,-1,-1,  0,0,  0,-1,0 },
    { -1,+1,-1,  0,1,  0,-1,0 },
    { +1,+1,-1,  1,1,  0,-1,0 },
    { +1,-1,-1,  1,0,  0,-1,0 },
    // top
    { -1,+1,+1,  0,0,  0,+1,0 },
    { -1,-1,+1,  0,1,  0,+1,0 },
    { +1,-1,+1,  1,1,  0,+1,0 },
    { +1,+1,+1,  1,0,  0,+1,0 },
};

static void DrawCube(float cx, float cy, float cz, float s) {
    const sc_vertex_t *v = CubeData;
    int i;
    for (i = 24;  i;  --i, ++v) {
        glNormal3f(v->nx, v->ny, v->nz);
        glTexCoord2i(v->s, v->t);
        glVertex3f(cx + s * v->px, cy + s * v->py, cz + s * v->pz);
    }
}

////////////////////////////////////////////////////////////////////////////////

#define TRIANGLE_THRESHOLD 0.005f
#define THRESHOLD_JITTER (0.125f * TRIANGLE_THRESHOLD)
#define MAX_ELEVATION 0.0625
#define MAX_DISPLACEMENT 0.125f

#define GetDisplacement() ((0.5f - MAX_DISPLACEMENT) + ((2.0f * MAX_DISPLACEMENT) * (float)randFloat()))

// MSVC's optimizer breaks the following code, but since it's only used for
// construction of a single display list, we can live without optimization here
#pragma optimize("", off)

static void ectri(
    float x1, float y1, float z1,
    float x2, float y2, float z2,
    float x3, float y3, float z3
) {
    float a, b, c, s;
    float dx1 = x1 - x3,  dx2 = x2 - x3,  dx3;
    float dy1 = y1 - y3,  dy2 = y2 - y3,  dy3;
    float dz1 = z1 - z3,  dz2 = z2 - z3,  dz3;
    float cx, nx = dy1 * dz2 - dy2 * dz1;
    float cy, ny = dz1 * dx2 - dz2 * dx1;
    float cz, nz = dx1 * dy2 - dx2 * dy1;
    float n = nx*nx + ny*ny + nz*nz;

    // small triangle -> draw directly
    if ((n - THRESHOLD_JITTER * (float)randFloat()) < TRIANGLE_THRESHOLD) {
        glNormal3f(nx, ny, nz);
        glVertex3f(x1, y1, z1);
        glVertex3f(x2, y2, z2);
        glVertex3f(x3, y3, z3);
        return;
    }

    // interpolate edge point 1
    a = GetDisplacement();
    b = 1.0f - a;
    dx1 = a * x2 + b * x3;
    dy1 = a * y2 + b * y3;
    dz1 = a * z2 + b * z3;

    // interpolate edge point 2
    a = GetDisplacement();
    b = 1.0f - a;
    dx2 = a * x3 + b * x1;
    dy2 = a * y3 + b * y1;
    dz2 = a * z3 + b * z1;

    // interpolate edge point 3
    a = GetDisplacement();
    b = 1.0f - a;
    dx3 = a * x1 + b * x2;
    dy3 = a * y1 + b * y2;
    dz3 = a * z1 + b * z2;

    // draw 3 flat triangles
    ectri(dx3, dy3, dz3,   dx2, dy2, dz2,   x1, y1, z1);
    ectri(dx1, dy1, dz1,   dx3, dy3, dz3,   x2, y2, z2);
    ectri(dx2, dy2, dz2,   dx1, dy1, dz1,   x3, y3, z3);

    // determine center point
    a = GetDisplacement();
    b = GetDisplacement();
    c = GetDisplacement();
    s = 1.0f / (a + b + c);
    a *= s;  b *= s;  c *= s;
    n = (float) randFloatAroundZero(MAX_ELEVATION);
    cx = a * dx1 + b * dx2 + c * dx3 + n * nx;
    cy = a * dy1 + b * dy2 + c * dy3 + n * ny;
    cz = a * dz1 + b * dz2 + c * dz3 + n * nz;

    // draw 3 center triangles
    ectri(dx1, dy1, dz1,  dx2, dy2, dz2,   cx, cy, cz);
    ectri(dx2, dy2, dz2,  dx3, dy3, dz3,   cx, cy, cz);
    ectri(dx3, dy3, dz3,  dx1, dy1, dz1,   cx, cy, cz);
}

static void DrawEnvCube(void) {
    const sc_vertex_t *v = CubeData;
    int i;
    randSeed(475346634);
    glBegin(GL_TRIANGLES);
    for (i = 6;  i;  --i, v += 4) {
        float cx, cy, cz;
        float a = GetDisplacement();
        float b = GetDisplacement();
        float c = GetDisplacement();
        float d = GetDisplacement();
        float s = 1.0f / (a + b + c + d);
        a *= s;  b *= s;  c *= s;  d *= s;
        cx = a * v[0].px + b * v[1].px + c * v[2].px + d * v[3].px;
        cy = a * v[0].py + b * v[1].py + c * v[2].py + d * v[3].py;
        cz = a * v[0].pz + b * v[1].pz + c * v[2].pz + d * v[3].pz;
        #define DO_ECTRI(a,b) ectri( \
            v[b].px, v[b].py, v[b].pz, \
            v[a].px, v[a].py, v[a].pz, \
            cx, cy, cz)
        DO_ECTRI(0, 1);
        DO_ECTRI(1, 2);
        DO_ECTRI(2, 3);
        DO_ECTRI(3, 0);
    }
    glEnd();
}

#pragma optimize("", on)

////////////////////////////////////////////////////////////////////////////////

#define TUBE_RADIUS   0.5f
#define TUBE_EXTRA    0.25f
#define BALL_RADIUS   1.5f
#define BALL_EXTRA    0.4f

#define TUBE_VIRT_SIZE  (0.5f * GRID_SIZE + TUBE_EXTRA)
#define BALL_VIRT_SIZE  (0.5f * GRID_SIZE + TUBE_EXTRA + BALL_EXTRA)

static void gst_ball(float x, float y, float z) {
    glPushMatrix();
    glTranslatef(x, y, z);
    gluSphere(quad, BALL_RADIUS, 17, 9);
    glPopMatrix();
}

static void gst_tube(float x, float y) {
    glPushMatrix();
    glTranslatef(x, y, -TUBE_VIRT_SIZE);
    gluCylinder(quad, TUBE_RADIUS, TUBE_RADIUS, 2.0f * TUBE_VIRT_SIZE, 9, 1);
    glPopMatrix();
}

static void gst_tubes(void) {
    gst_tube(-TUBE_VIRT_SIZE, +TUBE_VIRT_SIZE);
    gst_tube(-TUBE_VIRT_SIZE, -TUBE_VIRT_SIZE);
    gst_tube(+TUBE_VIRT_SIZE, +TUBE_VIRT_SIZE);
    gst_tube(+TUBE_VIRT_SIZE, -TUBE_VIRT_SIZE);
}

static void Gestell(void) {
    gst_tubes();
    push
        glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
        gst_tubes();
    pop
    push
        glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
        gst_tubes();
    pop
    gst_ball(-BALL_VIRT_SIZE, -BALL_VIRT_SIZE, -BALL_VIRT_SIZE);
    gst_ball(-BALL_VIRT_SIZE, -BALL_VIRT_SIZE, +BALL_VIRT_SIZE);
    gst_ball(-BALL_VIRT_SIZE, +BALL_VIRT_SIZE, -BALL_VIRT_SIZE);
    gst_ball(-BALL_VIRT_SIZE, +BALL_VIRT_SIZE, +BALL_VIRT_SIZE);
    gst_ball(+BALL_VIRT_SIZE, -BALL_VIRT_SIZE, -BALL_VIRT_SIZE);
    gst_ball(+BALL_VIRT_SIZE, -BALL_VIRT_SIZE, +BALL_VIRT_SIZE);
    gst_ball(+BALL_VIRT_SIZE, +BALL_VIRT_SIZE, -BALL_VIRT_SIZE);
    gst_ball(+BALL_VIRT_SIZE, +BALL_VIRT_SIZE, +BALL_VIRT_SIZE);
}

////////////////////////////////////////////////////////////////////////////////

static GLuint texCubeSide;
static GLuint listEnvCube;

static void PrepareCubeSideTex(void) {
    TGimage *A;
    A = tgCreate(32,32, TG_FORMAT_GRAY | TG_FORMAT_WRAP);
    tgFill(A, 0xFF000000);
    tgRect(A, 4,4, 24,24, 0xFFFFFFFF);
    tgBlurHEx(A, 3, 1.00);
    tgBlurVEx(A, 3, 1.00);
    tgGamma(A, TG_RGB, 3.0);
    texCubeSide = tgCompileGL(A, 1);
    tgFree(A);
}

void Prepare_Cubes(void) {
    InitGrid();
    ClearGrid();
    PrepareCubeSideTex();

    // geneate environment cube
    listEnvCube = glGenLists(1);
    glNewList(listEnvCube, GL_COMPILE);
    DrawEnvCube();
    glEndList();
    UpdateProgress();
}

////////////////////////////////////////////////////////////////////////////////

void Init_Cubes(void) {
    SetPerspective(65.0, 1.0, 200.0);
    SetLight(GL_LIGHT0, 1.00f, 0.0f);
    SetLight(GL_LIGHT1, 0.50f, 0.0f);
    SetAmbientLight(0.125f);
    last_ft = (float) t;
    gluQuadricNormals(quad, GLU_SMOOTH);
    gluQuadricTexture(quad, GLU_FALSE);
}


void Render_Cubes(void) {
    int i;
    float rot, el, l;
    grid_point_t *p;
    ft = (float) t;

    // compute camera position
    rot = ft * 12.23f + 32.2f;
    el = 30.0f * sinf(0.5f * ft);

    // compute fading
    l = (float) t - (float) FADE_OUT_TIME;
    if (l < 0.0f)
        l = 1.0f;
    else if (l > FADE_OUT_SPEED)
        l = 0.0;
    else {
        l = 1.0f - l / FADE_OUT_SPEED;
        l = l*l * (3.0f - 2.0f * l);
    }
    l = MIN_LIGHT + l * (MAX_LIGHT - MIN_LIGHT);

    // inner cube rotation (random version)
    BeginCaptureMatrix();
    glRotatef(ft * 14.31f + 98.7f,  1.0f, 0.0f, 0.0f);
    glRotatef(ft * 16.21f + 43.2f,  0.0f, 1.0f, 0.0f);
    glRotatef(ft * 18.76f + 12.3f,  0.0f, 0.0f, 1.0f);
    EndCaptureMatrix(mat_random);

    // inner cube rotation (eye-following version)
    BeginCaptureMatrix();
    glRotatef(rot, 0.0f, 0.0f, 1.0f);
    glRotatef(el,  1.0f, 0.0f, 0.0f);    
    EndCaptureMatrix(mat_follow);

    // sub-scene switch
    if (ft > next_switch) {
        if (!*(++current_grid_func))
            current_grid_func = grid_func_list;
        next_switch += SWITCH_INTERVAL;
    }

    // grid update
    randSeed((unsigned long) (ft * 184723.4828f));
    UpdateGrid(*current_grid_func, 1.0f - (float) libc_exp(MOVE_SPEED * (last_ft - ft)));
    last_ft = ft;

    // camera and light setup
    glClear(GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(
        60.0, 0.0, 0.0,
        0.0, 0.0, 0.0,
        0.0, 0.0, 1.0
    );
    glRotatef(rot, 0.0f, 0.0f, 1.0f);
    glRotatef(el,  1.0f, 0.0f, 0.0f);    
    SetLightPos(GL_LIGHT0,  50.0,  50.0,  50.0, 1.0);
    SetLightPos(GL_LIGHT1, -50.0, -50.0, -50.0, 1.0);

    // background cube
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_BACK_FACE | GLS_ANTIALIAS);
    SetMaterial(1.0, 128);
    glColor3f(l * 0.5f, l * 0.4f, 0.0f);
    glPushMatrix();
    glScalef(60.0f, 60.0f, 60.0f);
    glCallList(listEnvCube);
    glPopMatrix();

    // "Gestell"
    SetMaterial(1.0, 128);
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_BACK_FACE | GLS_ANTIALIAS);
    glColor3ub(255, 215, 0);
    glUseProgram(phong_shader);  // same as above
    Gestell();
    //glUseProgram(0);  // same as below

    // back glass
    //SetMaterial(1.0, 128);  // same as above
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_BACK_FACE | GLS_ALPHA_BLEND | GLS_ANTIALIAS);
    glColor4f(1.0f, 1.0f, 1.0f, 0.25f);
    //glUseProgram(phong_shader);  // same as above
    glFrontFace(GL_CW);
    glBegin(GL_QUADS);
    DrawCube(0.0f, 0.0f, 0.0f, 0.5f * GRID_SIZE);
    glEnd();
    glFrontFace(GL_CCW);
    glUseProgram(0);

    // BEGIN inner cubes drawing
    glPushMatrix();
    glTranslatef(-0.5f * (GRID_SIZE - 1), -0.5f * (GRID_SIZE - 1), -0.5f * (GRID_SIZE - 1));
    glBindTexture(GL_TEXTURE_2D, texCubeSide);
    SetMaterial(0.0, 1);

    // solid cubes
    glsSet(GLS_LIGHTING | GLS_DEPTH_TEST | GLS_CULL_BACK_FACE | GLS_TEXTURE | GLS_ANTIALIAS);
    glColor3ub(255, 255, 255);
    glBegin(GL_QUADS);
    p = grid;
    for (i = GRID_SIZE * GRID_SIZE * GRID_SIZE;  i;  --i, ++p)
        if ((p->flags & (GP_VISIBLE | GP_FADING)) == GP_VISIBLE) {
            #define P2C(p) (0.5f + (0.5f / (GRID_SIZE - 1)) * (p))
            glColor4f(P2C(p->cx), P2C(p->cy), P2C(p->cz), 1.0f);
            DrawCube(p->cx, p->cy, p->cz, CUBE_SIZE);
        }
    glEnd();

    // fading cubes
    glsSet(GLS_LIGHTING | GLS_DEPTH_TEST | GLS_CULL_BACK_FACE | GLS_ALPHA_BLEND | GLS_TEXTURE | GLS_ANTIALIAS);
    glBegin(GL_QUADS);
    p = grid;
    for (i = GRID_SIZE * GRID_SIZE * GRID_SIZE;  i;  --i, ++p)
        if ((p->flags & (GP_VISIBLE | GP_FADING)) == (GP_VISIBLE | GP_FADING)) {
            glColor4f(P2C(p->cx), P2C(p->cy), P2C(p->cz), p->light);
            DrawCube(p->cx, p->cy, p->cz, CUBE_SIZE);
        }
    glEnd();
    
    // END inner cubes drawing
    glPopMatrix();

    // front glass
    SetMaterial(1.0, 128);
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_BACK_FACE | GLS_ALPHA_BLEND | GLS_ANTIALIAS);
    glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    glUseProgram(phong_shader);
    glBegin(GL_QUADS);
    DrawCube(0.0f, 0.0f, 0.0f, 0.5f * GRID_SIZE);
    glEnd();
    glUseProgram(0);
}


void Uninit_Cubes(void) {
    glDisable(GL_LIGHT1);
}


void Destroy_Cubes(void) {
}
