#include "ve2.h"
//#include "glext.h"
#include "imageloader.h"
#include "texgen.h"
#include "glow.h"
#include "spline.h"
//#include "mesh.h"
#include "vertexbuf.h"
#include "voxel.h"

#include "star.h"
#include "city.h"

static GLuint texWall, texRoof, texLights, texFlower;
static GLuint listBase, listTube;
static void *vboFlowers;

#define BUILDING_COUNT     6
#define BUILDING_SIZE      8
#define BUILDING_SPACING  10.0f
#define HALF_BLD_SIZE     (0.5f * BUILDING_SIZE)
#define ZSCALE            (-1.25f)
#define ROOF_STRIDE       (BUILDING_SIZE + 3)
#define BLAST_Z_SCALE     0.5f
#define BLAST_NOISE       0.25f
#define GRAVITY           9.81f
#define EXPLODE_FLASH_TIME 0.2f
#define CAMERA_SHAKE_TIME 2.0f
#define SHAKE_INTENSITY   0.2f
#define CANNON_SCALE      0.1f
#define CANNON_Z_OFFSET   (-15.0f * CANNON_SCALE)
#define EXTRA_Z_OFFSET    (CANNON_Z_OFFSET * 0.275f)
#define STAR_COUNT        5000

#define FLOWER_COUNT      2048
#define FLOWER_XY_SPEED   5.0f
#define FLOWER_Z_SPEED    10.0f
#define FLOWER_A_SPEED    1.0f
#define FLOWER_GRAVITY    2.0f
#define FLOWER_SCALE      0.5f

#define GUNNER_0_X        (3)
#define GUNNER_0_Y        (3)
#define GUNNER_1_X        (-1)
#define GUNNER_1_Y        (-2)

typedef struct _shot {
    int   bx, by;  // target building
    float fx, fy;  // fine-tuning
    float r;
} shot_t;

#define SHOT_COUNT 3
const static shot_t Shots[SHOT_COUNT] = {
    // target   fine        radius
    {  0, -2,  1.5f, 2.2f,  4.0f },
    {  2,  3,  1.0f,-1.0f,  4.8f },
    { -1, -1, -0.7f, 3.0f,  4.0f },
};

const static double StarPath[] = {
    0.5,  60.0,-10.0, -13.0,
    2.5,  10.0, 10.0, -20.0,
    4.0,  30.0, 25.0, -17.0,
    5.0,  39.3, 37.0, -15.5,
SPLINE_INIT_END };

////////////////////////////////////////////////////////////////////////////////

#define L_STATIC_WALLS  0
#define L_STATIC_ROOFS  1
#define L_BALL          2
#define L_SKY           3
#define L_STARS         4
#define EXTRA_LISTS     5
#define L_NORMAL_WALL   EXTRA_LISTS
#define L_NORMAL_ROOF  (EXTRA_LISTS + SHOT_COUNT)
#define L_BROKEN_WALL  (EXTRA_LISTS + 2 * SHOT_COUNT)
#define L_BROKEN_ROOF  (EXTRA_LISTS + 3 * SHOT_COUNT)
#define LIST_COUNT     (EXTRA_LISTS + 4 * SHOT_COUNT)
static GLuint lists;

typedef struct _building {
    float x0, y0;
    int height;
    unsigned int seed;
} building_t;

static unsigned int rand_val = 0;
static void rand_generate(void) { rand_val = randNext(); }

static int heights[(BUILDING_COUNT*2+1)][(BUILDING_COUNT*2+1)];
#define BuildingHeight(x,y) heights[(y)+BUILDING_COUNT][(x)+BUILDING_COUNT]

static spline_t *StarSpline;


static void PrepareBuilding(building_t *building, int x, int y) {
    building->seed = 0x13375EED ^ (x * 0x11111111) ^ (y * 0x04444444);
    building->height = BuildingHeight(x,y);
    building->x0 = x * BUILDING_SPACING;
    building->y0 = y * BUILDING_SPACING;
}


