#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>
#include "skyfly.h"
#include "stereo.h"			/* stereo3d code additions */

/* Define GL_Exit() if not using MGL GLUT. */
#ifdef  USE_GLUT
#define GL_exit()	glutExit()
#else
#define GL_exit()	GM_exit()
#endif

#define ERR_WARNING                     0x1
#define ERR_FATAL                       0x2
#define ERR_SYSERR                      0x4

#define AMALLOC(a, type, num, func)     {                     \
	if((int)(a = (type*)malloc(sizeof(type)*(num))) <= 0)     \
		err_msg(ERR_FATAL, func, "amalloc failed");           \
}

float   ScaleZ   = 2.3;     /* Terrain height scale factor */
int     CellDim = 4;        /* Terrain cell is CellDim X Celldim quads */
int     NumCells = 36;      /* Terrain grid is NumCells X NumCells cells */
int     GridDim;            /* Terrain grid is GridDim X GridDim quads */
float   XYScale;            /* Conversion from world-space to grid index */
float   CellSize;           /* World-space size of cell */

int     Init_pos;           /* if true, set initial position and kbd mode */
float   Init_x, Init_y, Init_z, Init_azimuth;
int     rgbmode = GL_TRUE;

/* Color index ramp info */
int sky_base, terr_base;
int plane_colors[3];

/*
 * Data that changes from frame to frame needs to be double-buffered because
 * two processes may be working on two different frames at the same time.
 */
typedef struct buffered_data_struct {

        /* objects */
        perfobj_t       paper_plane_pos_obj[NUM_PLANES];
        perfobj_t       viewer_pos_obj;

        /* flags */
        unsigned long   paper_plane_pos_flags[2];
        unsigned long   viewer_pos_flags[2];

        /* data */
        float  paper_plane_position[NUM_PLANES][6];
        float  viewer_position[4];

} buffered_data;

/*
 * This is the per-pipe data structure which holds pipe id, semaphores,
 * and variable data buffers. Both gfxpipe and buffer structures live in
 * shared memory so the sim can communicate with its forked children.
 */
typedef struct gfxpipe_data_struct {
        int             gfxpipenum;
		buffered_data   **buffers;

} gfxpipe_data;

static gfxpipe_data     *gfxpipe;       /* A processes' gfxpipe struct */
static gfxpipe_data     *gfxpipes[2];   /* Maximum of 2 graphics pipes */
static int              num_pipes;
float           		fog_params[4];  /* Fog and clear color */
static float            fog_density = 0.025*2;
float           		far_cull  = 31.;    /* Far clip distance from eye */
int						mipmap = 0;
static int              texmode = GL_NEAREST;

static int              threecomp = 1;

int 	dither = GL_TRUE, fog = GL_FALSE;
int     Wxsize = 320, Wysize = 240; /* Default to 320x240 window */

/*
 * All non-variable data like geometry is stored in shared memory. This way
 * forked processes avoid duplicating data unnecessarily.
 */
shared_data     *SharedData;

/* //////////////////////////////////////////////////////////////////////// */

void sim_proc(void);
void sim_cyclops(void);
void sim_dualchannel(void);
void sim_singlechannel(void);
void cull_proc(void);
void draw_proc(void);
void sim_exit(void);
void init_misc(void);
void init_shmem(void); 
void init_terrain(void);
void init_clouds(void);
void init_paper_planes(void);
void init_positions(void);
void init_gfxpipes(void);
void init_gl(int gfxpipenum);
void err_msg(int type, char* func, char* error);
void fly(perfobj_t *viewer_pos);
void fly_paper_planes(perfobj_t *paper_plane_pos);
float terrain_height(void);
void openXinput(void);
void closeXinput(void);
int Xgetbutton(int button);

void init_skyfly(void)
{
	init_shmem();
    init_gfxpipes();
    init_clouds();
    init_terrain();
    init_paper_planes();
    init_positions();

	gfxpipe = gfxpipes[0];
	init_gl(gfxpipe->gfxpipenum);
}

/*
 * This is a single-channel version of the dual-channel simulation
 * described above.
 */
