#include "mathpi.h"
#include "sheep2.h"



#define MIN_Z 0.01
#define VIEW_ASPECT (4.0 / 3.0)



static BITMAP *bmp;
static STATE *state;
static int xi; /* Tile with camera over it */
static int x, y; /* For traversing the tile map */
static float xc, yc, zc; /* Camera position as floats */
static float xr, yr; /* Player's right vector (zr=0) */
static float xd, yd, zd; /* Player's down vector */
static MATRIX_f camera;

static V3D_f vtx[8];
static const V3D_f *vtxp[] = {&vtx[0], &vtx[1], &vtx[2], &vtx[3],
                              &vtx[4], &vtx[5], &vtx[6], &vtx[7]};

static V3D_f vtc[8+5]; /* Five clipping planes */
static V3D_f *vtcp[] = {&vtc[0], &vtc[1], &vtc[2], &vtc[3],
                        &vtc[4], &vtc[5], &vtc[6], &vtc[7],
                        &vtc[8], &vtc[9], &vtc[10], &vtc[11], &vtc[12]};

static V3D_f vtt[8+5]; /* Five clipping planes */
static V3D_f *vttp[] = {&vtt[0], &vtt[1], &vtt[2], &vtt[3],
                        &vtt[4], &vtt[5], &vtt[6], &vtt[7],
                        &vtt[8], &vtt[9], &vtt[10], &vtt[11], &vtt[12]};

static int out[8+5];



typedef struct DSPRITE DSPRITE;
typedef struct DSPRITEDATA DSPRITEDATA;

struct DSPRITE
{
	DSPRITE *next;
	DSPRITEDATA *d;
};

struct DSPRITEDATA
{
	DSPRITEDATA *next;
	V3D_f vtx[4];
	BITMAP *texture;
};



/* Sprites are created once, in this list. */
static DSPRITEDATA *spritedata = NULL;

/* They are listed multiply in these lists. Each time they are drawn, they
 * will be clipped differently.
 */
static DSPRITE ***spriteinside = NULL;
static DSPRITE *spriteoutside[4] = {NULL, NULL, NULL, NULL};



static void link_sprite(DSPRITEDATA *d, DSPRITE **sprite)
{
	DSPRITE *newsprite;
	if (*sprite && (*sprite)->d == d) return;
	newsprite = malloc(sizeof(*newsprite));
	if (!newsprite) return;
	newsprite->next = *sprite;
	*sprite = newsprite;
	newsprite->d = d;
}



static void link_sprite_at(DSPRITEDATA *d, int xi, int yi)
{
	if (yi < 0)
		link_sprite(d, &spriteoutside[DIR_AWAY]);
	else if (yi >= state->l->h)
		link_sprite(d, &spriteoutside[DIR_TOWARDS]);
	else if (xi < 0)
		link_sprite(d, &spriteoutside[DIR_LEFT]);
	else if (xi >= state->l->w)
		link_sprite(d, &spriteoutside[DIR_RIGHT]);
	else
		link_sprite(d, &spriteinside[yi][xi]);
}



static void add_sprite(float x, float y, float z, float xrad, float yrad, BITMAP *texture)
{
	DSPRITEDATA *d = malloc(sizeof(*d));
	int xi, yi;
	int lastxi, lastyi;
	float xf, yf;
	float lastxf, lastyf;
	int i;
	if (!d) return;
	if (!spriteinside) return;
	d->next = spritedata;
	spritedata = d;
	d->vtx[0].x = x-xr*xrad-xd*yrad; d->vtx[0].y = y-yr*xrad-yd*yrad; d->vtx[0].z = z-zd*yrad;
	d->vtx[1].x = x-xr*xrad+xd*yrad; d->vtx[1].y = y-yr*xrad+yd*yrad; d->vtx[1].z = z+zd*yrad;
	d->vtx[2].x = x+xr*xrad+xd*yrad; d->vtx[2].y = y+yr*xrad+yd*yrad; d->vtx[2].z = z+zd*yrad;
	d->vtx[3].x = x+xr*xrad-xd*yrad; d->vtx[3].y = y+yr*xrad-yd*yrad; d->vtx[3].z = z-zd*yrad;
	d->vtx[0].u =   0.01f; d->vtx[0].v =   0.01f;
	d->vtx[1].u =   0.01f; d->vtx[1].v = 255.99f;
	d->vtx[2].u = 255.99f; d->vtx[2].v = 255.99f;
	d->vtx[3].u = 255.99f; d->vtx[3].v =   0.01f;
	d->texture = texture;

	xf = d->vtx[3].x;
	yf = d->vtx[3].y;
	xi = (int)floor(xf);
	yi = (int)floor(yf);

	for (i = 0; i < 4; i++) {
		lastxf = xf; lastyf = yf;
		lastxi = xi; lastyi = yi;
		xf = d->vtx[i].x;
		yf = d->vtx[i].y;
		xi = (int)floor(xf);
		yi = (int)floor(yf);

		link_sprite_at(d, xi, yi);

		if (xi != lastxi && yi != lastyi) {
			int xmin, xmax, ymin, ymax;
			float x0, y0;
			float y_at_x0;
			if (xi > lastxi) xmax = xi, xmin = lastxi;
			            else xmin = xi, xmax = lastxi;
			if (yi > lastyi) ymax = yi, ymin = lastyi;
			            else ymin = yi, ymax = lastyi;
			x0 = xmax; y0 = ymax;
			y_at_x0 = lastyf + (yf - lastyf) * (x0 - lastxf) / (xf - lastxf) - y0;
			if (y_at_x0 > 0)
				link_sprite_at(d, ymax == yi ? lastxi : xi, ymax);
			else
				link_sprite_at(d, ymin == yi ? lastxi : xi, ymin);
		}
	}
}