static void DrawNormalWall(float x0, float y0, float z0, float dx, float dy) {
    int i;
    float tco, x1, y1, z1 = z0 + ZSCALE;
    for (i = 0;  i < BUILDING_SIZE;  ++i) {
        x1 = x0 + dx;
        y1 = y0 + dy;
        if (i) rand_generate();
        tco = (rand_val & 0x4000) ? 0.5f : 0.0f;
        glTexCoord2f(tco,        1.0f);  glVertex3f(x0, y0, z0);
        glTexCoord2f(tco,        0.0f);  glVertex3f(x0, y0, z1);
        glTexCoord2f(tco + 0.5f, 0.0f);  glVertex3f(x1, y1, z1);
        glTexCoord2f(tco + 0.5f, 1.0f);  glVertex3f(x1, y1, z0);
        x0 = x1;
        y0 = y1;
    }
}


static void DrawUpperWall(float x0, float y0, float z0, float dx, float dy, const float *map, int stride) {
    int i;
    float x1, y1, t0;
    for (i = 0;  i < BUILDING_SIZE;  ++i) {
        x1 = x0 + dx;
        y1 = y0 + dy;
        t0 = 1.0f - (z0 - *map) / ZSCALE;
        glTexCoord2f(0.0f, 1.0f);  glVertex3f(x0, y0, z0);
        glTexCoord2f(0.0f, t0);    glVertex3f(x0, y0, *map);
        map += stride;
        t0 = 1.0f - (z0 - *map) / ZSCALE;
        glTexCoord2f(0.5f, t0);    glVertex3f(x1, y1, *map);
        glTexCoord2f(0.5f, 1.0f);  glVertex3f(x1, y1, z0);
        x0 = x1;
        y0 = y1;
    }
}


static void WallCheck(const float *map, int stride, int *maxh) {
    int i, h;
    for (i = 0;  i < BUILDING_SIZE;  ++i) {
        h = (int) (*map / ZSCALE + (1.0f / 128));
        if (h < *maxh)  *maxh = h;
        map += stride;
    }
}


static void DrawBuildingWalls(const building_t *building, const float *roof) {
    int h, maxh = building->height;
    float z;
    randSeed(building->seed);

    // measure roof base height
    if (roof) {
        WallCheck(&roof[ROOF_STRIDE + 1], 1, &maxh);
        WallCheck(&roof[ROOF_STRIDE + 1 + BUILDING_SIZE], ROOF_STRIDE, &maxh);
        WallCheck(&roof[ROOF_STRIDE * (BUILDING_SIZE + 1) + 1 + BUILDING_SIZE], -1, &maxh);
        WallCheck(&roof[ROOF_STRIDE * (BUILDING_SIZE + 1) + 1], -ROOF_STRIDE, &maxh);
    }

    // base part
    for (h = 0;  h < maxh;  ++h) {
        z = ZSCALE * h;
        rand_generate();
        glNormal3f(0.0f, -1.0f, 0.0f);
        DrawNormalWall(building->x0 - HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z, 1.0f, 0.0f);
        glNormal3f(1.0f, 0.0f, 0.0f);
        DrawNormalWall(building->x0 + HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z, 0.0f, 1.0f);
        glNormal3f(0.0f, 1.0f, 0.0f);
        DrawNormalWall(building->x0 + HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z, -1.0f, 0.0f);
        glNormal3f(-1.0f, 0.0f, 0.0f);
        DrawNormalWall(building->x0 - HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z, 0.0f, -1.0f);
    }

    // upper part
    if (roof) {
        z = ZSCALE * maxh;
        glNormal3f(0.0f, -1.0f, 0.0f);
        DrawUpperWall(building->x0 - HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z, 1.0f, 0.0f,
                      &roof[ROOF_STRIDE + 1], 1);
        glNormal3f(1.0f, 0.0f, 0.0f);
        DrawUpperWall(building->x0 + HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z, 0.0f, 1.0f,
                      &roof[ROOF_STRIDE + 1 + BUILDING_SIZE], ROOF_STRIDE);
        glNormal3f(0.0f, 1.0f, 0.0f);
        DrawUpperWall(building->x0 + HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z, -1.0f, 0.0f,
                      &roof[ROOF_STRIDE * (BUILDING_SIZE + 1) + 1 + BUILDING_SIZE], -1);
        glNormal3f(-1.0f, 0.0f, 0.0f);
        DrawUpperWall(building->x0 - HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z, 0.0f, -1.0f,
                      &roof[ROOF_STRIDE * (BUILDING_SIZE + 1) + 1], -ROOF_STRIDE);
    }
}