void sim_singlechannel(void)
{
	buffered_data  **buffered = gfxpipes[0]->buffers;

	fly(&(buffered[0]->viewer_pos_obj));
	fly_paper_planes(buffered[0]->paper_plane_pos_obj);
}

/*-------------------------------------- Cull ------------------------------*/

/*
 *   The cull and draw processes operate in a classic producer/consumer, 
 * write/read configuration using a ring buffer. The ring consists of pointers
 * to perfobj's instead of actual geometric data. This is important because
 * you want to minimize the amount of data 'shared' between two processes that
 * run on different processors in order to reduce cache invalidations.
 *   enter_in_ring and get_from_ring spin on ring full and ring empty 
 * conditions respectively.
 *   Since cull/draw are shared group processes(sproc), the ring buffer is
 * in the virtual address space of both processes and shared memory is not
 * necessary.
*/

#define RING_SIZE   1000    /* Size of ring */

typedef struct render_ring_struct {
    volatile unsigned long      head, tail;
    perfobj_t                   **ring;
} render_ring;

render_ring ringbuffer;

void        enter_in_ring(perfobj_t *perfobj);
perfobj_t*  get_from_ring(void);

void cull_proc(void)
{

    static struct cull {
        perfobj_t       **cells;
        perfobj_t       viewer_pos_obj[2];
        unsigned long   viewer_pos_flags[4];
        float           viewer_position[2][4];
        float           fovx, side, farr, epsilon, plane_epsilon;
    } cull;

    static init = 0;

    if (!init) {
        int             x, y;

        cull.fovx = FOV *(float) Wxsize /(float) Wysize;
        cull.side = far_cull / cosf(cull.fovx / 2.);
        cull.farr = 2.* cull.side * sinf(cull.fovx / 2.);
        cull.epsilon = sqrtf(2.) * CellSize / 2.;
        cull.plane_epsilon = .5;

        cull.cells = (perfobj_t **) malloc(NumCells * NumCells * sizeof(perfobj_t *));
        for (x = 0; x < NumCells; x++)
            for (y = 0; y < NumCells; y++)
                cull.cells[x * NumCells + y] =
                    &(SharedData->terrain_cells[x * NumCells + y]);

        ringbuffer.ring = malloc(RING_SIZE * sizeof(perfobj_t *));
        ringbuffer.head = ringbuffer.tail = 0;

        cull.viewer_pos_obj[0].flags = cull.viewer_pos_flags;
        cull.viewer_pos_obj[0].vdata = cull.viewer_position[0];
        cull.viewer_pos_obj[1].flags = cull.viewer_pos_flags;
        cull.viewer_pos_obj[1].vdata = cull.viewer_position[1];

        *(cull.viewer_pos_flags) = PD_VIEWER_POS;
        *(cull.viewer_pos_flags + 1) = PD_END;
        init = 1;
    }

	{
	float           *viewer;
	float           vX, vY, vazimuth, px, py;
	float           left_area, right_area, far_area;
	float           left_dx, left_dy, right_dx, right_dy, far_dx=0, far_dy=0;
	float           ax, ay, bx, by, cx, cy;
	float           minx, maxx, miny, maxy;
	int             i, buffer = 0;
	int             x, y, x0, y0, x1, y1;
	perfobj_t      *viewer_pos, *paper_plane_pos;
	buffered_data  *buffered;
	perfobj_t      *terrain_texture = &(SharedData->terrain_texture_obj);
	perfobj_t      *paper_plane = &(SharedData->paper_plane_obj);
	perfobj_t      *paper_plane_start = &(SharedData->paper_plane_start_obj);
	perfobj_t      *paper_plane_end = &(SharedData->paper_plane_end_obj);
	perfobj_t      *clouds_texture = &(SharedData->clouds_texture_obj);
	perfobj_t      *clouds = &(SharedData->clouds_obj);
	int             visible_planes[NUM_PLANES], nplanes;

	buffered = gfxpipe->buffers[buffer];

	viewer_pos = &(buffered->viewer_pos_obj);
	paper_plane_pos = buffered->paper_plane_pos_obj;

	vX = *((float *) viewer_pos->vdata + 0);
	vY = *((float *) viewer_pos->vdata + 1);
	vazimuth = *((float *) viewer_pos->vdata + 3);

	viewer = cull.viewer_position[buffer];

	viewer[0] = vX;
	viewer[1] = vY;
	viewer[2] = *((float *) viewer_pos->vdata + 2);
	viewer[3] = vazimuth;

	/*
	 * Begin cull to viewing frustrum
	 */
	ax = (vX - sinf(-vazimuth + cull.fovx *.5) * cull.side);
	ay = (vY + cosf(-vazimuth + cull.fovx *.5) * cull.side);
	bx = vX;
	by = vY;
	cx = (vX + sinf(vazimuth + cull.fovx *.5) * cull.side);
	cy = (vY + cosf(vazimuth + cull.fovx *.5) * cull.side);

	minx = MIN(MIN(ax, bx), cx);
	miny = MIN(MIN(ay, by), cy);
	maxx = MAX(MAX(ax, bx), cx);
	maxy = MAX(MAX(ay, by), cy);

	x0 = MAX((int) (minx / CellSize), 0);
	x1 = MIN((int) (maxx / CellSize) + 1, NumCells);
	y0 = MAX((int) (miny / CellSize), 0);
	y1 = MIN((int) (maxy / CellSize) + 1, NumCells);

	left_dx = ax - bx;
	left_dy = ay - by;
	right_dx = cx - bx;
	right_dy = cy - by;

	enter_in_ring(&cull.viewer_pos_obj[buffer]);

	if (viewer[2]<SKY_HIGH) {
	   /* draw clouds first */
	   enter_in_ring(clouds_texture);
	   enter_in_ring(clouds);
	}

	enter_in_ring(terrain_texture);
	/*
	 * Add visible cells to ring buffer 
	 */
	for (x = x0; x < x1; x++) {
		for (y = y0; y < y1; y++) {
			float           cntrx =(x +.5) * CellSize;
			float           cntry =(y +.5) * CellSize;

			left_area = left_dx * (cntry - by) - left_dy * (cntrx - bx);
			right_area = right_dx * (cntry - by) - right_dy * (cntrx - bx);
			far_area = far_dx * (cntry - ay) - far_dy * (cntrx - ax);

			if ((left_area < cull.epsilon * cull.side && right_area > -cull.epsilon * cull.side
				/*&& far_area < cull.epsilon * cull.far*/) || 0) {
					enter_in_ring(cull.cells[x * NumCells + y]);
			}
		}
	}

	nplanes = 0;
	enter_in_ring(paper_plane_start);
	/*
	 * Add visible planes to ring buffer
	*/
	for (i = 0; i < NUM_PLANES; i++) {

		px = *((float *) paper_plane_pos[i].vdata + 0);
		py = *((float *) paper_plane_pos[i].vdata + 1);
		left_area = left_dx * (py - by) - left_dy * (px - bx);
		right_area = right_dx * (py - by) - right_dy * (px - bx);
		far_area = far_dx * (py - ay) - far_dy * (px - ax);

		if (left_area < cull.plane_epsilon * cull.side && right_area > -cull.plane_epsilon * cull.side
			/*&& far_area < cull.plane_epsilon * cull.far*/) {
			enter_in_ring(&paper_plane_pos[i]);
			enter_in_ring(paper_plane);
			visible_planes[nplanes++] = i;
		}
	}

	enter_in_ring(paper_plane_end);

	if (viewer[2]>SKY_HIGH) {
	   /* draw clouds after everything else */
	   enter_in_ring(clouds_texture);
	   enter_in_ring(clouds);
	}

	enter_in_ring((perfobj_t *) 0);     /* 0 indicates end of frame */

	buffer = !buffer;
	}
}