static void free_sprite_list(DSPRITE *sprite)
{
	while (sprite) {
		DSPRITE *next = sprite->next;
		free(sprite);
		sprite = next;
	}
}



static void destroy_sprite_array(void)
{
	if (spriteinside) {
		int x, y;
		for (y = 0; y < state->l->h; y++) {
			if (spriteinside[y]) {
				for (x = 0; x < state->l->w; x++)
					free_sprite_list(spriteinside[y][x]);
				free(spriteinside[y]);
			}
		}
		free(spriteinside);
		spriteinside = NULL;
	}
	{
		int i;
		for (i = 0; i < 4; i++) {
			free_sprite_list(spriteoutside[i]);
			spriteoutside[i] = NULL;
		}
	}
	while (spritedata) {
		DSPRITEDATA *next = spritedata->next;
		free(spritedata);
		spritedata = next;
	}
}



static void create_sprite_array(void)
{
	int x, y;
	spriteinside = malloc(state->l->h*sizeof(*spriteinside));
	if (!spriteinside) return;
	for (y = 0; y < state->l->h; y++) spriteinside[y] = NULL;
	for (y = 0; y < state->l->h; y++) {
		spriteinside[y] = malloc(state->l->w*sizeof(*spriteinside[y]));
		if (!spriteinside[y]) goto error;
		for (x = 0; x < state->l->w; x++) spriteinside[y][x] = NULL;
	}
	return;

	error:
	destroy_sprite_array();
}



struct BLOWERLIST
{
	BLOWERLIST *next;
	BLOWER *blower;
};



static void free_blower_list(BLOWERLIST *blower)
{
	while (blower) {
		BLOWERLIST *next = blower->next;
		free(blower);
		blower = next;
	}
}



void destroy_blower_array(STATE *state)
{
	if (state->blowerarray) {
		int x, y;
		for (y = 0; y < state->l->h; y++) {
			if (state->blowerarray[y]) {
				for (x = 0; x < state->l->w; x++)
					free_blower_list(state->blowerarray[y][x]);
				free(state->blowerarray[y]);
			}
		}
		free(state->blowerarray);
		state->blowerarray = NULL;
	}
}



void create_blower_array(STATE *state)
{
	int x, y;
	state->blowerarray = malloc(state->l->h*sizeof(*state->blowerarray));
	if (!state->blowerarray) return;
	for (y = 0; y < state->l->h; y++) state->blowerarray[y] = NULL;
	for (y = 0; y < state->l->h; y++) {
		state->blowerarray[y] = malloc(state->l->w*sizeof(*state->blowerarray[y]));
		if (!state->blowerarray[y]) goto error;
		for (x = 0; x < state->l->w; x++) state->blowerarray[y][x] = NULL;
	}
	for (y = 0; y < state->l->n_blowers; y++) {
		BLOWERLIST *bl = malloc(sizeof(*bl));
		if (!bl) return;
		bl->blower = &state->l->blower[y];
		bl->next = state->blowerarray[bl->blower->y][bl->blower->x];
		state->blowerarray[bl->blower->y][bl->blower->x] = bl;
	}
	return;

	error:
	destroy_blower_array(state);
}



static int anticlockwise(void)
{
	float xn, yn, zn;

	cross_product_f(
		vtx[1].x - vtx[0].x, vtx[1].y - vtx[0].y, vtx[1].z - vtx[0].z,
		vtx[2].x - vtx[0].x, vtx[2].y - vtx[0].y, vtx[2].z - vtx[0].z,
		&xn, &yn, &zn);

	return xn*vtx[0].x + yn*vtx[0].y + zn*vtx[0].z <= 0;
}