static void RoofVertex(float x, float y, const float *z) {
    float n, vx_x, vy_y;
    float vx_z = z[1] - z[-1];
    float vy_z = z[ROOF_STRIDE] - z[-ROOF_STRIDE];
    n = isqrtf(4.0f + vx_z);
    vx_x = 2.0f * n;
    vx_z *= n;
    n = isqrtf(4.0f + vy_z);
    vy_y = 2.0f * n;
    vy_z *= n;
    //  |vx_x|   |  0 |
    //  |  0 | X |vy_y|
    //  |vx_z|   |vy_z|
    glNormal3f(
         vx_z * vy_y,
         vx_x * vy_z,
        -vx_x * vy_y
    );
    glVertex3f(x, y, *z);
}


static void DrawBuildingRoof(const building_t *building, const float *roof) {
    if (!roof) {
        // static roof
        float z = building->height * ZSCALE;
        glNormal3f(0.0f, 0.0f, -1.0f);
        glTexCoord2f(-HALF_BLD_SIZE, +HALF_BLD_SIZE);
        glVertex3f(building->x0 - HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z);
        glTexCoord2f(+HALF_BLD_SIZE, +HALF_BLD_SIZE);
        glVertex3f(building->x0 + HALF_BLD_SIZE, building->y0 + HALF_BLD_SIZE, z);
        glTexCoord2f(+HALF_BLD_SIZE, -HALF_BLD_SIZE);
        glVertex3f(building->x0 + HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z);
        glTexCoord2f(-HALF_BLD_SIZE, -HALF_BLD_SIZE);
        glVertex3f(building->x0 - HALF_BLD_SIZE, building->y0 - HALF_BLD_SIZE, z);
    } else {
        // broken roof
        const float *mappos;
        int x, y;
        float x0, y0;
        for (y = 0;  y < BUILDING_SIZE;  ++y) {
            y0 = building->y0 - HALF_BLD_SIZE + y;
            mappos = &roof[(y + 1) * ROOF_STRIDE + 1];
            for (x = 0;  x < BUILDING_SIZE;  ++x) {
                x0 = building->x0 - HALF_BLD_SIZE + x;
                glTexCoord2f(0.0f, 0.0f); RoofVertex(x0,        y0,        &mappos[0]);
                glTexCoord2f(0.0f, 1.0f); RoofVertex(x0,        y0 + 1.0f, &mappos[ROOF_STRIDE]);
                glTexCoord2f(1.0f, 1.0f); RoofVertex(x0 + 1.0f, y0 + 1.0f, &mappos[1 + ROOF_STRIDE]);
                glTexCoord2f(1.0f, 0.0f); RoofVertex(x0 + 1.0f, y0,        &mappos[1]);
                ++mappos;
            }
        }
    }
}


static void GenerateRoofHeightMap(float *map, float cx, float cy, float z0, float r) {
    int x, y;
    float dx, dy, d;
    r *= r;
    for (y = -1;  y <= (BUILDING_SIZE + 1);  ++y)
        for (x = -1;  x <= (BUILDING_SIZE + 1);  ++x) {
            dx = cx - (x - HALF_BLD_SIZE);
            dy = cy - (y - HALF_BLD_SIZE);
            d = r - dx*dx - dy*dy;  // distance from epicentre
            if (d <= 0.0)
                *map++ = z0;
            else
                *map++ = z0 + BLAST_Z_SCALE * sqrtf(d)
                       + (float)randFloat() * BLAST_NOISE;
        }
}


static int IsStaticBuilding(int x, int y) {
    int i;
    for (i = 0;  i < SHOT_COUNT;  ++i)
        if ((x == Shots[i].bx) && (y == Shots[i].by))
            return 0;
    return 1;
}


////////////////////////////////////////////////////////////////////////////////


typedef struct _flower {
    float x0, y0, z0;
    float vx, vy, vz;
    float a0[4];
    float va[4];
} flower_t;
static flower_t Flowers[FLOWER_COUNT];