void enter_in_ring(perfobj_t *perfobj)
{
	while (ringbuffer.head == RING_SIZE+ringbuffer.tail-1) {}
	ringbuffer.ring[ringbuffer.head % RING_SIZE] = perfobj;
	ringbuffer.head++;
}

perfobj_t* get_from_ring(void)
{
    static perfobj_t *pobj;

    while(ringbuffer.tail == ringbuffer.head) {}
    pobj = ringbuffer.ring[ringbuffer.tail % RING_SIZE];
    ringbuffer.tail++;
    return pobj;        
}

/*-------------------------------------- Draw ------------------------------*/

void draw_proc(void)
{
	perfobj_t      *too_draw;

	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	while (too_draw = get_from_ring()) {
		drawperfobj(too_draw);
	}
}


/*------------------------------- Init -----------------------------------*/

void init_texture_and_lighting(void);
void init_buffered_data(buffered_data *buffered);

void init_misc(void)
{
	float   density;

	threecomp = rgbmode;

	/*
	 * Compute fog and clear color to be linear interpolation between blue
	 * and white.
	 */
	density = 1.- expf(-5.5 * fog_density * fog_density *
							  far_cull * far_cull);
	density = MAX(MIN(density, 1.), 0.);

	fog_params[0] = .23 + density *.57;
	fog_params[1] = .35 + density *.45;
	fog_params[2] = .78 + density *.22;
	fog_params[3] = 1.0f;
}