static void render_polygon(int vc, const V3D_f *vtxp[], int polytype, BITMAP *texture)
{
	vc = clip3d_f(polytype, MIN_Z, 0.0f, vc, vtxp, vtcp, vttp, out);

	if (vc) {
		int i;

		for (i = 0; i < vc; i++)
			persp_project_f(vtc[i].x, vtc[i].y, vtc[i].z, &vtc[i].x, &vtc[i].y);

		polygon3d_f(bmp, polytype, texture, vc, vtcp);
	}
}



static void draw_water(void)
{
	float extent = MIN(zc, 8.0f) * 64.0f;

	apply_matrix_f(&camera,             - extent,             - extent, 0, &vtx[0].x, &vtx[0].y, &vtx[0].z);
	apply_matrix_f(&camera,             - extent, state->l->h + extent, 0, &vtx[1].x, &vtx[1].y, &vtx[1].z);
	apply_matrix_f(&camera, state->l->w + extent, state->l->h + extent, 0, &vtx[2].x, &vtx[2].y, &vtx[2].z);
	apply_matrix_f(&camera, state->l->w + extent,             - extent, 0, &vtx[3].x, &vtx[3].y, &vtx[3].z);

	vtx[0].u = (            - extent) * 64.0f; vtx[0].v = (            - extent) * 64.0f + state->watershift;
	vtx[1].u = (            - extent) * 64.0f; vtx[1].v = (state->l->h + extent) * 64.0f + state->watershift;
	vtx[2].u = (state->l->w + extent) * 64.0f; vtx[2].v = (state->l->h + extent) * 64.0f + state->watershift;
	vtx[3].u = (state->l->w + extent) * 64.0f; vtx[3].v = (            - extent) * 64.0f + state->watershift;

	render_polygon(4, vtxp, POLYTYPE_PTEX, dat[GFX_WATER].dat);
}



#define MAKE_CLIPPER(dir, OP, p, q) \
static int clip_##dir##_##p(V3D_f src[], float p, V3D_f dst[], int vc, int out[]) \
{ \
	int i, lasti, j; \
	for (i = 0; i < vc; i++) out[i] = src[i].p OP p; \
 \
	j = 0; \
 \
	for (lasti = vc-1, i = 0; i < vc; lasti = i++) { \
		if (out[lasti] != out[i]) { \
			float t = (p - src[lasti].p) / (src[i].p - src[lasti].p); \
			dst[j].p = p; \
			dst[j].q = src[lasti].q + (src[i].q - src[lasti].q) * t; \
			dst[j].z = src[lasti].z + (src[i].z - src[lasti].z) * t; \
			dst[j].u = src[lasti].u + (src[i].u - src[lasti].u) * t; \
			dst[j].v = src[lasti].v + (src[i].v - src[lasti].v) * t; \
			j++; \
		} \
		if (!out[i]) dst[j++] = src[i]; \
	} \
 \
	return j; \
}

MAKE_CLIPPER(low , <, x, y)
MAKE_CLIPPER(high, >, x, y)
MAKE_CLIPPER(low , <, y, x)
MAKE_CLIPPER(high, >, y, x)

#undef MAKE_CLIPPER



static int sprite_clip_away(DSPRITE *sprite)
{
	return clip_high_y(sprite->d->vtx, 0, vtx, 4, out);
}



static int sprite_clip_towards(DSPRITE *sprite)
{
	return clip_low_y(sprite->d->vtx, state->l->h, vtx, 4, out);
}



static int sprite_clip_left(DSPRITE *sprite)
{
	int vc = clip_high_x(sprite->d->vtx, 0, vtx, 4, out);
	vc = clip_low_y(vtx, 0, vtt, vc, out);
	return clip_high_y(vtt, state->l->h, vtx, vc, out);
}



static int sprite_clip_right(DSPRITE *sprite)
{
	int vc = clip_low_x(sprite->d->vtx, state->l->w, vtx, 4, out);
	vc = clip_low_y(vtx, 0, vtt, vc, out);
	return clip_high_y(vtt, state->l->h, vtx, vc, out);
}



static int sprite_clip_tile(DSPRITE *sprite)
{
	int vc = clip_low_x(sprite->d->vtx, x, vtt, 4, out);
	vc = clip_high_x(vtt, x+1, vtx, vc, out);
	vc = clip_low_y(vtx, y, vtt, vc, out);
	return clip_high_y(vtt, y+1, vtx, vc, out);
}