static void CreateNewFlowers(float cx, float cy, float cz, float r) {
    flower_t *f = Flowers;
    int i, j;
    randSeed(*(int*)(&cx) ^ *(int*)(&cy) ^ *(int*)(&cz) ^ *(int*)(&r));
    for (i = FLOWER_COUNT;  i;  --i, ++f) {
        f->x0 = cx + (float)randFloatAroundZero(1.0) * r;
        f->y0 = cy + (float)randFloatAroundZero(1.0) * r;
        f->z0 = cz + (float)randFloatAroundZero(1.0) * r * BLAST_Z_SCALE;
        f->vx = (float)randFloatAroundZero(FLOWER_XY_SPEED);
        f->vy = (float)randFloatAroundZero(FLOWER_XY_SPEED);
        f->vz = (float)randFloat() * (-FLOWER_Z_SPEED);
        for (j = 0;  j < 3;  ++j) {
            f->a0[j] = (float)randFloat() * 6.2f;
            f->va[j] = (float)randFloatAroundZero(FLOWER_A_SPEED);
        }
    }
}


static void DrawFlowers(float t, float *v) {
    flower_t *f = Flowers;
    int i;
    float a, b, c;
    float ax, ay, az;
    float bx, by, bz;
    float cx, cy, cz;
    for (i = FLOWER_COUNT;  i;  --i, ++f) {
        // compute center of flower
        cx = f->x0 + t *  f->vx;
        cy = f->y0 + t *  f->vy;
        cz = f->z0 + t * (f->vz + t * FLOWER_GRAVITY);
        
        // compute first "arm"
        a = f->a0[0] + t * f->va[0];
        b = f->a0[1] + t * f->va[1];
        c = cosf(a);
        ax = c * sinf(b);
        ay = c * cosf(b);
        az = sinf(a);

        // compute second "arm"
        a = f->a0[2] + t * f->va[2];
        b = f->a0[3] + t * f->va[3];
        c = cosf(a);
        bx = c * sinf(b);
        by = sinf(a);
        bz = c * cosf(b);

        // orthogonalize
        c = ax*bx + ay*by + az*bz;
        bx -= ax * c;
        by -= ay * c;
        bz -= az * c;

        // scale arms
        bx *= FLOWER_SCALE;
        by *= FLOWER_SCALE;
        bz *= FLOWER_SCALE;
        ax *= FLOWER_SCALE;
        ay *= FLOWER_SCALE;
        az *= FLOWER_SCALE;

        // create vertices
        v[ 0] = 0.0f;   v[ 1] = 0.0f;
        v[ 2] = cx+ax;  v[ 3] = cy+ay;  v[ 4] = cz+az;
        v[ 5] = 0.0f;   v[ 6] = 1.0f;
        v[ 7] = cx+bx;  v[ 8] = cy+by;  v[ 9] = cz+bz;
        v[10] = 1.0f;   v[11] = 1.0f;
        v[12] = cx-ax;  v[13] = cy-ay;  v[14] = cz-az;
        v[15] = 1.0f;   v[16] = 0.0f;
        v[17] = cx-bx;  v[18] = cy-by;  v[19] = cz-bz;
        v += 20;
    }
}


////////////////////////////////////////////////////////////////////////////////


#define RAD2DEG 57.2957795130823f

static int Camera = 0;
static double CameraStart = 0.0;
static int BrokenBuildings = 0;

static int Aiming = 0;
static float AimStart, AimStop;
static float StartAzimuth, EndAzimuth;
static float StartElevation, EndElevation;

static const int GunnerX[2] = { GUNNER_0_X, GUNNER_1_X };
static const int GunnerY[2] = { GUNNER_0_Y, GUNNER_1_Y };
static float Azimuth[2] = { 0.0f, 0.0f };
static float Elevation[2] = { 0.0f, 0.0f };

static int Firing = 0;
static float FireStart, FireStop;
static float FlightTime;

static int Exploding = 0;
static float ExplodeStart, ExplodeStop;
static float ExplodeTime;

static float OriginX, OriginY, OriginZ;
static float TargetX, TargetY, TargetZ;
static float  SpeedX,  SpeedY,  SpeedZ;
static float   DistX,   DistY,   DistZ;