void init_shmem(void)
{
	int			   i;
    unsigned long  *flagsptr;
    perfobj_vert_t *vertsptr;
    int             nflags, nverts;

    AMALLOC(SharedData, shared_data, 1, "init_shmem");
    AMALLOC(SharedData->terrain_cells, perfobj_t,
            NumCells * NumCells, "init_shmem");
    AMALLOC(SharedData->terrain_cell_flags, unsigned long *,
            NumCells * NumCells, "init_shmem");
    AMALLOC(SharedData->terrain_cell_verts, perfobj_vert_t *,
            NumCells * NumCells, "init_shmem");

    /*
     * Allocate the flags and vertices of all terrain cells in 2 big chunks
     * to improve data locality and consequently, cache hits 
     */
    nflags = 2 * CellDim + 1;
	AMALLOC(flagsptr, unsigned long, nflags * NumCells * NumCells, "init_shmem");
	nverts = (CellDim + 1) * 2 * CellDim;
	AMALLOC(vertsptr, perfobj_vert_t, nverts * NumCells * NumCells, "init_shmem");

	for (i = 0; i < NumCells * NumCells; i++) {
		SharedData->terrain_cell_flags[i] = flagsptr;
		flagsptr += nflags;
		SharedData->terrain_cell_verts[i] = vertsptr;
		vertsptr += nverts;
	}
}

/*
 * Initialize gfxpipe data structures. There is one set of semaphores
 * per pipe.
 */
void init_gfxpipes(void)
{
	int             i, j;

	num_pipes = 1;

	for (i = 0; i < num_pipes; i++) {

		AMALLOC(gfxpipes[i], gfxpipe_data, 1, "initgfxpipes");
		AMALLOC(gfxpipes[i]->buffers, buffered_data *, NBUFFERS,
				"init_gfxpipes");
		gfxpipes[i]->gfxpipenum = i;
	}

	for (j = 0; j < NBUFFERS; j++) {
		AMALLOC(gfxpipes[0]->buffers[j], buffered_data, 1,
				"init_gfxpipes");
		init_buffered_data(gfxpipes[0]->buffers[j]);
		}
}

void init_buffered_data(buffered_data *buffered)
{
    int             i;
    perfobj_t      *pobj;

    pobj = &(buffered->viewer_pos_obj);
    pobj->flags = buffered->viewer_pos_flags;
    pobj->vdata = buffered->viewer_position;

    *(buffered->viewer_pos_flags) = PD_VIEWER_POS;
    *(buffered->viewer_pos_flags + 1) = PD_END;

    for (i = 0; i < NUM_PLANES; i++) {
        pobj = &(buffered->paper_plane_pos_obj[i]);
        pobj->flags = buffered->paper_plane_pos_flags;
        pobj->vdata = buffered->paper_plane_position[i];
    }
    *(buffered->paper_plane_pos_flags) = PD_PAPER_PLANE_POS;
    *(buffered->paper_plane_pos_flags + 1) = PD_END;
}