static void draw_sprites(DSPRITE **spritep, int (*clip)(DSPRITE *))
{
	/* Sort the sprites by z */
	DSPRITE *zfirst = NULL;
	DSPRITE **zspritep;

	while (*spritep) {
		DSPRITE *transsprite = *spritep;
		float nz = transsprite->d->vtx[0].x*camera.v[2][0] +
		           transsprite->d->vtx[0].y*camera.v[2][1] +
		           transsprite->d->vtx[0].z*camera.v[2][2];
		*spritep = transsprite->next;

		zspritep = &zfirst;

		while (*zspritep) {
			float z = (*zspritep)->d->vtx[0].x*camera.v[2][0] +
			          (*zspritep)->d->vtx[0].y*camera.v[2][1] +
			          (*zspritep)->d->vtx[0].z*camera.v[2][2];

			if (z < nz) break;

			zspritep = &(*zspritep)->next;
		}

		transsprite->next = *zspritep;
		*zspritep = transsprite;
	}

	*spritep = zfirst;

	while (zfirst) {
		int vc = (*clip)(zfirst);
		if (vc) {
			int i;
			for (i = 0; i < vc; i++)
				apply_matrix_f(&camera, vtx[i].x, vtx[i].y, vtx[i].z, &vtx[i].x, &vtx[i].y, &vtx[i].z);
			render_polygon(vc, vtxp, POLYTYPE_ATEX_MASK, zfirst->d->texture);
		}
		zfirst = zfirst->next;
	}
}



static void draw_base(void)
{
	/* Draw the base of the level */
	if (state->l->hole[y][x]) {
		apply_matrix_f(&camera, x  , y  , 0, &vtx[0].x, &vtx[0].y, &vtx[0].z);
		apply_matrix_f(&camera, x  , y+1, 0, &vtx[1].x, &vtx[1].y, &vtx[1].z);
		apply_matrix_f(&camera, x+1, y+1, 0, &vtx[2].x, &vtx[2].y, &vtx[2].z);
		apply_matrix_f(&camera, x+1, y  , 0, &vtx[3].x, &vtx[3].y, &vtx[3].z);
		render_polygon(4, vtxp, POLYTYPE_PTEX, dat[GFX_GRASS].dat);
	}
}



static void draw_lower_tile(BLOCK *block)
{
	if (block->nextdown) {
		int recursefirst = state->pz >= block->z;
		if (recursefirst) draw_lower_tile(block->nextdown);

		if (block->z >= block->nextdown->z + BLOCKS_SEPARATED) {
			/* Draw the bottom */
			if (block->z >= zc) {
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
			}
			block = block->nextdown;
			/* Draw the top of the next block down */
			if (block->z+1 <= zc) {
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
			}
		} else
			block = block->nextdown;

		if (!recursefirst) draw_lower_tile(block);
	} else {
		/* Draw the bottom */
		if (block->z >= zc) {
			apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[0].x, &vtx[0].y, &vtx[0].z);
			apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
			apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
			apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[3].x, &vtx[3].y, &vtx[3].z);
			render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
		}
		draw_base();
	}
}



static void draw_tile(void)
{
	BLOCK *block = state->block[y][x];

	if (block) {
		int recursefirst = state->pz >= block->z;
		if (!recursefirst && spriteinside)
			draw_sprites(&spriteinside[y][x], &sprite_clip_tile);
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;
		if (recursefirst)
			draw_lower_tile(block);

		switch (block->type) {
			case BLOCK_SLOPE(DIR_AWAY):
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[3].x, &vtx[3].y, &vtx[3].z);
				break;
			case BLOCK_SLOPE(DIR_RIGHT):
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[3].x, &vtx[3].y, &vtx[3].z);
				break;
			case BLOCK_SLOPE(DIR_TOWARDS):
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				break;
			case BLOCK_SLOPE(DIR_LEFT):
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				break;
			default:
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				break;
		}
		if (anticlockwise()) render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);

		if (!recursefirst)
			draw_lower_tile(block);
		else
			draw_sprites(&spriteinside[y][x], &sprite_clip_tile);
	} else {
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;
		draw_base();
		if (spriteinside)
			draw_sprites(&spriteinside[y][x], &sprite_clip_tile);
	}
}