void City_Aim(float AimTime, float _in_FlightTime, float _in_ExplodeTime) {
    const shot_t *s = &Shots[BrokenBuildings];
    int g = BrokenBuildings & 1;
    Firing = 0;
    if (BrokenBuildings >= SHOT_COUNT) return;
    Aiming = 1;
    AimStart = (float) t_global;
    AimStop = AimStart + AimTime;
    FlightTime = _in_FlightTime;
    ExplodeTime = _in_ExplodeTime;
    OriginX = GunnerX[g] * BUILDING_SPACING;
    OriginY = GunnerY[g] * BUILDING_SPACING;
    OriginZ = BuildingHeight(GunnerX[g], GunnerY[g]) * ZSCALE + CANNON_Z_OFFSET + EXTRA_Z_OFFSET;
    TargetX = s->bx * BUILDING_SPACING + s->fx;
    TargetY = s->by * BUILDING_SPACING + s->fy;
    TargetZ = BuildingHeight(s->bx, s->by) * ZSCALE;
    DistX = TargetX - OriginX;
    DistY = TargetY - OriginY;
    DistZ = TargetZ - OriginZ;
    SpeedX = DistX / FlightTime;
    SpeedY = DistY / FlightTime;
    SpeedZ = DistZ / FlightTime - GRAVITY * FlightTime;
    StartAzimuth = Azimuth[g];
    EndAzimuth = RAD2DEG * atan2f(DistX, DistY);
    StartElevation = Elevation[g];
    EndElevation = RAD2DEG * atanf(-SpeedZ * FlightTime / sqrtf(DistX*DistX + DistY*DistY));
}

void City_Fire(void) {
    Aiming = 0;
    Firing = 1;
    FireStart = (float) t_global;
    FireStop = FireStart + FlightTime;
}

void City_SetCamera(int cam) {
    Camera = cam;
    CameraStart = t_global;
}


static void UpdateState(void) {
    float t = (float) t_global;
    if (Aiming) {
        int g = BrokenBuildings & 1;
        if (t >= AimStop) {
            Aiming = 0;
            Azimuth[g] = EndAzimuth;
            Elevation[g] = EndElevation;
        } else {
            float f1 = (t - AimStart) / (AimStop - AimStart);
            float f0 = 1.0f - f1;
            Azimuth[g] = f0 * StartAzimuth + f1 * EndAzimuth;
            Elevation[g] = f0 * StartElevation + f1 * EndElevation;
        }
    }
    if (Exploding && (t >= ExplodeStop))
        Exploding = 0;
    if (Firing && (t >= FireStop)) {
        Firing = 0;
        Exploding = 1;
        ExplodeStart = t;
        ExplodeStop = t + ExplodeTime;
        CreateNewFlowers(TargetX, TargetY, TargetZ, Shots[BrokenBuildings].r);
        ++BrokenBuildings;
    }
}


static float GetFlashIntensity(int condition, float base, float duration) {
    float t = (float) t_global;
    if (condition) {
        t -= base;
        if (t >= duration)
            return 0.0f;
        return 1.0f - t / duration;
    } else
        return 0.0f;
}


static void GetProjectilePosition(float t, float *x, float *y, float *z) {
    if (t > FireStop)  t = FireStop;
    t -= FireStart;
    *x = OriginX + t *  SpeedX;
    *y = OriginY + t *  SpeedY;
    *z = OriginZ + t * (SpeedZ + t * GRAVITY);
}


////////////////////////////////////////////////////////////////////////////////