#ifdef 	STEREO
/* Additional routine for building stereo projection matrixes. */
void calcStereoMatrix(void)
{
	glMatrixMode(GL_PROJECTION);
	/* rebuild frustum with rightward image shift for left eye view */
	glPushMatrix();
	glLoadIdentity();
	glFrustum(-fFrustumWidth2+fDxImageShift, fFrustumWidth2+fDxImageShift,
			  -fFrustumHeight2, fFrustumHeight2,
			  fFrustumZnear, fFrustumZfar);
	/* offset viewpoint for left eye perspective */
	glTranslatef(fDxViewpoint, 0.0, 0.0);
	glGetFloatv(GL_PROJECTION_MATRIX, &mProjectionLeft[0]);
	glPopMatrix();
	/* rebuild frustum with leftward image shift for right eye view */
	glPushMatrix();
	glLoadIdentity();
	glFrustum(-fFrustumWidth2-fDxImageShift, fFrustumWidth2-fDxImageShift,
			  -fFrustumHeight2, fFrustumHeight2,
			  fFrustumZnear, fFrustumZfar);
	/* offset viewpoint for right eye perspective */
	glTranslatef(-fDxViewpoint, 0.0, 0.0);
	glGetFloatv(GL_PROJECTION_MATRIX, &mProjectionRight[0]);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}
#endif

void init_gl(int gfxpipenum)
{
	glDrawBuffer(GL_BACK);
	glClear(GL_COLOR_BUFFER_BIT);
	if (!rgbmode)
		glIndexi(0);

	set_fog(fog);

	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);

    set_dither(dither);

	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );

	init_texture_and_lighting();

	glMatrixMode(GL_PROJECTION);
	gluPerspective(FOV * RAD_TO_DEG, (float)Wxsize/(float)Wysize,
														 .1, far_cull *.95);
#ifdef  STEREO
	/* remember frustum settings for stereo3d perspective adjustments */
	fFrustumZnear = 0.1;
	fFrustumZfar = far_cull * 0.95;
	/* gluPerspective() trigonometry: tan(FOVangle/2) = (Yheight/2) / Znear */
	fFrustumHeight2 = tan(FOV/2.0) * fFrustumZnear;
	fFrustumWidth2 = fFrustumHeight2 * (GLfloat)Wxsize/(GLfloat)Wysize;
	/* estimate frustum shift = approx 5% of frustum width */
	fDxImageShift = 0.05 * fFrustumWidth2;
	/* set left/right offset from default perspective viewpoint */
	fDxViewpoint = 0.10;
	calcStereoMatrix();
#endif

	glMatrixMode(GL_MODELVIEW);
	glHint(GL_FOG_HINT, GL_FASTEST);
	glFogi(GL_FOG_MODE, GL_EXP2);
	glFogf(GL_FOG_DENSITY, fog_density);

	if (rgbmode) {
		glFogfv(GL_FOG_COLOR, fog_params);
		if (fog && fog_density > 0)
			glEnable(GL_FOG);
	} else if (FOG_LEVELS > 1) {
		glFogi(GL_FOG_INDEX, FOG_LEVELS-1);
		if (fog)
			glEnable(GL_FOG);
	}
}

unsigned char* read_bwimage(char *name, int *w, int *h);