static void draw_left_wall(void)
{
	BLOCK *block = state->block[y][x];
	BLOCK *bside = x > 0 ? state->block[y][x-1] : NULL;

	if (block) {
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;

		while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

		switch (block->type) {
			case BLOCK_SLOPE(DIR_AWAY):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_LEFT) || bside->type == BLOCK_SLOPE(DIR_AWAY))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(3, vtxp+1, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_TOWARDS):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_LEFT) || bside->type == BLOCK_SLOPE(DIR_TOWARDS))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				render_polygon(3, vtxp, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_LEFT):
				/* There is no wall on this side. */
				break;
			default: /* including BLOCK_SLOPE(DIR_RIGHT) */
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_LEFT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
				break;
		}

		block = block->nextdown;

		for (; block; block = block->nextdown) {
			while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

			if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_LEFT))) {
				if (block->z < bside->z + BLOCK_RAISED) {
					if (block->z >= bside->z - BLOCK_RAISED) continue;
					if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) continue;
				}
			}
			apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
			apply_matrix_f(&camera, x  , y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
			apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
			apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
			render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
		}
	}
}



static void draw_right_wall(void)
{
	BLOCK *block = state->block[y][x];
	BLOCK *bside = x < state->l->w-1 ? state->block[y][x+1] : NULL;

	if (block) {
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;

		while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

		switch (block->type) {
			case BLOCK_SLOPE(DIR_AWAY):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_RIGHT) || bside->type == BLOCK_SLOPE(DIR_AWAY))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				render_polygon(3, vtxp, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_TOWARDS):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_RIGHT) || bside->type == BLOCK_SLOPE(DIR_TOWARDS))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(3, vtxp+1, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_RIGHT):
				/* There is no wall on this side. */
				break;
			default: /* including BLOCK_SLOPE(DIR_LEFT) */
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_RIGHT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
				break;
		}

		block = block->nextdown;

		for (; block; block = block->nextdown) {
			while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

			if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_RIGHT))) {
				if (block->z < bside->z + BLOCK_RAISED) {
					if (block->z >= bside->z - BLOCK_RAISED) continue;
					if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) continue;
				}
			}
			apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
			apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
			apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
			apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
			render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
		}
	}
}



static void draw_away_wall(void)
{
	BLOCK *block = state->block[y][x];
	BLOCK *bside = y > 0 ? state->block[y-1][x] : NULL;

	if (block) {
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;

		while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

		switch (block->type) {
			case BLOCK_SLOPE(DIR_LEFT):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_AWAY) || bside->type == BLOCK_SLOPE(DIR_LEFT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				render_polygon(3, vtxp, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_RIGHT):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_AWAY) || bside->type == BLOCK_SLOPE(DIR_RIGHT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(3, vtxp+1, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_AWAY):
				/* There is no wall on this side. */
				break;
			default: /* including BLOCK_SLOPE(DIR_TOWARDS) */
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_AWAY))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x  , y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
				break;
		}

		block = block->nextdown;

		for (; block; block = block->nextdown) {
			while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

			if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_AWAY))) {
				if (block->z < bside->z + BLOCK_RAISED) {
					if (block->z >= bside->z - BLOCK_RAISED) continue;
					if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) continue;
				}
			}
			apply_matrix_f(&camera, x+1, y  , block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
			apply_matrix_f(&camera, x+1, y  , block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
			apply_matrix_f(&camera, x  , y  , block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
			apply_matrix_f(&camera, x  , y  , block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
			render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
		}
	}
}



static void draw_towards_wall(void)
{
	BLOCK *block = state->block[y][x];
	BLOCK *bside = y < state->l->h-1 ? state->block[y+1][x] : NULL;

	if (block) {
		vtx[0].u = 0.01;   vtx[0].v = 0.01;
		vtx[1].u = 0.01;   vtx[1].v = 255.99;
		vtx[2].u = 255.99; vtx[2].v = 255.99;
		vtx[3].u = 255.99; vtx[3].v = 0.01;

		while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

		switch (block->type) {
			case BLOCK_SLOPE(DIR_LEFT):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_TOWARDS) || bside->type == BLOCK_SLOPE(DIR_LEFT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(3, vtxp+1, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_RIGHT):
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_TOWARDS) || bside->type == BLOCK_SLOPE(DIR_RIGHT))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				render_polygon(3, vtxp, POLYTYPE_PTEX, dat[GFX_BRICKS].dat);
				break;
			case BLOCK_SLOPE(DIR_TOWARDS):
				/* There is no wall on this side. */
				break;
			default: /* including BLOCK_SLOPE(DIR_AWAY) */
				if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_TOWARDS))) {
					if (block->z < bside->z + BLOCK_RAISED) {
						if (block->z >= bside->z - BLOCK_RAISED) break;
						if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) break;
					}
				}
				apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
				apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
				apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
				apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
				render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
				break;
		}

		block = block->nextdown;

		for (; block; block = block->nextdown) {
			while (bside && bside->z >= block->z + 1.0f - BLOCK_RAISED/2.0f) bside = bside->nextdown;

			if (bside && (BLOCK_CAN_SUPPORT(bside->type) || bside->type == BLOCK_SLOPE(DIR_TOWARDS))) {
				if (block->z < bside->z + BLOCK_RAISED) {
					if (block->z >= bside->z - BLOCK_RAISED) continue;
					if (bside->nextdown && bside->z < bside->nextdown->z + BLOCKS_SEPARATED) continue;
				}
			}
			apply_matrix_f(&camera, x  , y+1, block->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z);
			apply_matrix_f(&camera, x  , y+1, block->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z);
			apply_matrix_f(&camera, x+1, y+1, block->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z);
			apply_matrix_f(&camera, x+1, y+1, block->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z);
			render_polygon(4, vtxp, POLYTYPE_PTEX, dat[BLOCK_DESTRUCTIBLE(block->type) ? GFX_BRICKS : GFX_CONCRETE].dat);
		}
	}
}