static void SetCamera(int cam, double t) {
    float ax, ay, az;
    float bx, by, bz;
//cam=CITYCAM_OVERVIEW;
//cam=CITYCAM_WATCH_GUNNER_0;
//cam=CITYCAM_FLYBY;
    switch (cam) {

case CITYCAM_OVERVIEW:
    gluLookAt(
        0.0, -100.0, -150.0,
        0.0, 0.0, 0.0,
        0.0, 1.0, 0.0
    );
break;

case CITYCAM_FLYBY:
    gluLookAt(
        80.0 * sin(0.2 * t + 1.2),
        80.0 * cos(0.2 * t + 1.2),
        -22.0,
        0.0, 0.0, 0.0,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_BULLET_RIDE:
    GetProjectilePosition((float)t_global - 0.2f, &ax, &ay, &az);
    GetProjectilePosition((float)t_global + 0.2f, &bx, &by, &bz);
    gluLookAt(
        ax, ay, az - 1.0,
        bx, by, bz,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_WATCH_GUNNER_0:
    gluLookAt(
        20.0, 20.0 + t, -18.0,
        30.0, 30.0, -13.0,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_FOLLOW_PROJECTILE:
    GetProjectilePosition((float)t_global, &ax, &ay, &az);
    gluLookAt(
        -10.0 + t, 15.0 - t, -13.0,
        ax, ay, az + 5.0,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_APPROACH_GUNNER_1:
    gluLookAt(
        -16.0, -12.0 + 2.0 * t, -18.0,
        -10.0, -20.0, -14.0,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_FLYBY_2:
    gluLookAt(
        10.0 + 3.0*t,  0.0, -17.0,
        20.0, 30.0, -14.0,
        0.0, 0.0, -1.0
    );
break;

case CITYCAM_ROTATE_GUNNER_0:
    gluLookAt(
        32.0 - t,  28.0 + t,  t - 40.0,
        30.0, 30.0, -13.0,
        sin(t), cos(t), 0.0
    );
break;

case CITYCAM_AWAITING_BLAST:
    gluLookAt(
        -15.5, 5.0 - 0.5 * t, -8.0,
        -10.0, -10.0, -10.0,
        0.0, 0.0, -1.0
    );
break;

    }
}


////////////////////////////////////////////////////////////////////////////////


static void GenerateRoofTexture(void) {
  TGimage *A;
  A = tgCreate(256,256, TG_FORMAT_GRAY | TG_FORMAT_WRAP);
  tgFill(A, 0xFF969696);
  tgNoise(A, 16384);
  tgBlurHEx(A, 5, 1.00);
  tgBlurVEx(A, 5, 1.00);
  tgEmboss(A, -1.44);
  tgNoise(A, 2048);
  tgEmboss(A, 0.00);
  texRoof = tgCompileGL(A, 1);
  tgFree(A);
}


static void DrawSky(void) {
    int i;
    glBegin(GL_QUAD_STRIP);
    for (i = 0;  i <= 64;  ++i) {
        float y = i * 0.098174770424681f;
        float x = 160.0f * sinf(y);
              y = 160.0f * cosf(y);
        glColor3ub(20, 40, 80);
        glVertex3f(x, y, 50.0f);
        glColor3ub(0, 0, 0);
        glVertex3f(x, y, -200.0f);
    }
    glEnd();
}


static void DrawStars(int count, float size) {
    unsigned char c;
    float x, y, z, n;
    glPointSize(size);
    glBegin(GL_POINTS);
    for (;  count;  --count) {
        x = (float) randFloatAroundZero(1.0);
        y = (float) randFloatAroundZero(1.0);
        z = (float) randFloat();
        n = isqrtf(x*x + y*y + z*z);
        c = (unsigned char) (randNext() >> 13);
        glColor3ub(c, c, c);
        glVertex3f(
            160.0f * n * x,
            160.0f * n * y,
           -160.0f * n * z
        );
    }
    glEnd();
}


////////////////////////////////////////////////////////////////////////////////


void Prepare_City(void) {
    int i, x, y;
    static const shot_t *s;
    building_t building;
    static float roofmap[ROOF_STRIDE * ROOF_STRIDE];
    float mat[16];
    
    // prepare assets and stuff
    lists = glGenLists(LIST_COUNT);
    GenerateRoofTexture();
    texWall = LoadTexture(GL_TEXTURE_2D, "house_wall.jpg", GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT);
    texLights = LoadTexture(GL_TEXTURE_2D, "house_lights.png", GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT);
    texFlower = LoadTexture(GL_TEXTURE_2D, "bluemchen.png", GL_LINEAR, GL_REPEAT);
    StarSpline = spline_create(3, StarPath);

    // create flower VBO
    vboFlowers = vbCreate(
        GL_T2F_V3F, GL_STREAM_DRAW, FLOWER_COUNT * 4, 0,
        0, 0, 0  // no index stuff necessary
    );

    // load voxels
    BeginCaptureMatrix();
        glRotatef(90.0f, 0.0f, 0.0f, 1.0f);
        glRotatef(90.0f, -1.0f, 0.0f, 0.0f);
        glScalef(CANNON_SCALE, CANNON_SCALE, CANNON_SCALE);
        glTranslatef(5.5f, 1.5f, -1.0f);
    EndCaptureMatrix(mat);
    listBase = CompileVoxelFromFile("cannon_base.vxl", mat);
    listTube = CompileVoxelFromFile("cannon_tube.vxl", mat);

    // create cannon ball
    glNewList(lists + L_BALL, GL_COMPILE);
    gluQuadricNormals(quad, GLU_SMOOTH);
    gluSphere(quad, 0.5, 128, 64);
    glEndList();
    UpdateProgress();

    // create sky
    glNewList(lists + L_SKY, GL_COMPILE);
    DrawSky();
    glEndList();
    UpdateProgress();

    // create stars
    glNewList(lists + L_STARS, GL_COMPILE);
    DrawStars(STAR_COUNT/4, 1.0f);
    DrawStars(STAR_COUNT/4, 1.3f);
    DrawStars(STAR_COUNT/4, 1.7f);
    DrawStars(STAR_COUNT/4, 2.0f);
    glEndList();
    UpdateProgress();

    // determine building heights
    randSeed(682347623);
    for (y = -BUILDING_COUNT;  y <= BUILDING_COUNT;  ++y)
        for (x = -BUILDING_COUNT;  x <= BUILDING_COUNT;  ++x) {
            double hscale = 5.0 + sqrt(x*x + y*y);
            BuildingHeight(x,y) = (int) (5.0 + hscale * randFloat());
        }
    
    // create static wall list
    glNewList(lists + L_STATIC_WALLS, GL_COMPILE); glBegin(GL_QUADS);
    for (y = -BUILDING_COUNT;  y <= BUILDING_COUNT;  ++y)
        for (x = -BUILDING_COUNT;  x <= BUILDING_COUNT;  ++x) {
            if (!IsStaticBuilding(x, y)) continue;
            PrepareBuilding(&building, x, y);
            DrawBuildingWalls(&building, NULL);
        }
    glEnd(); glEndList();
    UpdateProgress();

    // create static roof list
    glNewList(lists + L_STATIC_ROOFS, GL_COMPILE); glBegin(GL_QUADS);
    for (y = -BUILDING_COUNT;  y <= BUILDING_COUNT;  ++y)
        for (x = -BUILDING_COUNT;  x <= BUILDING_COUNT;  ++x) {
            if (!IsStaticBuilding(x, y)) continue;
            PrepareBuilding(&building, x, y);
            DrawBuildingRoof(&building, NULL);
        }
    glEnd(); glEndList();
    UpdateProgress();
    
    // create extra buildings
    s = Shots;
    for (i = 0;  i < SHOT_COUNT;  ++i, ++s) {
        x = s->bx;
        y = s->by;
        PrepareBuilding(&building, x, y);
        GenerateRoofHeightMap(roofmap,
            s->fx, s->fy,
            ZSCALE * BuildingHeight(x, y),
            s->r
        );
        #define PREPARE_LIST(lstoffset, func, map) \
            glNewList(lists + lstoffset + i, GL_COMPILE); glBegin(GL_QUADS); \
            DrawBuilding##func(&building, map); \
            glEnd(); glEndList();
        PREPARE_LIST(L_NORMAL_WALL, Walls, NULL);
        PREPARE_LIST(L_NORMAL_ROOF, Roof,  NULL);
        PREPARE_LIST(L_BROKEN_WALL, Walls, roofmap);
        PREPARE_LIST(L_BROKEN_ROOF, Roof,  roofmap);
        UpdateProgress();
    }
}


void Init_City(void) {
    SetPerspective(45.0, 0.1, 500.0);
    SetAmbientLight(0.2f);
    glEnable(GL_LIGHT0);
    SetLight(GL_LIGHT0, 1.0f, 1.0f);
    glEnable(GL_POINT_SMOOTH);
}


void Render_City(void) {
    int i;
    float flash;
    UpdateState();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    flash = GetFlashIntensity(Exploding, ExplodeStart, CAMERA_SHAKE_TIME);
    if (flash > 0.0f) {
        flash *= flash * SHAKE_INTENSITY;
        glTranslatef(
            flash * sinf(52.8f * ft + 2.57f),
            flash * sinf(49.3f * ft + 4.28f),
            flash * sinf(50.0f * ft + 0.42f)
        );
    }
    
    // standard camera setup
    SetCamera(Camera, t_global - CameraStart);
    SetLightPos(GL_LIGHT0, -1.0f, -1.0f, -1.0f, 0.0f);
    SetMaterial(0.0f, 128);

    // bright pass
    BeginBrightPass();
    glsSet(GLS_TEXTURE | GLS_DEPTH_TEST | GLS_CULL_FACE);
    glColor3ub(255, 255, 192);
    glBindTexture(GL_TEXTURE_2D, texLights);
    glCallList(lists + L_STATIC_WALLS);
    for (i = 0;  i < SHOT_COUNT;  ++i)
        glCallList(lists + ((i < BrokenBuildings) ? L_BROKEN_WALL : L_NORMAL_WALL) + i);
    glsUnset(GLS_TEXTURE);
    glColor3ub(0, 0, 0);
    glCallList(lists + L_STATIC_ROOFS);
    for (i = 0;  i < SHOT_COUNT;  ++i)
        glCallList(lists + ((i < BrokenBuildings) ? L_BROKEN_ROOF : L_NORMAL_ROOF) + i);
    EndBrightPass(5, 2);
    glClear(GL_DEPTH_BUFFER_BIT);

    // sky
    glsSet(0);
    glCallList(lists + L_SKY);
    glsSet(GLS_ADDITIVE_BLEND);
    glCallList(lists + L_STARS);

    // background
    glsSet(GLS_DEPTH_TEST);
    glColor3ub(32, 32, 32);
    ScreenQuad(-1000.0, -1000.0, 1000.0, 1000.0);

    // houses
    glsSet(GLS_TEXTURE | GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_FACE);
    glColor3ub(255, 255, 255);
    glBindTexture(GL_TEXTURE_2D, texWall);
    glCallList(lists + L_STATIC_WALLS);
    for (i = 0;  i < SHOT_COUNT;  ++i)
        glCallList(lists + ((i < BrokenBuildings) ? L_BROKEN_WALL : L_NORMAL_WALL) + i);
    glBindTexture(GL_TEXTURE_2D, texRoof);
    glCallList(lists + L_STATIC_ROOFS);
    for (i = 0;  i < SHOT_COUNT;  ++i)
        glCallList(lists + ((i < BrokenBuildings) ? L_BROKEN_ROOF : L_NORMAL_ROOF) + i);

    // glow    
    DrawBrightPass();

    // cannons'n'stuff
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING | GLS_CULL_FACE);
    SetMaterial(0.2f, 96);
    for (i = 0;  i < 2;  ++i) {
        glPushMatrix();
        glTranslatef(
            GunnerX[i] * BUILDING_SPACING,
            GunnerY[i] * BUILDING_SPACING,
            BuildingHeight(GunnerX[i], GunnerY[i]) * ZSCALE + CANNON_Z_OFFSET
        );
        glRotatef(Azimuth[i], 0.0f, 0.0f, -1.0f);
        glCallList(listBase);
        glRotatef(Elevation[i], -1.0f, 0.0f, 0.0f);
        glCallList(listTube);
        glPopMatrix();
    }

    // explosion flowers
    if (Exploding) {
        glsSet(GLS_DEPTH_TEST | GLS_TEXTURE | GLS_ALPHA_BLEND);
        glBindTexture(GL_TEXTURE_2D, texFlower);
        glColor4ub(255, 255, 255, 255);
        DrawFlowers((float) t_global - ExplodeStart,
                    vbMapVertices(vboFlowers, VB_MAP_WRITE | VB_MAP_INVALIDATE));
        vbUnmapVertices();
        vbDrawAll(vboFlowers, GL_QUADS);
    }

    // trajectory
    glsSet(GLS_DEPTH_TEST | GLS_LIGHTING);
    SetMaterial(1.0f, 128);
    if (Firing) {
        float x, y, z;
        GetProjectilePosition((float) t_global, &x, &y, &z);
        glPushMatrix();
        glTranslatef(x, y, z);
        glColor3ub(64, 64, 64);
        glCallList(lists + L_BALL);
        glPopMatrix();
    }
    
    // star
    glsSet(GLS_LIGHTING | GLS_TEXTURE | GLS_CULL_FACE | GLS_DEPTH_TEST);
    if (t < 5.0)
        DrawStarOnPath(t, 0.5, StarSpline);
    
    // light flash
    flash = GetFlashIntensity(Exploding, ExplodeStart, EXPLODE_FLASH_TIME);
    if (flash > 0.0f) {
        glsSet(GLS_OVERLAY | GLS_ALPHA_BLEND);
        glColor4f(1.0f, 1.0f, 1.0f, flash * flash);
        ScreenQuad(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
        glsUnset(GLS_OVERLAY);
    }
}


void Uninit_City(void) {
}


void Destroy_City(void) {
}