void init_texture_and_lighting(void)
{

    unsigned char  *bwimage256, *bwimage128;
    int             w, h;

    if(!(bwimage256 = (unsigned char*) read_bwimage("terrain.bw", &w, &h)))
       if(!(bwimage256 = (unsigned char *) 
                read_bwimage("/usr/demos/data/textures/terrain.bw", &w, &h)))
		err_msg(ERR_FATAL, "init_texture_and_lighting()",
										"Can't open terrain.bw");

	if(w != 256 || h != 256)
		err_msg(ERR_FATAL, "init_texture_and_lighting()",
										"terrain.bw must be 256x256");

	if (!(bwimage128 = (unsigned char *) read_bwimage("clouds.bw", &w, &h)))
		if (!(bwimage128 = (unsigned char *)
			  read_bwimage("/usr/demos/data/textures/clouds.bw", &w, &h)))
			err_msg(ERR_FATAL, "init_misc()", "Can't open clouds.bw");

	if (w != 128 || h != 128)
		err_msg(ERR_FATAL, "init_misc()", "clouds.bw must be 128x128");

	if (mipmap)
		texmode = GL_LINEAR_MIPMAP_LINEAR;
	else
		texmode = GL_NEAREST;

	if (!threecomp) {
		/*
		 * 1 and 2-component textures offer the highest performance on SkyWriter
		 * so they are the most recommended.
		 */
		glBindTexture(GL_TEXTURE_2D, 1);
		if (!mipmap)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texmode);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texmode);
		if (mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, /*0,*/ 1, 256, 256, /*0,*/ GL_LUMINANCE, GL_UNSIGNED_BYTE, bwimage256);
		else if (rgbmode)
			glTexImage2D(GL_TEXTURE_2D, 0, 1, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bwimage256);
		else {
#define TXSIZE 128
			GLubyte buf[TXSIZE*TXSIZE];
			int ii;
			gluScaleImage(GL_LUMINANCE, 256, 256, GL_UNSIGNED_BYTE, bwimage256,
						  TXSIZE, TXSIZE, GL_UNSIGNED_BYTE, buf);
			for (ii = 0; ii < TXSIZE*TXSIZE; ii++) {
				buf[ii] = terr_base +
					FOG_LEVELS * (buf[ii] >> (8-TERR_BITS));
			}
			glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, TXSIZE, TXSIZE,
						 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, buf);
#undef TXSIZE
		}

		glBindTexture(GL_TEXTURE_2D, 2);
		if (!mipmap)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texmode);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texmode);
		if (mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, /*0,*/ 1, 128, 128, /*0,*/ GL_LUMINANCE, GL_UNSIGNED_BYTE, bwimage128);
		else if (rgbmode)
			glTexImage2D(GL_TEXTURE_2D, 0, 1, 128, 128, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, bwimage128);
		else {
#define TXSIZE 64
			GLubyte buf[TXSIZE*TXSIZE];
			int ii;
			gluScaleImage(GL_LUMINANCE, 128, 128, GL_UNSIGNED_BYTE, bwimage128,
						  TXSIZE, TXSIZE, GL_UNSIGNED_BYTE, buf);
			for (ii = 0; ii < TXSIZE*TXSIZE; ii++) {
				buf[ii] = sky_base +
					FOG_LEVELS * (buf[ii] >> (8-SKY_BITS));
			}
			glTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, TXSIZE, TXSIZE,
						 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, buf);