/* Slight ambiguity here. The following four functions draw blowers marked
 * with opposite directions, because the direction stored in the blower is
 * the direction in which the sheep are propagated. The function names refer
 * to the side the blowers appear on.
 */



#define MAKE_BLOWER_DRAWER(dir, SHEEPDIR, xl, yl, xr, yr) \
static void draw_##dir##_blowers(void) \
{ \
	BLOWERLIST *bl; \
 \
	vtx[0].u =   0.01f; vtx[0].v =   0.01f; \
	vtx[1].u =   0.01f; vtx[1].v = 255.99f; \
	vtx[2].u = 255.99f; vtx[2].v = 255.99f; \
	vtx[3].u = 255.99f; vtx[3].v =   0.01f; \
 \
	for (bl = state->blowerarray[y][x]; bl; bl = bl->next) { \
		BLOWER *blower = bl->blower; \
		if (blower->d == DIR_##SHEEPDIR) { \
			apply_matrix_f(&camera, x+(xl), y+(yl), blower->z+1, &vtx[0].x, &vtx[0].y, &vtx[0].z); \
			apply_matrix_f(&camera, x+(xl), y+(yl), blower->z  , &vtx[1].x, &vtx[1].y, &vtx[1].z); \
			apply_matrix_f(&camera, x+(xr), y+(yr), blower->z  , &vtx[2].x, &vtx[2].y, &vtx[2].z); \
			apply_matrix_f(&camera, x+(xr), y+(yr), blower->z+1, &vtx[3].x, &vtx[3].y, &vtx[3].z); \
			render_polygon(4, vtxp, POLYTYPE_ATEX_MASK, dat[GFX_BLOWER].dat); \
		} \
	} \
}

MAKE_BLOWER_DRAWER(left   , RIGHT  , 0, 1, 0, 0)
MAKE_BLOWER_DRAWER(right  , LEFT   , 1, 0, 1, 1)
MAKE_BLOWER_DRAWER(away   , TOWARDS, 0, 0, 1, 0)
MAKE_BLOWER_DRAWER(towards, AWAY   , 1, 1, 0, 1)

#undef MAKE_BLOWER_DRAWER