#undef TXSIZE
		}
	} else {
		float r0, r1, g0, g1, b0, b1;
		int i;
		unsigned char *t256 = (unsigned char *)malloc(256*256*3);

		/* terrain */
		r0 = 0.40f;   r1 = 0.30f;
		g0 = 0.30f;   g1 = 0.70f;
		b0 = 0.15f;   b1 = 0.10f;

		for(i = 0; i < 256*256; i++) {
			float t = bwimage256[i] / 255.0f;
			t256[3*i+0] = (unsigned char) (255.0f * (r0 + t*t * (r1-r0)));
			t256[3*i+1] = (unsigned char) (255.0f * (g0 + t * (g1-g0)));
			t256[3*i+2] = (unsigned char) (255.0f * (b0 + t*t * (b1-b0)));
		}
		glBindTexture(GL_TEXTURE_2D, 1);
		if (!mipmap)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texmode);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texmode);
		if (mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, /*0,*/ 3, 256, 256, /*0,*/ GL_RGB, GL_UNSIGNED_BYTE, t256);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, t256);


		/* sky without fog */
		r0 = 0.23;  r1 = 1.0f;
		g0 = 0.35;  g1 = 1.0f;
		b0 = 0.78;  b1 = 1.0f;
		for(i = 0; i < 128*128; i++) {
			float t = bwimage128[i] / 255.0f;
			t256[3*i+0] = (unsigned char) (255.0f * (r0 + t * (r1-r0)));
			t256[3*i+1] = (unsigned char) (255.0f * (g0 + t * (g1-g0)));
			t256[3*i+2] = (unsigned char) (255.0f * (b0 + t * (b1-b0)));
		}
		glBindTexture(GL_TEXTURE_2D, 2);
		if (!mipmap)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texmode);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texmode);
		if (mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, /*0,*/ 3, 128, 128, /*0,*/ GL_RGB, GL_UNSIGNED_BYTE, t256);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, t256);

		/* sky with fog */
		r0 = fog_params[0];  r1 = 1.0f;
		g0 = fog_params[1];  g1 = 1.0f;
		b0 = fog_params[2];  b1 = 1.0f;
		for(i = 0; i < 128*128; i++) {
			float t = bwimage128[i] / 255.0f;
			t256[3*i+0] = (unsigned char) (255.0f * (r0 + t * (r1-r0)));
			t256[3*i+1] = (unsigned char) (255.0f * (g0 + t * (g1-g0)));
			t256[3*i+2] = (unsigned char) (255.0f * (b0 + t * (b1-b0)));
		}
		glBindTexture(GL_TEXTURE_2D, 3);
		if (!mipmap)
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texmode);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texmode);
		if (mipmap)
			gluBuild2DMipmaps(GL_TEXTURE_2D, /*0,*/ 3, 128, 128, /*0,*/ GL_RGB, GL_UNSIGNED_BYTE, t256);
		else
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, t256);
		free(t256);
	}

	free(bwimage256);
	free(bwimage128);

	/* both textures use BLEND environment */
	if (rgbmode) {
		if (threecomp) {
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		}
		else {
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
		}
	} else if (FOG_LEVELS > 1) {
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);
	} else {
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	}

	{
		GLfloat position[] = { LX, LY, LZ, 0., };
		GLfloat one[] = { 1.0, 1.0, 1.0, 1.0 };

		if (rgbmode)
			glLightfv(GL_LIGHT0, GL_AMBIENT, one);
		glLightfv(GL_LIGHT0, GL_POSITION, position);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, one);
		glLightfv(GL_LIGHT0, GL_SPECULAR, one);
	}

	if (rgbmode) {
		GLfloat ambient[] = { 0.3, 0.3, 0.1, 0.0 };
		GLfloat diffuse[] = { 0.7, 0.7, 0.1, 0.0 };
		GLfloat zero[] = { 0.0, 0.0, 0.0, 0.0 };

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, zero);
	} else {
		glMaterialiv(GL_FRONT_AND_BACK, GL_COLOR_INDEXES, plane_colors);
	}

	{
		glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 0);
		glEnable(GL_LIGHT0);
	}
}

void lightpos(void)
{
	GLfloat position[] = { LX, LY, LZ, 0., };
	glLightfv(GL_LIGHT0, GL_POSITION, position);
}

void texenv(int env)
{
	GLfloat colors[3][4] = { { 0., 0., 0., 0., },
							 { .1, .1, .1, 0., },       /* terrain */
							 { 1., 1., 1., 0., }, };    /* sky */
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, colors[env]);
}

/*-------------------------------- Utility ---------------------------------*/

void err_msg(int type, char* func, char* error)
{
	char    msg[512];

	if (type & ERR_WARNING) {
		fprintf(stderr, "Warning:  ");
		sprintf(msg, "Warning:  %s", error);
	}
	else if (type & ERR_FATAL) {
		fprintf(stderr, "FATAL:  ");
		sprintf(msg, "FATAL:  %s", error);
	}

	fprintf(stderr, "%s: %s\n", func, error);
	if (type & ERR_SYSERR) {
		perror("perror() = ");
		fprintf(stderr, "errno = %d\n", errno);
	}
	fflush(stderr);

	if (type & ERR_FATAL) {
		GL_exit();
		exit(-1);
		}
}

void set_fog(int enable)
{
	fog = enable;
	if (fog) {
		glEnable(GL_FOG);
		if (rgbmode)
			glClearColor(fog_params[0], fog_params[1], fog_params[2], 1.0);
		else {
            glClearIndex(sky_base + FOG_LEVELS - 1);
        }
    } else {
        glDisable(GL_FOG);
        if (rgbmode)
            glClearColor(0.23, 0.35, 0.78, 1.0);
        else {
            glClearIndex(sky_base);
        }
    }
}

void set_dither(int enable)
{
    dither = enable;
    if (dither) {
        glEnable(GL_DITHER);
    } else {
        glDisable(GL_DITHER);
    }
}