void draw_level(BITMAP *pbmp, STATE *pstate)
{
	BITMAP *overlay;
	int yi; /* Tile with camera over it */

	bmp = pbmp;
	state = pstate;

	set_projection_viewport(bmp->cl, bmp->ct, bmp->cr - bmp->cl, bmp->cb - bmp->ct);

	xi = (state->px << PDISP_SHIFT) + (PDISP_MAX >> 1);
	yi = (state->py << PDISP_SHIFT) + (PDISP_MAX >> 1);

	switch (state->pmd) {
		case DIR_AWAY:    yi -= state->pdisp; break;
		case DIR_RIGHT:   xi += state->pdisp; break;
		case DIR_TOWARDS: yi += state->pdisp; break;
		case DIR_LEFT:    xi -= state->pdisp; break;
	}

	xc = xi * (1.0f / PDISP_MAX);
	yc = yi * (1.0f / PDISP_MAX);

	xi >>= PDISP_SHIFT;
	yi >>= PDISP_SHIFT;

	if (xi < 0) xi = 0; else if (xi >= state->l->w) xi = state->l->w;
	if (yi < 0) yi = 0; else if (yi >= state->l->h) yi = state->l->h;

	zc = state->pz + 0.7 + 0.05*sin(state->pzphase*(2*M_PI/PZPHASE_MAX));
	zc += (state->pdisp * state->pzmd) * (1.0f / PDISP_MAX) + state->pzdisp;

	/* (xc,yc,zc) is used for transformations. (xi,yi) is used for determining
	 * drawing order.
	 */

	{
		float phi = state->pfd*(M_PI/2) + state->pfd_disp;
		float sp = sin(phi), cp = cos(phi);

		float st = sin(state->plookup), ct = cos(state->plookup);

		camera.v[0][0] =  cp;
		camera.v[0][1] =  sp;
		camera.v[0][2] =  0;

		camera.v[1][0] =  st*sp * VIEW_ASPECT;
		camera.v[1][1] = -st*cp * VIEW_ASPECT;
		camera.v[1][2] = -ct    * VIEW_ASPECT;

		camera.v[2][0] =  ct*sp;
		camera.v[2][1] = -ct*cp;
		camera.v[2][2] =  st;

		camera.t[0] = -(xc* cp    + yc* sp    + zc* 0 );
		camera.t[1] =  (xc*-st*sp + yc* st*cp + zc* ct) * VIEW_ASPECT;
		camera.t[2] = -(xc* ct*sp + yc*-ct*cp + zc* st);

		xr =  cp;
		yr =  sp;

		xd =  st*sp;
		yd = -st*cp;
		zd = -ct;
	}

	create_sprite_array();

	{
		SHEEP *sheep;
		for (sheep = state->sheep; sheep; sheep = sheep->next)
			add_sprite(sheep->x, sheep->y, sheep->z, SHEEP_R, SHEEP_R, dat[GFX_SHEEP_IN_BUBBLE].dat);
	}

	bmp->cl++; bmp->ct++; bmp->cr--; bmp->cb--; /* Evil polygon routines */

	rectfill(bmp, bmp->cl, bmp->ct, bmp->cr-1, ((bmp->cb+bmp->ct)>>1)-1, COL_SKY);
	rectfill(bmp, bmp->cl, (bmp->cb+bmp->ct)>>1, bmp->cr-1, bmp->cb-1, COL_WATER);

	draw_water();

	draw_sprites(&spriteoutside[DIR_AWAY], &sprite_clip_away);
	draw_sprites(&spriteoutside[DIR_TOWARDS], &sprite_clip_towards);

	draw_sprites(&spriteoutside[DIR_LEFT], &sprite_clip_left);
	draw_sprites(&spriteoutside[DIR_RIGHT], &sprite_clip_right);

	for (y = 0; y < yi; y++) {
		for (x = 0; x < xi; x++) {
			draw_left_blowers();
			draw_away_blowers();
			draw_tile();
			draw_right_wall();
			draw_towards_wall();
			draw_right_blowers();
			draw_towards_blowers();
		}
		for (x = state->l->w - 1; x > xi; x--) {
			draw_right_blowers();
			draw_away_blowers();
			draw_tile();
			draw_left_wall();
			draw_towards_wall();
			draw_left_blowers();
			draw_towards_blowers();
		}
		draw_left_blowers();
		draw_right_blowers();
		draw_away_blowers();
		draw_tile();
		draw_towards_wall();
		draw_towards_blowers();
	}
	for (y = state->l->h - 1; y > yi; y--) {
		for (x = 0; x < xi; x++) {
			draw_left_blowers();
			draw_towards_blowers();
			draw_tile();
			draw_right_wall();
			draw_away_wall();
			draw_right_blowers();
			draw_away_blowers();
		}
		for (x = state->l->w - 1; x > xi; x--) {
			draw_right_blowers();
			draw_towards_blowers();
			draw_tile();
			draw_left_wall();
			draw_away_wall();
			draw_left_blowers();
			draw_away_blowers();
		}
		draw_left_blowers();
		draw_right_blowers();
		draw_towards_blowers();
		draw_tile();
		draw_away_wall();
		draw_away_blowers();
	}
	for (x = 0; x < xi; x++) {
		draw_left_blowers();
		draw_away_blowers();
		draw_towards_blowers();
		draw_tile();
		draw_right_wall();
		draw_right_blowers();
	}
	for (x = state->l->w - 1; x > xi; x--) {
		draw_right_blowers();
		draw_away_blowers();
		draw_towards_blowers();
		draw_tile();
		draw_left_wall();
		draw_left_blowers();
	}
	draw_left_blowers();
	draw_right_blowers();
	draw_away_blowers();
	draw_towards_blowers();
	draw_tile();

	destroy_sprite_array();

	for (x = 0; x < N_PIPELINE_BLOCKS; x++) {
		float xunit = (bmp->cr+2-bmp->cl) * (1.0f / 80.0f);
		float yunit = (bmp->cb+2-bmp->ct) * (VIEW_ASPECT / 80.0f);
		float xl = bmp->cr+1 + ((x - N_PIPELINE_BLOCKS + state->pipeline_disp) * 8 - 1) * xunit;
		float xr = xl + 8 * xunit;
		float yt = bmp->ct-1 + yunit;
		float yb = yt + 8 * yunit;
		int d = state->pipeline[x];
		if (!BLOCK_CAN_SUPPORT(d)) d = (d - state->pfd) | 4;
		if (x == 0 && state->pipeline_disp <= 1.0f) {
			float swell = (sin(state->pzphase*(4*M_PI/PZPHASE_MAX)) * 0.5f + 0.5f) * (1.0f - state->pipeline_disp);
			xl -= xunit * swell;
			xr += xunit * swell;
			yt -= yunit * swell;
			yb += yunit * swell;
		}
		vtc[0].x = xl; vtc[0].y = yt; vtc[0].v =   0.01f;
		vtc[1].x = xl; vtc[1].y = yb; vtc[1].v = 255.99f;
		vtc[2].x = xr; vtc[2].y = yb; vtc[2].v = 255.99f;
		vtc[3].x = xr; vtc[3].y = yt; vtc[3].v =   0.01f;
		if (d == BLOCK_SLOPE(DIR_RIGHT)) {
			vtc[0].u = 255.99f;
			vtc[1].u = 255.99f;
			vtc[2].u =   0.01f;
			vtc[3].u =   0.01f;
			d = GFX_PIPELINE_LEFT;
		} else {
			vtc[0].u =   0.01f;
			vtc[1].u =   0.01f;
			vtc[2].u = 255.99f;
			vtc[3].u = 255.99f;
			switch (d) {
				case BLOCK_SLOPE(DIR_LEFT):    d = GFX_PIPELINE_LEFT;    break;
				case BLOCK_SLOPE(DIR_AWAY):    d = GFX_PIPELINE_AWAY;    break;
				case BLOCK_SLOPE(DIR_TOWARDS): d = GFX_PIPELINE_TOWARDS; break;
				default:                       d = GFX_PIPELINE_FULL;    break;
			}
		}
		polygon3d_f(bmp, POLYTYPE_ATEX_MASK, dat[d].dat, 4, vtcp);
	}

	overlay = create_bitmap(512, 8);
	if (overlay) {
		float xl = (  4.0f / 320.0f) * (bmp->cr+2-bmp->cl);
		float xr = (316.0f / 320.0f) * (bmp->cr+2-bmp->cl);
		float yt = (188.0f / 200.0f) * (bmp->cb+2-bmp->ct);
		float yb = (196.0f / 200.0f) * (bmp->cb+2-bmp->ct);
		text_mode(0);
		textprintf(overlay, font, 0, 0, COL_WHITE,
			"%3d to be released  %3d saved  %3d lost",
			MIN(state->sheep_left, 999),
			MIN(state->sheep_saved, 999),
			MIN(state->sheep_lost, 999));
		if (state->sheep_left >= state->l->n_sheep) {
			int x = 312 * state->blower_time / state->l->initial_sheep_delay;
			if (x > 0) rectfill(overlay, 0, 0, x-1, 7, COL_YELLOW);
		}
		vtc[0].x = xl; vtc[0].y = yt; vtc[0].u =   0.01f; vtc[0].v = 0.01f;
		vtc[1].x = xl; vtc[1].y = yb; vtc[1].u =   0.01f; vtc[1].v = 7.99f;
		vtc[2].x = xr; vtc[2].y = yb; vtc[2].u = 311.99f; vtc[2].v = 7.99f;
		vtc[3].x = xr; vtc[3].y = yt; vtc[3].u = 311.99f; vtc[3].v = 0.01f;
		polygon3d_f(bmp, POLYTYPE_ATEX_MASK, overlay, 4, vtcp);
		destroy_bitmap(overlay);
	}

	bmp->cl--; bmp->ct--; bmp->cr++; bmp->cb++; /* Evil polygon routines */
	rect(bmp, bmp->cl, bmp->ct, bmp->cr-1, bmp->cb-1, 0);

	if (state->fade < 128) {
		RGB *orig = dat[THE_PALETTE].dat;
		PALETTE faded;
		int i;
		for (i = 0; i < PAL_SIZE; i++) {
			faded[i].r = (int)orig[i].r * state->fade >> 7;
			faded[i].g = (int)orig[i].g * state->fade >> 7;
			faded[i].b = (int)orig[i].b * state->fade >> 7;
		}
		set_palette(faded);
	}
}
